Swift 3.0 中不安全的世界 Swift 3.0 Unsafe World

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看205 回复回复3 收藏收藏1 分享淘帖 转播转播 分享分享 微信
查看: 205|回复: 3
收起左侧

Swift 3.0 中不安全的世界 Swift 3.0 Unsafe World

[复制链接]
swifter 发表于 2016-11-1 08:19:38 | 显示全部楼层 |阅读模式
快来登录
获取最新的苹果动态资讯
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
就像大多数现在的变成语言一样,在 Swfit 中你就像生活在一个幸福的世界中,这里的内存被额外的部分所管理,而像这样的内存管理语言的编译和运行要么就像 Swift 一样,要么他运行的好坏取决于他的垃圾回收机制。而这些我们所提到的这些隐藏在编程语言中的,你不必要去或者很少的情况下你需要去思考这些问题。

Like most of the modern languages, in Swift you will be living in a happy world where all the memory is managed by an external element, it could be the compiler/runtime like swift or it could be worse and depend on a garbage collector. References to instances that fly around are hidden in the language and rarely you need to deal with these issues.


然而由于 Swift 的多样性的特点,你可能需要调用一个危险的 C 的 Api 比如说 OpenGL 或者 POSIX 中的函数,在这些情况下你可能需要处理一些让我们头疼的情况。没错,我说的就是指针和手动在堆中申请内存空间。

However, given Swift versatility you could need to call a dirty C Api like OpenGL or POSIX functions, and in those cases you will need to deal with that thing that has caused lots of headaches to many of us, yes, I'm talking about pointers and allocating memory manually in the heap.


在 Swift 3.0 以前,Swift 的不安全的 API 有点混乱,你可以通过好几个方法来达到相同的结果,但是那只是你从 stackoverflow 上复制、粘贴来的,但是你没有彻底的理解真正发生了什么。在 Swift 3.0 中所有事物都发生了改变,而且他变得更容易理解。

Before 3.0, Swift unsafe API was a little bit confusing, there were several paths to achieve the same result, and that caused that you usually ended by copying and pasting what you saw in stackoverflow without understanding what is really happening. In 3.0 everything has changed, and it has done it for the better.

Swift 3.0 中不安全的世界 Swift 3.0 Unsafe World

Swift 3.0 中不安全的世界 Swift 3.0 Unsafe World - 敏捷大拇指 - Swift 3.0 中不安全的世界 Swift 3.0 Unsafe World


在这篇文章中,我不会告诉你如何将代码从 Swift 2.x 迁移到 Swift 3.0。反而我将会告诉你这些事情在 Swift 3.0 中如何工作,因为通常造成不安全的引用的主要原因是与 C 的底层 API 的交互。

In this post I won't write how to migrate from Swift 2.x to 3.0 code, instead I will describe how the things works in 3.0 and will be comparing occasionally with C, since usually the main purpose of using unsafe references is to interact with low level C APIs.


让我们从最简单的操作开始——开辟内存空间来存储一个整型变量。

Let's start by doing the simplest operation, allocating memory to hold an Integer.


在 C 中,你将会写下下面这样的代码:

In C, you will do something like this:

[C] 纯文本查看 复制代码
int *a = malloc(sizeof(int));
*a = 42;

printf("a's value: %d",*a);

free(a)


而这在 Swift 在这么实现的:

The same is achieved in Swift with:

[Swift] 纯文本查看 复制代码
let a = UnsafeMutablePointer<Int>.allocate(capacity: 1)
a.pointee = 42

print("a's value: \(a.pointee)") //42

a.deallocate(capacity: 1)


第一类我们所看到的是 Swift 中的 UnsafeMutablePointer<T> ,这普通的结构体相当于一个 T 的指针,正如你所示的,他有一个静态函数, allocate 将会开辟需要的内存空间。

The first type we see in Swift is UnsafeMutablePointer<T>, this generic struct represents a pointer to the type T, and as you can see, it has a static method, allocate that will reserve capacity number of elements.


正如你所想的,这个 UnsafeMutablePointer 还有一个变形—— UnsafePointer ,这个类型不允许你修改指针的值,此外不可修改的 UnsafePointer 甚至没有 allocate 方法。

As you can imagine, there is a variant of UnsafeMutablePointer called UnsafePointer that won't allow you to change the pointee element. Moreover, non-mutable UnsafePointer does not even have a allocate method.


在 Swift 中,你还有另外一个方法来创建一个 Unsafe[Mutable]Pointer 方法,那就是使用 & 操作。当传一个 block 或者函数,你可以使用一个 & 来传入一个指针。让我们来看下面这个例子:

In Swift, there is another way to generate a Unsafe[Mutable]Pointer, it is by using the & operator. When passing parameters to a block or function, you can use & to take a pointer to it. Let's see an example

[Swift] 纯文本查看 复制代码
func receive(pointer:UnsafePointer<Int>){
  print("param value is:\(pointer.pointee)")    //42
}

var a: Int = 42
receive(pointer: &a)


& 操作需要一个 var 变量,但是这个将会提供给你你所需要解决的各种情况。比如说,你可以使用可修改的引用(mutable reference),甚至修改它,比如说:

& operand requires type to be var, but it will give you what you need in each situation. For example, you could take a mutable reference and even change it, like this:

[Swift] 纯文本查看 复制代码
func receive(mutablePointer:UnsafeMutablePointer<Int>){
  mutablePointer.pointee *= 2
}

var a = 42
receive(mutablePointer: &a)
print("A's value has changed in the function: \(a)") //84


这个例子和前面那个例子有重要的区别。在前面的例子中,我们需要手动开辟内存空间(我们需要在创建好后手动分配内存空间),同时在这个简单的例子中的函数中,我们快速的创建了一个指向内存的指针。明显的,管理内存并且使用指针指向他是2个不同的话题,在接下来的例子中,我们将会聊一聊如何管理内存空间。

There is an important difference between the first sample, and these last. In the first one we manually allocated the memory, (and we needed to manually allocate it afterwards), and in the samples with a function, we are creating a pointer from a Swift allocated memory. Obviously, managing the memory and taking pointers are two different topics. In the last part of the post we will talk about memory management.


但是我们如何在Swift中如何在不创建一个函数的情况下,调用指针。为了达到这种目的,我们需要使用 withUnsafeMutablePointer ,他将会调用一个 Swift 的引用类型和一个有参数的 block ,让我们来看看下面这个例子。

But, what if we want to take a pointer to a Swift managed memory without having to create a function?. To do it we will use withUnsafeMutablePointer, that will take a reference to a Swift type and a block with the pointer as it's parameter. Let's see it in action

[Swift] 纯文本查看 复制代码
var a = 42
withUnsafeMutablePointer(to: &a){ $0.pointee *= 2}

print("a's value is: \(a)") //84


现在我们知道了这个方法,现在我们调用 C 中那些有指针的 API ,让我们看来看下下面这个 POSIX 的打开读取路径并获取其中内容的当前地址的方法。

Now that we know the tools, we are ready to call C APIs with pointers in its paremters, let's see an example of POSIX function opendir / readdir to list the contents of the current directory.

[Swift] 纯文本查看 复制代码
var dirEnt: UnsafeMutablePointer<dirent>?
var dp:UnsafeMutablePointer<Dir>?
let data = ".".data(using:ascii)
data?.withUnsafeBytes({(ptr:UnsafePointer<Int8>) in
    dp = opendir(ptr)
})
repeat{
  dirEnt = readdir(dp)
  if let dir = dirEnt{
    withUnsafePointer(to:&dir.pointee.d_name,{ ptr in
      let ptrStr = unsafeBitCase(ptr,to:UnsafePointer<CChar>.self)
      let name = String(cString:ptrStr)
      print("\(name)")
    })
  }
} while dirEnt != nil





1、指针转换
Cast between pointers


当处理 C 的 API 的时候,你有时候需要将指向结构体的指针转换为不同的结构体。对于 C 的 API 的处理很简单(同时也是十分危险并且容易出现报错)的,就像你在 Swift 中所看到的,所有指针的类型是被固定的,这意味着一个 UnsafePointer<Int> 的指针不能再用在需要 UnsafePointer<UInt8> 的地方,这使得能够更好的编写出更加安全的代码,但是同样意味着你不能在你需要的时候随意转换指针类型。比如说 socket 中的 bind() 方法比如说这些情况下,我们将会使用 withMemoryRebound 这个我们用来将一个指针类型转换为另一个指针类型的方法。让我们来看看我们是如何使用角色转换,在 bind 函数中当你创建一个 sockaddr_in 结构体转换为 sockaddr

When dealing with C API you need sometimes to cast pointers to struct to a different struct. This is very easy to do en C (and very dangerous and error prone too), as you have seen in Swift, all pointers are typed, that means that an UnsafePointer<Int> cannot be used where an UnsafePointer<UInt8> is required, that's good in terms of producing a safer code, but at the same time that makes not possible to interact with C APIs that requires this types of casts, like for example socket bind() function. For theses cases, we will use withMemoryRebound which is a function that will convert a pointer from a type to a different one, let's see how we can use the cast in the bind function where you typically create a sockaddr_in struct and then cast to sockaddr

[Swift] 纯文本查看 复制代码
var addrIn = sockaddr_in()
// Fill sockaddr_in fields 
withUnsafePointer(to: &addrIn) { ptr in
    ptr.withMemoryRebound(to: sockaddr.self, capacity: 1, { ptrSockAddr in
        bind(socketFd, UnsafePointer(ptrSockAddr), socklen_t(MemoryLayout<sockaddr>.size))
    })
}


这个一个用来转变指针类型的特别的方法,一些 C 的 API 需要传一个 void* 指针。在 Swift 3.0 以前,你可能需要使用 UnsafePointer<Void> 。然而在3.0中有一个新的类型来处理这些指针: UnsafeRawPointer 。这个结构体和不同的结构体不同,所以这意味着他不会将其中的信息绑定到任何指定的类型中,这另我们的编码过程变得很简单。为了创建一个 UnsafeRawPointer 指针,我们只需要调用它的创建函数来包裹我们所需要的那个指针。如果我们想要用另外的方法,来将这个 UsafeRawPointer 的指针转化为其他类型的指针的时候,我们需要使用 withMemoryRebound 的上一个版本的方法,在这里他叫做 assumingMemoryBound 。

There is a special case when casting pointers, some C APIs requires to pass a void* pointer. Before Swift 3.0, you could do it with UnsafePointer<Void> however, in 3.0 a new type has been added to handle these types of pointers: UnsafeRawPointer. This struct is not generic, so it means that it won't hold information tied to any specific type and that will simplifly our code. To create a UnsafeRawPointer we can just wrap an existing pointer with it in its constructor. If we want to go the other way around, converting an UnsafeRawPointer to a specific typed pointer we would need to use a version of withMemoryRebound of the previous sample but in this case it is called assumingMemoryBound.

[Swift] 纯文本查看 复制代码
let intPtr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
let voidPtr = UnsafeRawPointer(intPtr)
let intPtrAgain = voidPtr.assumingMemoryBound(to: Int.self)





2、数组指针
Pointers as arrays


到这里,我们我们已经学会了一些指针的基本使用方法,同时你可以处理大多数的 C 的 API 调用。然而指针使用的地方还有很多,比如说遍历内存块,这对于程序员来说是我们可以获得很多重要信息。在 Swift 中我们有一些方法来做这些事情,比如说 UnsafePointer 有提供了一个方法 advanced(by: ) 来遍历内存,这个方法返回了另一个 UnsafePointer ,这样我们就可以读写那个内存区域里面的内容。

Up until here we have covered the typical usage of pointers, and you can deal with most of C API calls, however pointers are used for more usages, one of them is to iterate through a chunk of memory that we know it holds valuable data. In Swift we have several ways to do the same. Actually, UnsafePointer has a advanced(by: ) method that allows you to iterate through the memory, advanced(by: ) returns another UnsafePointer so we can store or read what's there.

[Swift] 纯文本查看 复制代码
let size = 10
var a = UnsafeMutablePointer<Int>.allocate(capacity: size)
for idx in 0..<10 {
    a.advanced(by: idx).pointee = idx
}
a.deallocate(capacity: size)


另外, Swift 还有一个 UnsafeBufferPointer 的结构体来更方便的实现这个需求。这个结构体是一个Swift数组和指针的桥梁。如果我们使用 UnsafePointer 来作为变量从而调用创建函数创建一个 UnsafeBufferPointer ,我们将能够使用大多数的Swift原生的数组(Array)方法,因为 UnsafeBufferPointer 遵守并实现了 Collections , Indexable 和 RandomAccessCollection 协议。所以我们可以像这样遍历内存:

In addition to this, Swift has another struct that makes this usage easier, we are talking about UnsafeBufferPointer. This Struct is the bridge between Swift arrays and pointers. If we construct a UnsafeBufferPointer from an UnsafePointer we will be able to use most of the array functions of native Swift type given that UnsafeBufferPointer implements Collection, Indexable and RandomAccessCollection swift protocols. Said that we can iterate throught memory like this:

[Swift] 纯文本查看 复制代码
// Using a and size from previous code 
var b = UnsafeBufferPointer(start: a, count: size)
b.forEach({
    print("\($0)" // Prints 0 to 9 that we fill previously 
)})


当我们提到 UnsafeBufferPointer 的是一个Swift中数组的桥梁的时候,这也意味着我们很容易使用 UnsafeBufferPointer 来调用一个已经存在的数组,比如说下面这个例子:

When we said that UnsafeBufferPointer is the bridge with Swift arrays, it is also because it is easy to take a UnsafeBufferPointer from an existing array like this sample:

[Swift] 纯文本查看 复制代码
var a = [1, 2, 3, 4, 5, 6]
a.withUnsafeBufferPointer({ ptr in
    ptr.forEach({ print("\($0)") }) // 1, 2, 3... 
})





3、内存管理带来的危害
Memory management dangers


我们已经看到了很多方法来引用原始内存,但是我们不能忘记我们正在进入一个危险区域。可能重复 Unsafe 单词可能会提醒我们要小心的使用它们。然而我们我们是使用 unsafe 引用来混合两个世界(不需要内存管理和手动内存管理)。让我们来看看他在我们灵活使用中所带来的危害。

We have seen quite a lot ways to reference to raw memory, but we cannot forget that we are entering in a dangerous terrain. Probably the repating word Unsafe warns us to be careful when using it. Moreover, we are mixing two worlds when using unsafe references. Let's see the dangers in all its glory with an example:

[Swift] 纯文本查看 复制代码
var collectionPtr: UnsafeMutableBufferPointer<Int>?
func duplicateElements(inArray: UnsafeMutableBufferPointer<Int>) {
    for i in 0..<inArray.count {
        inArray[ i ] *= 2
    }
}

repeat {
    var collection = [1, 2, 3]
    collection.withUnsafeMutableBufferPointer({ collectionPtr = $0 })
} while false

duplicateElements(inArray: collectionPtr!) // Crash due to EXC_BAD_ACCESS 


虽然这个简单的例子我们不会真正的碰到,但是实际在快速创建变量的过程中我们会碰到和他类似但是比他更加复杂的代码。在这里, collection 在一个 block 中被创建,同时在 block 结束后引用被释放。我们有意的在调用 collection 后将引用保存在了 collectoinPtr 中,然后在原始的 collection 不在存在后继续调用,所以程序在调用 duplicateElements(inArray: ) 后崩溃了,如果我们想要使用指针来快速创建变量,我们需要确定这些变量能够在我们需要使用它们的时候可用。注意ARC将在每个变量离开他的作用于的时候为每个变量添加 release 方法,如果这个变量没有被强引用的话,他就会被释放。

Although this sample is not real, it illustrates what happens in more complex code when using pointers to swift allocated variables. Here, collection is created in a block, and therefore, the reference will be freed after the block ends. We, intentionally, save a reference in collectionPtr that we use after the original collection is not valid anymore so it crashes when trying to use it in duplicateElements(inArray: ). If we want to take pointers to swift allocated elements we need to be sure that they are going to be available when we want to use it. Keep in mind that ARC will add a release to any reference that is leaving the scope, and if it is not strong referenced in any other place, it will be freed.


一个解决方法是不适用 Swift 的内存管理方法而是我们自己手动开辟内存空间,就像我们文章中所提到的那些简单的代码一样,这就解决了访问无效引用的问题,但是这引入了另一个问题。如果我们没有手动释放内存,那么就会存在内存泄漏问题。

A solution to overcome Swift's memory management is to allocate the memory by ourselves, like we've done in some samples of this post, this eliminates the problem of accessing to not valid references, but it introduces another problem. If we don't deallocate manually what we have allocated our program will have memory leaks.




4、使用 bitPattern 来修改指针的值
bitPattern for pointers with fixed values


为了更好地完成这篇文章,在这我将介绍一些 Swift 中指针的用法。 第一个就是在使用C的API的时候使用 void* 方法而不是使用内存地址。通常这会发生在一个函数接受不同类型的参数,并简单的将参数打包成 void* 类型,就像下面这个例子一样:

To finish the post I would like to introduce a couple of more usages of pointers in Swift. One of them is something that it is useful in C APIs that require a void* pointer with a value instead with a memory address. Usually this happens when a function accept different types of parameters and to be generic wrap the value in a void*. It would be something like this:

[Swift] 纯文本查看 复制代码
void generic_function(int value_type, void* value);

generic_function(VALUE_TYPE_INT, (void *)2);
struct function_data data;
generic_function(VALUE_TYPE_STRUCT, (void *)&data);


如果我们想要在 Swift 中调用第一个函数,我们需要使用特别的构造函数,这会创建一个特殊的地址的。所有这些函数将不会改变允许你改变内存地址中变量的值,所以我们将会在这种情况下使用 UnsafePointer(bitPattern: ) 。

If we want to use the first call of that funciton from swift we need to use a special constructor that will allow us to create a pointer pointing to a specific address. All the functions that we have seen doesn't allow you to change the address of the pointee, so we will be using UnsafePointer(bitPattern: ) in this case.

[Swift] 纯文本查看 复制代码
generic_function(VALUE_TYPE_INT, UnsafeRawPointer(bitPattern: 2))
var data = function_data()
withUnsafePointer(to: &data, { generic_function(VALUE_TYPE_STRUCT, UnsafeRawPointer($0)) } )





5、透明指针
Opaque Pointer


在这篇文章的最后我想说的就是如何使用 Swift 中的透明指针。在C的 API 中我们经常会调用用户数据,而用户的数据将会成为一个 void* 指针,该用户数据将是一个 void * 指针,他将保存在一个任意内存中。一个通用的使用方法是当处理函数并设置回调方法的时候,事件将会被调用。在这种情况下,传入一个引用到一个 Swift 对象中,然后我们就可以在 C 的回调函数中调用指针的方法。

The last thing I would like to comment is the post is the usage of Opaque pointers to Swift types. It is very common to have a userData like parameter in some C API functions, that user data will be a void* pointer that will hold an arbitrary memory value that will be used after. A common use case is when dealing with functions that sets some callbacks that will be invoked when an event happens. In that case it will be useful to pass a reference to a Swift object so we can call its method from the C callback.


我们能够使用 UnsafeRawPointer 就像我们曾在这篇文章中的其他例子中所看到的。然而正如我们所看到的,这些调用在内存管理中有一定的问题,当我们传入一个指针到 C 中来指向一个我们没有 retain 的变量的时候,这个对象将被释放,同时这个程序将会崩溃。

We could use a regular UnsafeRawPointer like we have seen in the rest of the post, however, as we've also seen, doing this could have problems with memory management. If we pass to a C world function a pointer to a object that we don't retain before, it can be freed and out program will crash.


Swift 有一个实用的方法来根据我们是否真的需要,从而决定指向这个对象的指针是否进行 retain 。这就是 Unmanaged 结构体的一个静态函数。使用 passRetained() 我们将会创建一个被 retained 的指向这个对象的指针,那么我们就能保证当他在 C 中被调用的时候他仍旧在那。当这个对象已经在回调函数中被 retianed 的时候我们可以使用 passUnretained() 。这两个方法将会产生 Unmanaged 的实例变量,这个实力变量将会通过调用 toOpaque() 方法转换为 UnsafeRawPointer

Swift has an utility to take pointers to objects retaining its reference or not depending on our needs. Those are static functions of Unmanaged struct. With passRetained() we will create a retained reference to an object, so we can be sure that when using it from C world, it will be still there. If the object is already retained for the life of the callback we can also use passUnretained(). Both methods produces a instance of Unmanaged that will be converted to a UnsafeRawPointer by calling toOpaque()


在另一方面我们可以将 UnsafeRawPointer 通过相反的 API fromOpaque() 和 takeRetained() 转换为一个类或者结构体。

On the other side, we can transform an UnsafeRawPointer to a class or struct instance using the inverse api fromOpaque() and takeRetained() or takeUnretained().

[Swift] 纯文本查看 复制代码
void set_callback(void (*functionPtr)(void*), void* userData));


[Swift] 纯文本查看 复制代码
struct CallbackUserData {
    func sayHello() { print("Hello world!" ) }
}

func callback(userData: UnsafeMutableRawPointer) {
    let callbackUserData = Unmanaged.fromOpaque(userData).takeRetainedValue()
    callbackUserData.sayHello() // "Hello world!" 
}

var userData = CallbackUserData()
let reference = Unmanaged.passRetained(userData).toOpaque()
set_callback(callback, reference)





6、总结
Conclusions


正如你所看到的,调用 C 的代码在 Swift 是可行的,同时知道了有这些方法使得我们不需要用大量的代码就能实现我们想要的效果。不安全和非管理的 API 在本文中被大量的提到,但是我希望这是一个很好的进行深入了解的机会,从而你可以对他感兴趣或者能够真正的使用它。

As you can see, calling C code from Swift is completely doable, and knowing the tools we have, it is easy and it doesn't need a lot of lines of code to do it. Unsafe and Unmanaged API is bigger that we covered in this post, but I hope this is a good foundation to dive deeper in case you are interested or you need it.




作者:Meron@Tech,Posted on 27 septiembre, 2016 by Roberto Perez in programming, swift

都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

买定离手 发表于 2016-11-1 13:39:43 | 显示全部楼层
练英文么~~~
电音之王 发表于 2016-11-1 15:38:34 | 显示全部楼层
收藏。
死神来了 发表于 6 天前 | 显示全部楼层
一提指针就头大
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励
热门推荐

合作伙伴

Swift小苹果