块和大中枢派发
第37条:理解”块“这一概念
Block的了解还是很有必要的,它和函数类似,只不过是直接定义在另一个函数里的,和定义它的那个函数共享同一个范围的东西。用
^
来表示,后面接一对花括号,括号里是blcok
的实现代码。
void(^someBlcok)() = ^{
NSLog(@"there has someBlock");
};
someBlcok(); // print:“there has someBlock”
格式: 返回类型 (^Block Name)(参数){};
int (^addBlock)(int a, int b) = ^(int a ,int b){
return a + b;
};
int testAdd = addBlock(7,5);
NSLog(@"testAdd === %d",testAdd); // print: “testAdd === 12”
int additonal = 5;
int (^minusBlock)(int a, int b) = ^(int a,int b){
return additonal - a - b;
};
int testMinus = minusBlock(2,6);
NSLog(@"minus === %d",testMinus); //print :"minus === -3"
注意当要改变块里面的变量的时候,需要加上
__block
修饰符
NSArray * array = @[@0,@1,@2,@3,@4,@5];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock:^(NSNumber * number,NSUInteger idx,BOOL *stop){
if([number compare:@4] == NSOrderedAscending)
{
count++;
}
}];
NSLog(@"count ==== %lu",count); // print : "count ==== 4"
注意还可以使用反序和顺序
- (void)enumerateObjectsWithOptions:(NSEnumerationOptions)opts
usingBlock:(void (^)(ObjectType obj, NSUInteger idx, BOOL *stop))block
typedef NS_OPTIONS(NSUInteger, NSEnumerationOptions) {
NSEnumerationConcurrent = (1UL << 0), // 顺序
NSEnumerationReverse = (1UL << 1), // 倒序
};
定义
block
的时候,所占的内存区域是分配在栈中的,也就是block
只在定它的那个范围内有效。所以我们我们需要对其对象发送copy
消息以拷贝值,这样的话,就可以把块从栈复制到堆了,块这样就成了带引用计数的对象啦。
第38条:为常用的块类型创建typedef
由于在定义块变量时,需要把变量名放在类型之中,而不要放在右侧,这样非常难记,也非常难懂,鉴于此,我们应该为常用的块类型起个别名,此时
typedef
关键字就用到了。
typedef int (^YPQBlcok)(BOOL flag,int value);
第39条:用handler块降低代码分散程度
当用Handler块的时候,可以直接将块和相关对象放在一起。这样代码更清晰而紧凑。
#import
typedef void(^YPQNetworkCompletionHandler)(NSData * data);
@interface YPQNetworkFetcher: NSObject
@property (nonatomic, readonly, strong) NSURL * url;
- (id)initWithURL:(NSURL *)url;
- (void)startWithCompletionHandler:(YPQNetworkCompletionHandler)comletion;
@end
注意实现文件中 的 copy
#import "YPQNetworkFetcher.h"
@interface YPQNetworkFetcher()
@property (nonatomic, readwrite, strong)NSURL * url;
@property (nonatomic, copy)YPQNetworkCompletionHandler completionHandle;
@property (nonatomic, strong) NSData * downloadedData;
@end
@implementation YPQNetworkFetcher
- (id)initWithURL:(NSURL *)url
{
if(self = [super init])
{
_url = url;
}
return self;
}
- (void)startWithCompletionHandler:(YPQNetworkCompletionHandler)comletion
{
self.completionHandle = comletion;
// 开始 request
// downloadedData 获值
// 当申请完成,调用 [self p_requestCompleted];
}
- (void)p_requestCompleted
{
if(_completionHandle)
{
_completionHandle(_downloadedData);
}
}
@end
第40条:用块引用其所属对象时不要出现保留环
就是我们使用Block的时候很容易出现循环引用。
#import "ViewController.h"
#import "YPQNetworkFetcher.h"
@interface ViewController ()
{
YPQNetworkFetcher * _networkFetcher;
}
@property (nonatomic ,strong) NSData * downloadData;
@end
- (void)downloadTheData
{
NSURL * url = [NSURL URLWithString:@"https://www.example.com/...."];
_networkFetcher = [[YPQNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData * data){
self.downloadData = data;
}];
}
这样看起来没问题,但是已经形成循环引用啦
-
block
保留了viewController
实例; -
viewController
实例则通过_networkFetcher
保留YPQNetworkFetcher
; -
_completionHandle
又保留了block
。
这样环就形成了,打破环就得打破其中某一个环节,比较好用的方法可以通过;
// 打破上述的第一环节
__weak __typeof(self) weakSelf = self;
[_networkFetcher startWithCompletionHandler:^(NSData * data){
weakSelf.downloadData = data;
}];
或者
// 打破上述的第二环节
[_networkFetcher startWithCompletionHandler:^(NSData * data){
self.downloadData = data;
_networkFetcher = nil;
}];
第41条:多用派发队列,少用同步锁
如果多个线程要执行同一份代码的时候,通常都要使用锁实现某种同步机制。
@synchronized(self) {
//safe
}
_lock = [[NSLock alloc] init];
[_lock lock];
//safe
[_lock unlock];
但是这两种方式效率不高,而且也无法提供绝对的线程安全。有种简单而高效的办法可以代替他们,那即是使用”串行同步队列“,将读取操作和写入操作都安排在同一个队列中。
_syncQuene = dispatch_queue_create("com.yang.test", NULL); - (NSString *)testGCDString
{
__block NSString * localString;
dispatch_sync(_syncQuene, ^{
localString = _testGCDString;
});
return localString;
}
- (void)setTestGCDString:(NSString *)testGCDString
{
dispatch_sync(_syncQuene, ^{
_testGCDString = testGCDString;
});
}
当然还可以继续优化, 将同步派发改为异步派发,并且由于多个获取方法可以并发执行,而获取方法和设置方法之间不能并发执行,此时改用并发队列。
_syncQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)testGCDString
{
__block NSString * localString;
dispatch_sync(_syncQuene, ^{
localString = _testGCDString;
});
return localString;
}
- (void)setTestGCDString:(NSString *)testGCDString
{
dispatch_async(_syncQuene, ^{
_testGCDString = testGCDString;
});
}
但是像上面这样,还是无法正确实现同步。读取和写入可以随时执行,为了不让其任意执行,此时dispatch_barrier_async
就出现啦
_syncQuene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- (NSString *)testGCDString
{
__block NSString * localString;
dispatch_sync(_syncQuene, ^{
localString = _testGCDString;
});
return localString;
}
- (void)setTestGCDString:(NSString *)testGCDString
{
dispatch_barrier_async(_syncQuene, ^{
_testGCDString = testGCDString;
});
}
dispatch_barrier_async
必须单独执行,不能与其他块并行。这只对并发队列有意义,因为串行队里中块总是按顺序逐个来执行的。并发队列如果发现接下来要处理的块是个barrier block
,那么就一直要等到当前所有并发块都执行完毕,才会单独执行这个barrier block
。待barrier block
执行过后,再按正常方式继续向下处理。
在上面这个队列中,在写入操作用了
dispatch_barrier_async
来实现后,对属性的读取操作依然可以并行,但写入操作必须单独执行。当然设置函数中,我们也可以用dispatch_barrier_sync
同步来实现,有时可能更高效,看具体场景吧。
将同步与异步派发结合起来,实现与普通加锁机制一样的同步行为,也不会阻塞执行异步派发的线程,所以是 OK 的。
第42条:多用GCD,少用performSelector
在我们用到推迟执行方法的时候,我们可以有种选择。
// performSelector
[self performSelector:@selector(afterThreeSecondBeginAction)
withObject:nil
afterDelay:3.0f];
// dispatch after
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^(void){
[self afterThreeSecondBeginAction];
});
performSelector
系列方法在内存管理方面容易疏失,并且所处理的选择子太过局限了,GCD 作为纯C的API,一般担忧还是没必要的,所以说少用performSelector
还是有理由的。
第43条:掌握GCD及操作队列的使用时机
简单的说,不要过度使用GCD,要合理使用它,这个可能需要项目经验的积累的。像
NSOperationQueue
类也可以多了解下。
第44条:通过Dispatch Group 机制,根据系统资源状况执行任务
dispatch group
是GCD的一项特性,能够把任务分组。
这里是GCD的详细介绍,推荐一篇文章@ 少君的GCD
第45条:使用dispatch_once 来执行只需要运行一次的线程安全代码
static id _instace;
+ (instancetype)sharedInstanceWithSome
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[self alloc] init];
});
return _instace;
}
使用
dispatch_once
可以简化代码并且彻底保证线程安全,我们根本无须担心加锁或同步,另外它没有使用重量级的同步机制,所以也更高效。
第46条:不要使用dispatch_get_current_queue
其实这个已经在iOS6.0之后被弃用了,它可以做调试,但实际也用的不多,所以一般我想应该是不会用到吧。
持续笔记中····