Swift 指针

因为我并不是计算机专业出身,所以下面对C语言和指针的理解都来自于很久很久上过的C课程和工作以后接触的oc以及swift。说的不对的地方欢迎指正。

首先需要说明的概念是比特(bit),理论上,这是一个计算系统最小的单位,不是0就是1,可以简单理解成为,通电和断电。八个比特组成一个字节(byte),因为现代家用和商用的操作系统一般是32位或者64位系统。那么这是什么意思呢?不严谨的理解就是操作系统指挥cpu一次性拾取这么多位数据。也就是说32位系统一次从内存读取4个字节到32位cpu而64位系统一次读取8个字节到cpu。读到这里,作为一个工程师,你肯定会问以下两个问题:

1. 那我要是一个数据占不满这么多位怎么办啊?
2. 我要是数据超出了cpu怎么解决啊?

如果不是工程师,那么有可能会觉得问这样问题的人是不是有病?没满就没满啊,满了就再开一个不就好了啊?事实上,这涉及到一个最简单的工程思维,很多“常识性”的浪费其实并不是真正浪费。比如有一个叫做对齐的概念。懂一点计算机常识的人或多或少都听过这个概念,比如ssd的4k对齐。那么是内存对齐呢?其实就是利用占位符(也就是0)来填满没有被使用的内存空间,如果你要储存一个int 10在内存中,实际上是10 0 0 0 0 0 0 0 0,当然,这里只是一个示例,显然不可能是10存储在内存中。表面上看起来这是一种巨大的浪费,为什么我要占位而不能接着利用剩下来的空间呢?因为在cpu读取的时候,对齐之后的数据可以大大降低读写次数,比如你要存储“10个人”这三个字,如果我只想提取10出来,那么我只需要找到指向integer的指针然后直接读取所有步长就可以了,这会很大程度上减少非必要的读取次数。当然除此以外,还有很多其他的原因比如原子性之类的原因,具体的可以参考操作系统设计之类的资料。那么当我们说读取的时候是一个什么样的概念呢?其实有点像送信,每个房屋都会在邮政有一个代码,邮递员就是根据这个代码把邮件快递送到你家的,每个位也有一个在操作系统注册的代码,操作系统就是依赖这个代码来让cpu去找到具体的数据的。

说了这么多,所以到底什么是指针呢?指针就是一个整型数据,储存这个某一个数据在内存的中的地址。一般人看到这里就会懵逼了,既然指针是指向数据的一个整数,那么整数本身不也是一种数据吗?你是不是在蒙我?这怎么听起来哪里不对?所以指针可以指向另外一个指针?答案就是这么粗暴,是这样的。那你妹的不是太坑爹了吗?所以你给了我一个整数,我怎么知道这是一个数据还是一个指向数据的指针还是一个指向指针的指针?是的,你又猜对了,你不知道,至少对于swift来说,你不知道。很好,那怎么解决这个问题呢?swift对c指针进行了一些改造,给了你八种指针:

1. UnsafeMutablePointer<T>
2. UnsafePointer<T>
3. UnsafeMutableBufferPointer<T>
4. UnsafeBufferPointer<T>
5. UnsafeMutableRawPointer  
6. UnsafeMutableRawBufferPointer
7. UnsafeRawBufferPointer

又是一脸懵逼,这都什么玩意?c 语言里面就一个指针,swift什么鬼?给我这么多名字比我太奶奶裹脚布太长的东西?冷静一下,仔细看看这些东西,其实还是很有规律的,pointer当然表示这些都是指针,unsafe表示不安全,use on your own risk。这句话的意思是,swift本身是一门安全的语言,你们可劲作,崩了算我输。编译器负责开创,管理,销毁内存中的数据,所以像我这样非计算机专业出身的智障开发者,要用什么开什么就好了,完全没有任何技术含量,不需要任何计算机常识。但是,这个的前提是编译器来管理内存而不是开发者,如果你就是这么任性,不听不听,飞鞋点金。那么,swift能怎么办?只能贴个免责申明,对吧,人之常情,你也经常看到什么“仅供研究学习,不得用于商业”之类的东西,unsafe就是这个意思。作为ios开发者,mutable还是可以看得懂的,generic也是看得懂的,所以只剩下raw和buffer了。Raw表示这个指针直接指向数据而不是指向其他指针!!!c语言常识告诉我们,所有变量名,函数名,struct名都是指针。所以这就是为什么指向这些东西的指针实质上是指向指针的指针。Buffer是什么意思呢?其实就是这个单词本身的意思,你可以理解为一个array,表示这个一个指针群,可能包含不止一个指针。这样一来,我们已基本能够通过名字知道该如何使用一个指针了。

还记得C语言是如何创建一个整数的吗? int x = 8. 这行代码说明了什么呢?说明了int这个东西是一个raw value,所以呢,如果想要创建一个指向int的指针,我们应该使用UnsafeMutableRawPointer:

let intPoint = UnsafeMutableRawPointer.allocate(bytes: bytes, alignedTo: alignment)

通过系统的Documentaion,我们可以找到上面这个方法来初始化一个指针,那么这个函数名很直白的告诉你,这个函数的作用是在heap区allocate一个空间,空间包含有bytes位数据,按照alignment方法对齐。那么我们再按照documentation可以很清楚的得知如何去开创一个内存空间:

/*
MemoryLayout<Int>
MemoryLayout是一个抽象类,使用的时候需要告诉这个类具体的type
你可以使用你自己的class或者struct,MemoryLayout<MyClass>来获取相关内存的信息。
对于MemoryLayout,我们有三个很重要的属性:size,alignment,stride。
当你开创了空间以后系统会自动给这三个属性赋值,会告诉你,内存块大小是多少,对齐方式是怎么样的,递进步长是多少。
如果我们要创建一个指向一个Int类型数据的指针,那么首先,Int是不会超过一个byte的,对齐方式是64位对齐。
*/
let bytes = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let intPoint = UnsafeMutableRawPointer.allocate(bytes: bytes, alignedTo: alignment)

如果你说,这他么有什么用?好,那你听过数组吗?我们通过指针来创建一个指定大小swift数组Array<UInt>:

class IntArray {

    private let count:Int
    private var index = -1
    private var first = true
    private var start: UnsafeMutableRawPointer!
    private var bufferPointer: UnsafeRawBufferPointer!
    private let stride = MemoryLayout<Int>.stride
    private let alignment = MemoryLayout<Int>.alignment
    private let q = DispatchQueue(label: "ReservedQ")
    private var byteCount:Int {
        get{return stride * count}
    }
    
    init(count:Int) {
        self.count = count
        start = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
    }
    
    func append(_ newElement:UInt) {
        q.sync {
            guard index < count else {return}
            index += 1
            if first {
                first = false
                start.storeBytes(of: newElement, as: UInt.self)
            }else {
                start.advanced(by: stride * index).storeBytes(of: newElement, as: UInt.self)
            }
            bufferPointer = UnsafeRawBufferPointer(start: start, count: byteCount)
            for (i, byte) in bufferPointer.enumerated() {
                guard i < (index+1)*8 else {break}
                if byte != 0 && byte != 255 {
                    print(byte)
                }else if i%8 == 0 && byte == 0 {
                    print(0)
                }
            }
            print("--------------------")
        }
    }
    
    deinit {
        //这个函数是不可缺少的,这是unsafe的本质,you are on your own!你必须自己管理内存了。
        start.deallocate(bytes: byteCount, alignedTo: alignment)
    }
}

运行一下:

let arr = IntArray(count: 3)
arr.append(40)
arr.append(3)
arr.append(0)

结果是:
40
--------------------
40
3
--------------------
40
3
0
--------------------
当然直接说这个就是一个Array实在是太夸张了,但是,通过这么一个例子,我们可以更好的去理解如何在实际工作中去使用指针。毕竟对于iOS开发来说,指针这种东西一般也就是用来写一写C函数的wrapper的。比如给keychain写一个wrapper,给一些第三方的SDK写一写Wrapper(可能在今天正常的公司都会给个最起码是objective c的SDK,但是公司内部的很多硬件SDK还是只有C和C++接口 T_T)。但是这并不表示指针这样一样强力工具对于iOS开发就是不需要掌握的,因为懂指针可以让你用很简单的几行代码完成一些以前很难完成的工作。我们来看一个来自stackoverflow的例子:遍历整个enum的所有case,如果你不会指针,那么你写出来的方案就会是:

enum ProductCategory : String {
     case Washers = "washers", Dryers = "dryers", Toasters = "toasters"

     static let allValues = [Washers, Dryers, Toasters]
     
}

for category in ProductCategory.allValues{
}

虽然看上去还可以,但是设想一下,如果你有十几个case的话,那么不仅很烦,而且还需要保证allValues array里面都是正确的。通过指针你就可以很轻松的使用一个static方法来完成:

enum ProductCategory : String {
    case Washers = "washers", Dryers = "dryers", Toasters = "toasters"
    
    static func iterateEnum() -> AnyIterator<ProductCategory> {
        var i = 0
        return AnyIterator {
            let next = withUnsafeBytes(of: &i) { $0.load(as: self) }
            if next.hashValue != i { return nil }
            i += 1
            return next
        }
    }
}

for category in ProductCategory.allValues{
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容