Apple希望Swift中的指针能够尽量减少出现的机会,所以在Swift中,指针被映射为一个泛型,相对抽象。这在一定程度上造成了在Swift中使用指针的困难,尤其是对于不熟悉指针、没有指针操作经验的开发者(包括我自己)来说,在Swift中使用指针确实是一个挑战。挑战。在这篇文章中,我希望从最基本的使用开始,总结一下Swift中指针的一些常见使用方式和场景。本文假定您至少知道什么是指针。如果你不清楚指针本身的概念,可以看看这篇五分钟的C指针教程(或其中文版),应该会有很大帮助。最初在Swift中,指针由一种特殊类型UnsafePointer表示。遵循Cocoa一贯的不可变原则,UnsafePointer也是不可变的。当然,相应的,它也有一个可变的变体,UnsafeMutablePointer。很多时候C中的指针会以这两种类型引入到Swift中:C中const修饰的指针对应UnsafePointer(最常见的应该是C字符串的constchar*),其他变量指针对应UnsafeMutablePointer.此外,Swift中还有表示一组连续数据指针的UnsafeBufferPointer,表示不完整结构的COpaquePointer等。另外,你可能已经注意到,可以确定指向内容的指针的类型是一个泛型结构体。我们可以使用这个泛型来约束指针所指向的类型,以提供一定程度的安全性。对于UnsafePointer类型,我们可以通过内存属性对其进行赋值,如果指针是可变的UnsafeMutablePointer类型,我们也可以通过内存对其进行赋值。比如我们要写一个计数器,使用指针直接操作内存,我们可以这样写:funcincrementor(ptr:UnsafeMutablePointer){ptr.memory+=1}vara=10incrementor(&a)a//11这类似于C中指针的使用,我们可以通过在变量名前加上&符号将指向变量的指针传递给接受指针作为参数的方法。在上面的增量器中,我们通过直接操作内存属性来改变指针指向的内容。与此类似的方法是使用Swift的inout关键字。当我们将变量传递给具有inout参数的函数时,我们也使用&符号来表示地址。但不同的是,我们不需要在函数体内处理指针类型,而是可以直接对参数进行操作。funcincrementor1(inoutnum:Int){num+=1}varb=10incrementor1(&b)b//11虽然参数中传递的是&表示的意思和C中一样,都是“变量地址”,但是在Swift,我们没有办法通过这个符号直接获取UnsafePointer的实例。需要注意的是这里不同于C://不能编译leta=100letb=&aPointer初始化和内存管理在Swift中,我们不能直接获取一个已经存在的对象的地址,我们仍然可以创建一个新的UnsafeMutablePointer对象.与Swift中其他对象的自动内存管理不同,指针的管理需要我们手动申请和释放内存。一个UnsafeMutablePointer的内存有三种可能的状态:内存还没有被分配,这意味着它是一个空指针,或者内存之前已经被释放用于分配,但是值还没有被初始化内存被分配并且值已经被初始化其中,只有处于第三种状态的指针才能保证正常使用。UnsafeMutablePointer的初始化方法(init)完成了从其他类型转换为UnsafeMutablePointer的所有工作。如果我们想创建一个新的指针,我们需要做的就是使用alloc:类方法。该方法接受一个num:Int作为参数,会向系统申请num个泛型对应的内存。下面的代码申请一块Int大小的内存,并返回指向这块内存的指针:varintPtr=UnsafeMutablePointer.alloc(1)//"UnsafeMutablePointer(0x7FD3A8E00060)"接下来要做的就是检查这个指针的内容对于初始化,我们可以使用initialize:方法完成初始化:intPtr.initialize(10)//intPtr.memory为10。初始化完成后,我们可以使用memory来操作指针指向的内存值。使用后,我们绝对尽快释放指针指向的内容和指针本身。与initialize:配对的destroy用于销毁指针指向的对象,alloc:对应的dealloc:用于释放之前分配的内存。它们应该都是成对使用的:intPtr.destroy()intPtr.dealloc(1)intPtr=nil注意destroy对于像Int这样在C中映射到int的“琐碎值”是不需要的,因为这些值是分配的在常数段上。但是对于类或者结构实例这样的对象,如果不能保证初始化和销毁??的配对,就会发生内存泄漏。因此,无需特别考虑,确保initialize:和destroy配对是一个好习惯,无论内存中实际有什么。指向数组的指针Swift已经在将数组作为参数传递给Swift中的CAPI时为我们完成了转换,Apple官方博客上有一个很好的例子:importAccelerateleta:[Float]=[1,2,3,4]letb:[Float]=[0.5,0.25,0.125,0.0625]varresult:[Float]=[0,0,0,0]vDSP_vadd(a,1,b,1,&result,1,4)//resultnowcontains[1.5,2.25,3.125,4.0625]对于接受const数组的通用CAPI,要求的类型是UnsafePointer,非const数组对应UnsafeMutablePointer。使用时,对于const参数,我们直接传入Swift数组(上例中的a和b);而对于变量数组,只需在前面加上&,然后传入即可(如上例的结果)。对于参数传递,Swift进行了简化,使用起来非常方便。但是如果我们想像之前使用内存一样使用指针直接操作数组,就需要使用一个特殊的类型:UnsafeMutableBufferPointer。BufferPointer是指向一块连续内存的指针,通常用来表示数组或字典等集合类型。vararray=[1,2,3,4,5]vararrayPtr=UnsafeMutableBufferPointer(start:&array,count:array.count)//baseAddress为第一个元素的指针varbasePtr=arrayPtr.baseAddressasUnsafeMutablePointerbasePtr.memory//1basePtr.memory=10basePtr.memory//10//下一个元素varnextPtr=basePtr.successor()nextPtr.memory//2指针操作和与UnsafePointer的转换上面我们说过,在Swift中,不能像在C中那样使用&符号直接获取地址进行操作。如果我们想对一个变量进行指针操作,可以使用辅助方法withUnsafePointer。这个方法接受两个参数,第一个是任何类型的inout,第二个是一个闭包。Swift会将第一个输入转换为指针,然后将转换后的Unsafe指针作为参数调用闭包。在使用中看起来是这样的:vartest=10test=withUnsafeMutablePointer(&test,{(ptr:UnsafeMutablePointer)->Intinptr.memory+=1returnptr.memory})test//11这里我们实际上是在article相同的事情,只是不需要方法调用来将值转换为指针。这样做的好处对于那些只会执行一次的指针操作来说是显而易见的,可以更清楚地表达“我们只是想用这个指针做点什么”的意图。unsafeBitCastunsafeBitCast是一个非常危险的操作,它会强制对目标类型的指针指向的内存进行按位转换。因为这种转换是在Swift的类型管理之外完成的,所以编译器无法确定生成的类型是否真的正确,你必须确切地知道你在做什么。例如:letarr=NSArray(object:"meow")letstr=unsafeBitCast(CFArrayGetValueAtIndex(arr,0),CFString.self)str//"meow"因为NSArray可以存储任何NSObject对象,当我们使用CFArrayGetValueAtIndex获取它的值时,结果将是一个UnsafePointer。因为我们知道它存储的是一个String对象,所以我们可以直接将它转换为CFString。unsafeBitCast的一个更常见的用例是在不同类型的指针之间进行转换。因为指针本身占用的大小是一定的,所以在转换指针的类型时不会出现致命的问题。这在使用某些CAPI时很常见。比如很多CAPI要求的输入是void*,对应Swift中的UnsafePointer。我们可以通过以下方式将任何指针转换为UnsafePointer。varcount=100varvoidPtr=withUnsafePointer(&count,{(a:UnsafePointer)->UnsafePointerinreturnunsafeBitCast(a,UnsafePointer.self)})//voidPtr为UnsafePointer。相当于C中的void*//转换回UnsafePointervarintPtr=unsafeBitCast(voidPtr,UnsafePointer.self)intPtr.memory//100总结Swift在设计时以安全为重要原则,虽然可能是有点啰嗦,但我还是要重申,在Swift中直接使用和操作指针应该是最好的手段,它们总是无法保证安全。从传统的C代码和与其无缝协作的Objective-C代码迁移到Swift不是一个小项目,我们的代码库肯定会有一些地方不时与C一起工作。我们当然可以选择用Swift重写一些旧代码,但对于安全或性能关键部分,我们可能别无选择,只能继续使用CAPI。如果我们想继续使用那些API,了解一些基本的Swift指针操作和用法将很有帮助。对于新代码,尽量避免使用以Unsafe开头的类型,这样可以避免很多不必要的麻烦。Swift给开发者带来的最大优势在于,它可以让我们使用更先进的编程思想来进行更快速、更专注的开发。只有在尊重这种思维的前提下,我们才能更好地享受这种新语言带来的优势。显然,这种想法不包括到处使用UnsafePointer:)