Unmanaged使用

作者 Nate Cook 翻译者 Croath Liu 2015年4月13日

       API 对于开发者来说不只是把功能点接口暴露出来而已,同时也传达给我们一些其他的信息,比如说接口如何以及为什么要使用某些值。因为要传达这些信息,给东西起适当的名字这件事才变成了计算机科学中最难的部分之一,而这也成为好的 API 和不好的 API 的重要区别。

       通过 Swift 标准库就可以看出,Swift 在安全性和可靠性方面和与 Objective-C 互通性之间方面有着明显的界线。像 IntStringArray 这些类型在使用过程中都会表现出直接且无歧义的行为,但如果什么都不考虑就创建 UnsafeMutablePointerUnmanaged 等类型的实例,那恐怕就要踩到坑里了。

       这次我们关注 Unmanaged 这个关键字。Unmanaged 表示对不清晰的内存管理对象的封装,以及用烫手山芋的方式来管理他们。但开始之前,我们先回顾一下历史。

自动引用计数(Automatic Reference Counting)

       在石器时代(我是说 2011 年),在 Objective-C 中还要手动进行引用计数。每一个 retain 操作的引用都要与一个相应的 release 操作构成一个平衡的组合,才能避免应用在场景切换的过程中产生僵尸引用和内存泄漏……好脏。对于工程师来说需要小心地计算每一个对象的引用计数实在是太累了,而且对于新入行者门槛也过高了。

       自动引用计数(ARC)的到来让和手动内存管理相关的一切都失去了必要。在 ARC 下,编译器会在每一个生命周期内按照规则帮你进行 retain/release/autorelease 指令的调用,减少了很多麻烦。

ARC 出现前后内存管理差异

       在现在这个后 ARC 的世界里,所有的 Objective-C 和 从 Objective-C 方法返回的 Core Foundation 类型的内存都被自动管理,只剩下由 C 函数返回的 Core Foundation 类型还没有收编。对于后者而言,对象所有权的管理仍然停留在调用 CFRetain()CFRelease()、或通过某个 __bridge 函数桥接到 Objective-C 对象的方式的层面上。

       为了帮助大家理解 C 函数返回对象是否被调用者持有,苹果使用了 Create 规则 和 Get 规则 命名法:

  • Create 规则 的意思是,如果一个函数的名字含有 CreateCopy ,函数的返回值被函数的调用者持有。也就是说,调用 CreateCopy 函数的对象应该对返回对象调用 CFRelease 进行释放。

  • Get 规则 则不像 Create 规则一样能从命名规则看出规律。或许可以描述成函数名不含有 CreateCopy 的函数?这种函数遵守 Get 规则,返回对象的持有者不会发生变化。如果想持久化一个返回对象,大多数时候就是你自己手动 retain 它。

如果你是一个像我一样系三条皮带都怕裤子掉下来的那种开发者,那就去好好看看文档。即使大多数 API 遵从这种命名规则,以防意外情况,用的时候都应该好好看看文档确认一下。

等等!等等!我们这篇文章是讨论 Swift 的,回到正轨!

       Swift 仅支持 ARC,所以也没有地方调用 CFRelease__bridge_retained。那么 Swift 是如何让这种 “在上下文中内存管理” 的哲学融入自己的内存安全体系呢?

       事情分两种情况。注明 的 API,Swift 能够在上下文中严格遵循注释描述对 CoreFoundation API 进行内存管理,并以同样内存安全的方式桥接到 Objective-C 或 Swift 类型上。对于没有明确注明的 API,Swift 则会通过 Unmanaged 类型把工作交给开发者。

管理 Unmanaged

       虽然大多数的 CoreFoundation API 都有注明是否可自动管理,但一些重要的部分还没有得到充分重视。这篇文章编写时,Address Book framework 的 API 似乎是比较重要的尚未注明的部分,有一些函数还要传入或返回 Unmanaged 类型的对象。

       一个 Unmanaged<T> 实例封装有一个 CoreFoundation 类型 T,它在相应范围内持有对该 T 对象的引用。从一个 Unmanaged 实例中获取一个 Swift 值的方法有两种:

  • takeRetainedValue():返回该实例中 Swift 管理的引用,并在调用的同时减少一次引用次数,所以可以按照 Create 规则来对待其返回值。

  • takeUnretainedValue():返回该实例中 Swift 管理的引用而 不减少 引用次数,所以可以按照 Get 规则来对待其返回值。

    在实践中最好不要直接操作 Unmanaged 实例,而是用这两个 take 开头的方法从返回值中拿到绑定的对象。

我们来看一个例子。比如说我们这里要创建一个 ABAddressBook 来获取用户最好的朋友的名字:

let bestFriendID = ABRecordID(...)

// Create Rule - retained
let addressBook: ABAddressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue() if let
// Get Rule - unretained
bestFriendRecord: ABRecord = ABAddressBookGetPersonWithRecordID(addressBook, bestFriendID)?.takeUnretainedValue(),
// Create Rule (Copy) - retained
name = ABRecordCopyCompositeName(bestFriendRecord)?.takeRetainedValue() as? String
{
println("\(name): BFF!")
// Rhonda Shorsheimer: BFF!
}

通过使用 Swift 1.2 新增的 optional 绑定,取得对象并将其转化为 Swift 类型简直是小菜一碟。

最好的解决问题的方法是避免遇到问题

       现在我们已经知道如何对付 Unmanaged 了,现在我们还是看看如何避免碰到这种情况吧。如果 Unmanaged 引用是从你自己写的 C 函数返回的,那么你最好还是注明一下。这种注释能够帮助编译器理解如何自动管理你所返回对象的内存:就不要用 Unmanaged<CFString> 了,直接返回一个在 Swift 中类型安全以及内存管理完善的 CFString 类型。

       举例说明,我们有一个函数能将两个 CFString 对象拼装成一个字符串,并且要告诉 Swift 这个返回字符串的内存是被如何管理的。根据上面提到的命名规则,我们的函数应该叫做 CreateJoinedString —— 这个名字表达的意思是调用者将持有返回值。

CFStringRef CreateJoinedString(CFStringRef string1, CFStringRef string2);

既然这样,在函数实现中我们用 CFStringCreateMutableCopy 创建的 resultString 返回时没有与其创建函数平衡的 CFRelease:

CFStringRef CreateJoinedString(CFStringRef string1, CFStringRef string2) {
CFMutableStringRef resultString = CFStringCreateMutableCopy(NULL, 0, string1);
CFStringAppend(resultString, string2);
return resultString;
}

在 Swift 中像上面一样,我们也要手动管理内存。我们的函数被引用成返回一个

Unmanaged<CFString>!:

// imported declaration:
func CreateJoinedString(string1: CFString!, string2: CFString!) -> Unmanaged<CFString>! // to call:
let joinedString = CreateJoinedString("First", "Second").takeRetainedValue() as String

既然我们的函数遵循了 Create 规则进行命名,那么就可以打开编译器的隐式桥接来消除 Unmanaged 歧义。Core Foundation 提供了两个宏:CF_IMPLICIT_BRIDGING_ENABLEDCF_IMPLICIT_BRIDGING_DISABLED —— 用来打开和关闭 Clang 的 arc_cf_code_audited 变量:

CF_IMPLICIT_BRIDGING_ENABLED            // get rid of Unmanaged
# pragma clang assume_nonnull begin // also get rid of !s CFStringRef CreateJoinedString(CFStringRef string1, CFStringRef string2); # pragma clang assume_nonnull end
CF_IMPLICIT_BRIDGING_DISABLED

现在 Swift 已经能够控制这个函数返回值的内存管理了,我们的代码里也可以不用 Unmanaged 了:

// imported declaration:
func CreateJoinedString(string1: CFString, string2: CFString) -> CFString // to call:
let joinedString = CreateJoinedString("First", "Second") as String

       最后一点,如果你的函数 没有使用 Create/Get 规则来命名,那么明显地,你把这些函数用这个法则重新命名一次。当然在真实情况下这种修改可能并不容易,但是拥有明确性一致性返回的 API 的好处不仅仅是避免 Unmanaged。如果不能够重命名,也有另外两种注明方式可以使用:将持有者转移到调用者的函数应该使用 CF_RETURNS_RETAINED,反之则使用 CF_RETURNS_NOT_RETAINED。比如说,这个命名糟糕的 MakeJoinedString 就是用了手动注明的方式来表明其性质:

CF_RETURNS_RETAINED
__nonnull CFStringRef MakeJoinedString(__nonnull CFStringRef string1,
__nonnull CFStringRef string2);

       你可能感觉 Unmanaged 只是一时的权宜之计 —— 是的确实,因为对 CoreFoundation 中数量庞大的 API 进行标注的工作还在进行中。随着函数的交互形式被修改得越来越清晰,每一代 Xcode 发布都有可能需要你减少对 takeRetainedValue() 的调用。在最后一个 CFUnannotatedFunctionRef 被改好之前,Unmanaged 将会帮助你渡过难关。

最后加上原文地址

原文地址

Swift--Unmanaged使用的更多相关文章

  1. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  2. Swift - 访问通讯录-使用AddressBook.framework和AddressBookUI.framework框架实现

    1,通讯录访问介绍 通讯录(或叫地址簿,电话簿)是一个数据库,里面储存了联系人的相关信息.要实现访问通讯录有如下两种方式: (1)AddressBook.framework框架 : 没有界面,通过代码 ...

  3. 从0开始学Swift笔记整理(五)

    这是跟在上一篇博文后续内容: --Core Foundation框架 Core Foundation框架是苹果公司提供一套概念来源于Foundation框架,编程接口面向C语言风格的API.虽然在Sw ...

  4. IOS开发之SWIFT进阶部分

    概述 上一篇文章<iOS开发系列--Swift语言> 中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用 ...

  5. ios开发——实用技术篇Swift篇&地址薄、短信、邮件

    //返回按钮事件 @IBAction func backButtonClick() { self.navigationController?.popViewControllerAnimated(tru ...

  6. Swift进阶

    概述 访问控制 Swift命名空间 Swift和ObjC互相调用 Swift和ObjC映射关系 Swift调用ObjC ObjC调用Swift 扩展—Swift调用C 反射 扩展—KVO 内存管理 循 ...

  7. Swift互用性:与 Cocoa 数据类型共舞(Swift 2.0版)-b

    本节内容包括: 字符串(Strings) 数值(Numbers) 集合类(Collection Classes) 错误(Errors) Foundation数据类型(Foundation Data T ...

  8. Swift—Core Foundation框架-备

    Core Foundation框架是苹果公司提供一套概念来源于Foundation框架,编程接口面向C语言风格的API.虽然在Swift中调用这种C语言风格的API比较麻烦,但是在OS X和iOS开发 ...

  9. Swift json字典转模型 项目记录

    背景 最近项目开始转用Swift3开发,由于Swift中json(字典)转模型的选择方案较多,笔者最开始选择了HandyJSON的方案,在使用一段时间后发现当要进行某个字段取值使用时需要进行各种的转化 ...

  10. Swift 对象内存模型探究(一)

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/zIkB9KnAt1YPWGOOwyqY3Q 作者:王 ...

随机推荐

  1. 利用已控的标边界一台机器的 beacon对目标内网进行各种存活探测

    本节的知识摘要: 基于常规 tcp / udp 端口扫描的内网存活探测 基于 icmp 的内网存活探测 基于 arp 的内网存活探测 加载外部脚本进行的各种存活探测 基础环境说明:: WebServe ...

  2. [CSP-S模拟测试]:嘟嘟噜(约瑟夫问题)

    题目描述 由于众所周知的原因,冈部一直欠真由理一串香蕉.为了封上真由理的嘴,冈部承诺只要真由理回答出这个问题,就给她买一车的香蕉:一开始有$n$个人围成一个圈,从$1$开始顺时针报数,报出$m$的人被 ...

  3. eclipse 启动 tomcat 报错:Server mylocalhost was unable to start within 45 seconds

    这个专门转载一篇博文也是为了讽刺一下自己二逼的程序员职业,哈哈. eclipse启动tomcat服务器报错:Server mylocalhost was unable to start within ...

  4. php面试专题---15、MySQL数据库基础考察点

    php面试专题---15.MySQL数据库基础考察点 一.总结 一句话总结: 注意:只写精品 1.mysql定义int(3),那么我存1234就错了么? 不是:无影响:只会影响显示字符的个数:可以为整 ...

  5. Docker容器内部端口映射到外部宿主机端口

    Docker允许通过外部访问容器或者容器之间互联的方式来提供网络服务.容器启动之后,容器中可以运行一些网络应用,通过-p或-P参数来指定端口映射. 注意:宿主机的一个端口只能映射到容器内部的某一个端口 ...

  6. vue-lazyload 图片不更新

    前几天在用vue写项目的时候,因为图片比较多,所以采用了懒加载插件 vue-lazyload github:https://github.com/hilongjw/vue-lazyload#readm ...

  7. EZOJ #374学习

    分析 二分天数 暴力判断即可 代码 #include<bits/stdc++.h> using namespace std; #define int long long ],b[],c[] ...

  8. ruby的next if boolean

    next相当于continue

  9. jmeter之2种方法录制脚本

    有时候候录制脚本比写脚本且快,这时候我们可以采用录制的方法完成jmeter脚本工作 目录 1.badboy录制 2.代理服务器录制 1.badboy录制 第一步:下载安装 badboy2.2下载:链接 ...

  10. Linux 命令详解 - ps

    完整文档 ps 命令用于显示命令执行瞬间的进程状态(Process Status).如果想动态查看进程状态可以使用 top 命令. 进程的概念 进程类型 前台进程:由终端初始化,可以通过命令行进行交互 ...