惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园_首页
The GitHub Blog
The GitHub Blog
美团技术团队
Know Your Adversary
Know Your Adversary
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Register - Security
The Register - Security
Stack Overflow Blog
Stack Overflow Blog
Attack and Defense Labs
Attack and Defense Labs
G
Google Developers Blog
I
InfoQ
博客园 - 司徒正美
T
Troy Hunt's Blog
Google DeepMind News
Google DeepMind News
J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
S
Security Affairs
M
MIT News - Artificial intelligence
Simon Willison's Weblog
Simon Willison's Weblog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Tailwind CSS Blog
量子位
Vercel News
Vercel News
月光博客
月光博客
V
Vulnerabilities – Threatpost
N
News and Events Feed by Topic
Hugging Face - Blog
Hugging Face - Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
L
LangChain Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
L
LINUX DO - 最新话题
F
Full Disclosure
The Hacker News
The Hacker News
Hacker News: Ask HN
Hacker News: Ask HN
T
Tor Project blog
A
Arctic Wolf
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Forbes - Security
Forbes - Security
IT之家
IT之家
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Y
Y Combinator Blog
GbyAI
GbyAI
B
Blog RSS Feed
V
Visual Studio Blog
T
The Blog of Author Tim Ferriss
F
Fortinet All Blogs

土土哥的Blog

好久没有更新博客=。= 阿里巴巴国际无线技术部 - 招人啦~求iOS、Android、Java 转-阿里巴巴国际无线技术部 - 在这里遇见最好的自己 反编译分析并模拟实现methodSignatureForSelector方法 反编译分析Xcode8的Bug, release下连续两次调用有二级指针参数的空方法会Crash 有趣的Autolayout示例5-Masonry实现 在对象dealloc的后期执行Task-开源库TTGDeallocTaskHelper 用QuartzCode快速实现一个收藏动画 开源项目-拼图验证控件TTGPuzzleVerify的实现 Swift开源Mac App - BingWallPaper 有趣的Autolayout示例4-Masonry实现 翻译-为什么objc_msgSend必须用汇编实现 API返回结果设计经验与总结 结合访问Out Parameters出现EXC_BAD_ACCESS的例子,反编译汇编解读__autoreleasing 总结一些iOS项目中组织代码的方法 对组件化与模块化的思考与总结 开源项目-TTGTagCollectionView 有趣的Autolayout示例3-Masonry实现 Swift开源项目: TTGEmojiRate的实现 Swift写的库-TTGSnackbar 有趣的Autolayout示例2-Masonry实现 解决iOS项目的版本兼容问题-结合宏、Category和Runtime 用Runtime的手段填充任意NSObject对象的nil属性 有趣的Autolayout示例-Masonry实现 UITextView编辑时插入自定义表情-续-自定义表情图片的大小 RPC框架Thrift例子-PHP调用C++后端程序 GCD使用经验与技巧浅谈 Enum-枚举的正确使用-Effective-Objective-C-读书笔记-Item-5 @autoreleasepool-内存的分配与释放 有关宏定义的经验与技巧-简化代码-增强Log Effective-Objective-C-读书笔记-Item-4-如何正确定义常量 UITextView编辑时插入自定义表情-简单的图文混编 关于评论不见了=。= Entity和Model的不同-关于代码的数据层 一次审核被拒的经历-关于iCloud到底应该备份什么数据 Block类型变量-缓存Http请求与回调 提升UITableView性能-复杂页面的优化 NSString的Copy与内存分配 利用NSProxy实现消息转发-模块化的网络接口层设计-原创 Effective-Objective-C-读书笔记-Item-3 Effective-Objective-C-读书笔记-Item-2 Effective-Objective-C-读书笔记-Item-1 iOS项目的目录结构-原创 Android开源库-LinkTextView-原创 第一篇Blog
为GCD队列绑定NSObject类型上下文数据-利用__bridge_retained(transfer)转移内存管理权
土土哥 · 2015-03-29 · via 土土哥的Blog

2015-5-28更新

下面评论的好友“@Jim”给了种新的思路,就是在清除context的函数里面,用“_bridge_transfer”转换context,把context的内存管理权限重新交给ARC,这样,就不用显式调用“CFRelease”了。如下:

1
2
3
4
5
6
7
void cleanStaff(void *context) {

Data *data = (_bridge_transfer Data *)(context);
NSLog(@"In clean, context number: %d", data.number);


}

前言

看过GCD(Grand Central Dispatch)的Apple官方文档的朋友一定见过“dispatch_set_context”和“dispatch_get_context”这两个函数,那么这两个函数该怎么用呢?

我们都知道,GCD的接口参数都是“C语言类型“的,那么,我们如何将NSObject类型(Foundation框架)的数据,传入GCD的接口呢?(即:Core Foundation和Foundation对象的转换)

本文关键字

  • GCD:dispatch_set_context,dispatch_get_context
  • __bridge,__bridge_retained,__bridge_transfer
  • Core Foundation, NSObject

dispatch_set(get)_context

先看看这两个函数的原型:

1
2
3
4

void dispatch_set_context (dispatch_object_t object, void *context);

void* dispatch_get_context (dispatch_object_t object);

这里的object一般指的就是通过dispatch_queue_create创建的队列。

所以,这两个函数分别完成了将context“绑定”到特定GCD队列和从GCD队列获取对应context的任务。

什么是context

在上述函数原型中,context是一个“void类型指针”,学过C语言的朋友应该都知道,void型指针可以指向任意类型,就是说,context在这里可以是任意类型的指针。

从这里可以得知,我们可以为队列“set”任意类型的数据,并在合适的时候取出来用。

用malloc创建context并绑定到队列上

参考Apple官方的例子,我们先用传统的malloc创建context,看看如下简短例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

typedef struct _Data {
int number;
} Data;


void cleanStaff(void *context) {
NSLog(@"In clean, context number: %d", ((Data *)context)->number);

free(context);
}

- (void)runTest {

dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);


Data *myData = malloc(sizeof(Data));
myData->number = 10;


dispatch_set_context(queue, myData);


dispatch_set_finalizer_f(queue, cleanStaff);

dispatch_async(queue, ^{

Data *data = dispatch_get_context(queue);

NSLog(@"1: context number: %d", data->number);

data->number = 20;
});
}

上面的代码运行后如下:

1
2
2015-03-29 20:28:16.854 GCDTest[37787:1443423] 1: context number: 10
2015-03-29 20:28:16.855 GCDTest[37787:1443423] In clean, context number: 20

看,通过为队列设置context,我们就能为队列绑定自定义的数据,然后在合适的时候取出来用。

NSObject类型的context

在Mac、iOS的开发过程中,我们大部分用的都是Foundation框架下的类,就是如NSString、NSDictionary这些NSObject类型的类。

但是上面的dispatch_set(get)_context接受的context参数是C语言类型的,即Core Foundation类型的,我们如何转换呢?

由于ARC不能管理Core Foundation Object的生命周期,所以我们必须先转换context的“类型”,以便转换内存管理权。

__bridge

Apple已经为我们提供了用于转换的关键字,如下:

  • __bridge: 只做了类型转换,不修改内存管理权;
  • __bridge_retained(即CFBridgingRetain)转换类型,同时将内存管理权从ARC中移除,后面需要使用CFRelease来释放对象;
  • __bridge_transfer(即CFBridgingRelease)将Core Foundation的对象转换为Objective-C的对象,同时将内存管理权交给ARC。

重新定义context

为了方便下面的说明,我们先定义context类。

1
2
3
4
5
6
7
8
9
10
11
12
@interface Data : NSObject
@property(assign, nonatomic) int number;
@end

@implementation Data


- (void)dealloc {
NSLog(@"Data dealloc...");
}

@end

看,我们继承了dealloc方法,这样就能知道Data类型对象什么时候被释放。

需要注意的点

__bridge的转换是没有转移内存管理权的,这点要特别注意。

如果在传context对象时,用的是__bridge转换,那么context对象的内存管理权还在ARC手里,一旦当前作用域执行完,context就会被释放,而如果队列的任务用了context对象,就会造成“EXC_BAD_ACCESS”崩溃!

正确的用法

重写上面的例子,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

void cleanStaff(void *context) {

Data *data = (__bridge Data *)(context);
NSLog(@"In clean, context number: %d", data.number);


CFRelease(context);
}

- (void)testBody {

dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);


Data *myData = [Data new];
myData.number = 10;



dispatch_set_context(queue, (__bridge_retained void *)(myData));


dispatch_set_finalizer_f(queue, cleanStaff);

dispatch_async(queue, ^{


Data *data = (__bridge Data *)(dispatch_get_context(queue));

NSLog(@"1: context number: %d", data.number);

data.number = 20;
});
}

解释

  • 在dispatch_set_context的时候用__bridge_retained转换,将context的内存管理权从ARC移除,交给我们自己管理。
  • 在队列任务中,用dispatch_get_context获取context的时候,用__bridge转换,维持context的内存管理权不变,防止出了作用域context被释放。
  • 最后用CFRelease释放context内存。

运行结果

1
2
3
2015-03-29 21:12:41.631 GCDTest[38131:1465900] 1: context number: 10
2015-03-29 21:12:41.632 GCDTest[38131:1465900] In clean, context number: 20
2015-03-29 21:12:41.632 GCDTest[38131:1465900] Data dealloc...

由结果可知,我们的context对象在最后显式调用CFRelease才被释放。

总结

总的来说,就是合理运用__bridge_retained(transfer)关键字转换对象的内存管理权,让我们自己控制对象的生命周期。

参考