Effective Objective-C - 块与大中枢派发
前言
Effective Objective C 读书笔记 - 块与大中枢派发
多线程的两大核心:block,GCD
笔记
理解“块”这以概念
block其实也是一个数据类型,块也是对象类型
1
2
3void (^someBlock)() = ^{
// do something here
}块的强大之处在于:在声明块的范围里面,所有的变量都可以为其所捕获,在快内可以使用这些变量
在Block之前,只能使用函数指针或者是selector
每个OC对象都占有一个内存区域,块也是对象,下图就是块的内存区域:
- invoke 是函数指针,指向块的实现代码
- descriptor 是一个结构体指针,每个block都有这么一个变量,该结构体包含了块的详细信息:大小等等
- 块还会把 捕获到的变量 拷贝一份,注意这里拷贝的是指针
定义块的时候,块所占的内存是分配在栈中的,来看看下面的例子
1
2
3
4
5
6
7
8
9
10
11void (^block)(); // 声明一个block,是一个指针
if (/* some condition */) {
block = ^{
// do something
}
} else {
block = ^{
// do something
}
}
block();注释:在if和else中,分别创建了block,这个block是一个栈对象,也就是他们的内存是在栈上面的;然后将该栈内存的首地址赋予block。但是这个对象出了
{}
,其内存可能被覆写,那么block变量指向的就不是一个真正的块了注意,这里的block是一个指针;和一般的int理解上有所不同
1
2
3
4
5
6
7
8int a;
if (/* some condition */) {
int tmp = 2;
a = tmp;
} else {
int tmp = 3;
a = tmp;
}这里tmp的内存在出了
{}
之后也是会被覆写的,但是在{}
里面,直接将tmp的内存中的值写到了a的内存中,所以a还是可以使用的。但是,写到block内存中的是这个栈对象的地址,地址还是可以使用的,但是地址里面的内容就不一定是原来的块对象了至于,栈和堆的区别可以看看 Objective-C 拾遗:从Heap and Stack到Block 这篇文章
所以为了避免在栈上的块对象被销毁,就得使用
copy
方法了。block的copy
方法将block从栈拷贝到堆上面,那么这个时候的block就和正常的OC对象一样了,具备引用计数,可以使用ARC来管理了5和6分别讲述了 栈块 和 堆块 的区别,还有一种块,叫做 全局块 。这种block不会捕捉外部变量,它的内存在全局内存中,而且对他执行copy操作也只是空操作,类似一个函数
为常用的块类型创建typedef
定义块变量是非常不符合常理,不好记住,所以要使用 typedef
1
2
3
4typedef int (^EOCSomeBlock)(BOOL flag, int value);
EOCSomeBlock block = ^(BOOL flag, int value) {
// block implememtation
};上面的EOCSomeBlock表示 接受2个参数,并返回int的block类型
用handler块降低代码分散程度
- 也就是使用block来代替delegate
- 还可以同时使用多个block,来处理success或者fail的情况
用块引用其所属对象时不要出现保留环
多用派发队列,少用同步锁
dispatch_sync(queue, block);
等待block执行完,再执行后面的语句;dispatch_async(queue, block);
不等待block执行完,立刻执行后续语句。具体参考dispatch_async 和 dispatch_sync 的区别来个例子说明一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16_syncQueue = dispatch_queue_create("com.vanney9.queue", NULL);
- (NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{ // 1
localSomeThing = _someString;
});
return localSomeString; // 2
}
- (void)setSomeString:(NSString *)someString {
dispatch_async(_syncQueue, ^{ // 3
_someString = someString;
});
return; // 4
}- get方法使用同步派发(dispatch_sync):因为必须等待block执行完,才能返回localString,如果使用异步派发(dispatch_async)的话,block还没有执行,也就是localString还没有设置,就返回了,那么就返回空值了。。
- set方法使用异步派发,因为不需要等待block执行完
上面的例子使用的queue都是串行队列,也就是必须等待注册到该序列中的前面的block执行完了,才能执行自己的bloc;但是对于读操作来说是可以并行的,只要写操作和其他的操作串行化就可以了。所以可以使用 并行序列+栅栏 来实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14_syncQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 1
- (NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_syncQueue, ^{ // 2
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString {
dispatch_barrier_async(_syncQueue, ^{ // 3
_someString = somString;
});
}注释1:创建了一个并发队列
注释2:使用并发队列,可以同时执行get方法
注释3:使用栅栏技术,让写操作执行的时候,队列中的其他操作无法执行
多用GCD,少用performSelector系列方法
先来说说
performSelector
方法1
2
3
4
5
6
7SEL selector;
if (/* some condition*/) {
selector = @selector(foo);
} else {
selector = @selector(bar);
}
[object performSelector:selector];动态绑定之上再动态绑定
为什么不推荐使用呢?因为该方法返回的是id类型,也就是一个指向任意OC对象的指针(在32位架构上占32位,4字节;在64位架构上占64位,8字节)。但是如果selector方法返回int,float等类型时就不好转换了
使用GCD可以实现:延迟执行,选择特定线程执行
掌握GCD及操作队列的使用时机
- 还有一整多线程机制:NSOperationQueue + NSOperation 也就是操作队列, 它也可以实现和GCD相同的功能。其中NSOperationQueue类似GCD;而NSOperation类似block
- 操作队列对比GCD的优点:
- 可以取消尚未开始执行的NSOperation
- 可以指定操作间的依赖关系:可以指定当前的NSOperation在某个NSOperation执行结束后,才开始执行
- NSOperation的许多属性适合KVO,通过监听可以更粒子化的监控NSOperation
- 可以指定NSOperation的优先级:GCD的优先级是针对队列之间的;但是操作队列的优先级是针对NSOperation的,也就是同一队列里面的block的优先级。当然,操作队列也有线程间的优先级
通过Dispatch Group机制,根据系统资源状况来执行任务
- 使用 dispatch group 机制可以将任务分组。调用者可以等待这组任务执行完毕;也可以给直接提供回调函数,立刻进行下面的步骤
- 这个特性最重要的一点用途就是:把将要并发执行的多个任务合并为一组,那么就可以知道这些任务什么时候全部执行完毕
dispatch_group_wait
和dispatch_group_notify
两个函数可以用来执行结束后该做什么,具体参见 iOS与OS X多线程和内存管理 - GCD- 使用并发队列进行多线程任务时,系统会根据当时的情况自动分配线程,无须码农自己来调度
使用dispatch_once来执行只需运行一次的线程安全代码
这个常用于单例模式
example:
1
2
3
4
5
6
7
8+ (id)sharedInstance {
static ECOClass *sharedInstanced = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}在函数中的static变量:只在函数中可见,但是其生命期为整个程序。具体见 Static variable inside of a function in C