阅读更多

0顶
0踩

编程语言

转载新闻 Swift中的指针操作及使用

2015-01-21 11:06 by 副主编 mengyidan1988 评论(0) 有4514人浏览
Apple期望在Swift中指针能够尽量减少登场几率,因此在Swift中指针被映射为了一个泛型类型,并且还比较抽象。这在一定程度上造成了在Swift中指针使用的困难,特别是对那些并不熟悉指针,也没有多少指针操作经验的开发者(包括我自己也是)来说,在Swift中使用指针确实是一个挑战。在这篇文章里,我希望能从最基本的使用开始,总结一下在Swift中使用指针的一些常见方式和场景。这篇文章假定你至少知道指针是什么,如果对指针本身的概念不太清楚的话,可以先看看这篇五分钟C指针教程(或者它的中文版本),应该会很有帮助。

初步

在Swift中,指针都使用一个特殊的类型来表示,那就是UnsafePointer<T>。遵循了Cocoa的一贯不可变原则,UnsafePointer<T> 也是不可变的。当然对应地,它还有一个可变变体,UnsafeMutablePointer<T>。绝大部分时间里,C中的指针都会被以这两种类型引入到Swift中:C中const修饰的指针对应UnsafePointer(最常见的应该就是C字符串的const char *了),而其他可变的指针则对应UnsafeMutablePointer。除此之外,Swift中存在表示一组连续数据指针的UnsafeBufferPointer<T>,表示非完整结构的不透明指针COpaquePointer等等。另外你可能已经注意到了,能够确定指向内容的指针类型都是泛型的struct,我们可以通过这个泛型来对指针指向的类型进行约束以提供一定安全性。

对于一个UnsafePointer<T>类型,我们可以通过memory属性对其进行取值,如果这个指针是可变的UnsafeMutablePointer<T> 类型,我们还可以通过memory对它进行赋值。比如我们想要写一个利用指针直接操作内存的计数器的话,可以这么做:
func incrementor(ptr: UnsafeMutablePointer<Int>) {
    ptr.memory += 1
}
var a = 10
incrementor(&a)
a  // 11

这里和C的指针使用类似,我们通过在变量名前面加上&符号就可以将指向这个变量的指针传递到接受指针作为参数的方法中去。在上面的incrementor中我们通过直接操作memory属性改变了指针指向的内容。

与这种做法类似的是使用Swift的inout关键字。我们在将变量传入inout参数的函数时,同样也使用&符号表示地址。不过区别是在函数体内部我们不需要处理指针类型,而是可以对参数直接进行操作。
func incrementor1(inout num: Int) {
    num += 1
}
var b = 10
incrementor1(&b)
b  // 11

虽然&在参数传递时表示的意义和C中一样,是某个“变量的地址”,但是在Swift中我们没有办法直接通过这个符号获取一个UnsafePointer的实例。需要注意这一点和C有所不同:
// 无法编译
let a = 100
let b = &a

指针初始化和内存管理

在Swift中不能直接取到现有对象的地址,我们还是可以创建新的UnsafeMutablePointer对象。与Swift 中其他对象的自动内存管理不同,对于指针的管理,是需要我们手动进行内存的申请和释放的。一个 UnsafeMutablePointer的内存有三种可能状态:
  • 内存没有被分配,这意味着这是一个 null 指针,或者是之前已经释放过;
  • 内存进行了分配,但是值还没有被初始化;
  • 内存进行了分配,并且值已经被初始化。

其中只有第三种状态下的指针是可以保证正常使用的。UnsafeMutablePointer的初始化方法(init)完成的都是从其他类型转换到UnsafeMutablePointer的工作。我们如果想要新建一个指针,需要做的是使用alloc:这个类方法。该方法接受一个num: Int作为参数,将向系统申请num个数的对应泛型类型的内存。下面的代码申请了一个Int大小的内存,并返回指向这块内存的指针:
var intPtr = UnsafeMutablePointer<Int>.alloc(1)
// "UnsafeMutablePointer(0x7FD3A8E00060)"

接下来应该做的是对这个指针的内容进行初始化,我们可以使用initialize:方法来完成初始化:
intPtr.initialize(10)
// intPtr.memory 为 10

在完成初始化后,我们就可以通过memory来操作指针指向的内存值了。

在使用之后,我们最好尽快释放指针指向的内容和指针本身。与initialize:配对使用的destroy用来销毁指针指向的对象,而与alloc:对应的dealloc:用来释放之前申请的内存。它们都应该被配对使用:
intPtr.destroy()
intPtr.dealloc(1)
intPtr = nil

引用
注意:其实在这里对于Int这样的在C中映射为int的“平凡值”来说,destroy并不是必要的,因为这些值被分配在常量段上。但是对于像类的对象或者结构体实例来说,如果不保证初始化和摧毁配对的话,是会出现内存泄露的。所以没有特殊考虑的话,不论内存中到底是什么,保证initialize:和destroy配对会是一个好习惯。

指向数组的指针

在Swift中将一个数组作为参数传递到C API时,Swift已经帮助我们完成了转换,这在Apple的官方博客中有个很好的例子:
import Accelerate
let a: [Float] = [1, 2, 3, 4]
let b: [Float] = [0.5, 0.25, 0.125, 0.0625]
var result: [Float] = [0, 0, 0, 0]
vDSP_vadd(a, 1, b, 1, &result, 1, 4)
// result now contains [1.5, 2.25, 3.125, 4.0625]

对于一般的接受const数组的C API,其要求的类型为UnsafePointer,而非const的数组则对应UnsafeMutablePointer。使用时,对于const的参数,我们直接将Swift数组传入(上例中的a和b);而对于可变的数组,在前面加上&后传入即可(上例中的result)。

对于传参,Swift进行了简化,使用起来非常方便。但是如果我们想要使用指针来像之前用memory的方式直接操作数组的话,就需要借助一个特殊的类型:UnsafeMutableBufferPointer。Buffer Pointer是一段连续的内存的指针,通常用来表达像是数组或者字典这样的集合类型。
var array = [1, 2, 3, 4, 5]
var arrayPtr = UnsafeMutableBufferPointer<Int>(start: &array, count: array.count)
// baseAddress 是第一个元素的指针
var basePtr = arrayPtr.baseAddress as UnsafeMutablePointer<Int>
basePtr.memory // 1
basePtr.memory = 10
basePtr.memory // 10
//下一个元素
var nextPtr = basePtr.successor()
nextPtr.memory // 2

指针操作和转换
  • withUnsafePointer

上面我们说过,在Swift中不能像C里那样使用&符号直接获取地址来进行操作。如果我们想对某个变量进行指针操作,我们可以借助withUnsafePointer这个辅助方法。这个方法接受两个参数,第一个是 inout的任意类型,第二个是一个闭包。Swift会将第一个输入转换为指针,然后将这个转换后的Unsafe的指针作为参数,去调用闭包。使用起来大概是这个样子:
var test = 10
test = withUnsafeMutablePointer(&test, { (ptr: UnsafeMutablePointer<Int>) -> Int in
    ptr.memory += 1
    return ptr.memory
})
test // 11

这里其实我们做了和文章一开始的incrementor相同的事情,区别在于不需要通过方法的调用来将值转换为指针。这么做的好处对于那些只会执行一次的指针操作来说是显而易见的,可以将“我们就是想对这个指针做点事儿”这个意图表达得更加清晰明确。
  • unsafeBitCast

unsafeBitCast是非常危险的操作,它会将一个指针指向的内存强制按位转换为目标的类型。因为这种转换是在Swift的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,你必须明确地知道你在做什么。比如:
let arr = NSArray(object: "meow")
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), CFString.self)
str // “meow”

因为NSArray是可以存放任意NSObject对象的,当我们在使用CFArrayGetValueAtIndex从中取值的时候,得到的结果将是一个UnsafePointer<Void>。由于我们很明白其中存放的是String对象,因此可以直接将其强制转换为CFString。

关于unsafeBitCast一种更常见的使用场景是不同类型的指针之间进行转换。因为指针本身所占用的的大小是一定的,所以指针的类型进行转换是不会出什么致命问题的。这在与一些C API协作时会很常见。比如有很多C API要求的输入是void *,对应到Swift中为UnsafePointer<Void>。我们可以通过下面这样的方式将任意指针转换为UnsafePointer。
var count = 100
var voidPtr = withUnsafePointer(&count, { (a: UnsafePointer<Int>) -> UnsafePointer<Void> in
    return unsafeBitCast(a, UnsafePointer<Void>.self)
})
// voidPtr 是 UnsafePointer<Void>。相当于 C 中的 void *
// 转换回 UnsafePointer<Int>
var intPtr = unsafeBitCast(voidPtr, UnsafePointer<Int>.self)
intPtr.memory //100

总结

Swift从设计上来说就是以安全作为重要原则的,虽然可能有些啰嗦,但是还是要重申在Swift中直接使用和操作指针应该作为最后的手段,它们始终是无法确保安全的。从传统的C代码和与之无缝配合的Objective-C代码迁移到Swift并不是一件小工程,我们的代码库肯定会时不时出现一些和C协作的地方。我们当然可以选择使用Swift重写部分陈旧代码,但是对于像是安全或者性能至关重要的部分,我们可能除了继续使用C API以外别无选择。如果我们想要继续使用那些API的话,了解一些基本的Swift指针操作和使用的知识会很有帮助。

对于新的代码,尽量避免使用Unsafe开头的类型,意味着可以避免很多不必要的麻烦。Swift给开发者带来的最大好处是可以让我们用更加先进的编程思想,进行更快和更专注的开发。只有在尊重这种思想的前提下,我们才能更好地享受这门新语言带来的种种优势。显然,这种思想是不包括到处使用 UnsafePointer的。
作者:王巍(@onevcat),iOS和Unity3D开发者。
来自: OneV's Den
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • Swift中的指针操作和使用详细介绍

    主要介绍了Swift中的指针操作和使用详细介绍,Apple期望在Swift中指针能够尽量减少登场几率,因此在Swift中指针被映射为了一个泛型类型,并且还比较抽象,本文详细讲解了Swift中指针的相关知识,需要的朋友可以参考下

  • Swift中的指针操作详解

    从传统的C代码和与之无缝配合的Objective-C代码迁移到Swift并非小工程,我们的代码库肯定会时不时出现一些和C协作的地方,如果想要继续使用那些C API的话,了解一些基本的Swift指针操作和使用的知识会很有帮助。...

  • Swift 中如何使用指针?

    Swift 中指针被映射为了一个泛型类型,苹果希望我们在Swift开发中尽量减少指针的使用。指针类型都是泛型的 struct,开发者可以通过这个泛型来对指针指向的类型进行约束以提供一定安全性。 简介 UnsafePointer: 不...

  • Swift —— 指针

    Swift —— 指针1.指针2. 指针类型3. 原始指针的使用4. 泛型指针的使用5. 指针读取macho中的属性名称6. 内存绑定7. 内存管理 1.指针 为什么说指针是不安全的呢?主要以下几点: 比如我们在创建一个对象的时候,是...

  • c语言中指针怎么输入,在Swift中怎么使用C语言的指针

    在Swift中怎么使用C语言的指针Objective-C和C语言经常需要使用到指针。Swift中的数据类型由于良好的设计,使其可以和基于指针的C语言API无缝混用。同时Swift也可以自动处理大多数将指针作为参数的情况。在这篇文章里...

  • swift指针&内存管理-指针类型使用

    为什么说指针不安全原因:堆内存生命周期,内存空间访问越界,指针类型与内存类型不一致; Swift中的指针分为两类 typed pointer & raw pointer 原始指针-rawPointer 的使用 泛型指针的使用

  • Swift 指针 & 内存管理

    Swift 指针 & 内存管理

  • swift 生命周期_Swift 中的指针

    题图来自互联网指针是 C / C++ 中一个很重要的概念,是这些相对低级的语言的灵魂,然而 Swift 似乎天生对指针十分不友好,繁琐的用法让很多初学者一上来十分摸不着头脑。本文就简单谈谈 Swift 中指针的一些用法。为...

  • Swift 中的指针使用

    这在一定程度上造成了在 Swift 中指针使用的困难,特别是对那些并不熟悉指针,也没有多少指针操作经验的开发者 (包括我自己也是) 来说,在 Swift 中使用指针确实是一个挑战。在这篇文章里,我希望能从最基本的使用...

  • Swift指针的应用

    Swift与指针 由于Swift本身是一门较为现代的语言,支持很多高级特性,所以...所以,Swift通过在施加某种限制的前提下为开发者暴露了指针的使用接口,本篇文章重点介绍Swift使用指针的相关类型、函数,以及在实践应用

  • swift指针的操作

    这个人贼懒,但是还想方便以后查看,请点这个链接 https://www.jianshu.com/p/e90393ba2aea

  • [Swift]Swift 中的指针使用

    Apple 期望在 Swift 中指针能够尽量减少登场几率,因此...这在一定程度上造成了在 Swift 中指针使用的困难,特别是对那些并不熟悉指针,也没有多少指针操作经验的开发者 (包括我自己也是) 来说,在 Swift 中使用指

  • Swift 2中的指针与Objective-C指针相互传递

    在桥接文件中通常使用的OC代码,在OC中就可以直接操作调用C的函数。1. 在Swift中读C指针下面桥接文件中的方法会返回一个int指针,即C术语里面的(int *):@interface PointerBridge : NSObject { int count; } - ...

  • Swift 中的指针操作

    http://www.swiftyper.com/2017/01/15/unsafe-swift/ ...但是,Swift 也提供了我们使用指针直接操作内存的方法,直接操作内存是很危险的行为,很容易就出现错误,因此官方将直接操作内存称为 “unsafe 特性”。 一旦我

  • c语言指针换字母,Swift中C语言指针的访问和转换方法

    Swift 本身从设计上来说是一门非常安全的语言,在 Swift 的思想中,所有的引用或者变量的类型都是确定并且正确对应它们的实际类型的,你应当无法进行任意的类型转换,也不能直接通过指针做出一些出格的事情。...

  • tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl

    tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl

  • tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl

    tornado-6.1-cp36-cp36m-manylinux2014_aarch64.whl

  • 基于java的ssm停车位短租系统程序答辩PPT.pptx

    基于java的ssm停车位短租系统程序答辩PPT.pptx

  • tornado-6.4b1-cp38-abi3-musllinux_1_1_x86_64.whl

    tornado-6.4b1-cp38-abi3-musllinux_1_1_x86_64.whl

Global site tag (gtag.js) - Google Analytics