《Effective Objective-C 2.0编写高质量i
2019-01-28 本文已影响0人
Mr_WangZz
37. 理解 “块” 这一概念
实例:
/*
块(block)语法:
return_type (^block_name) (parameters)
*/
//1 全局块
// void (^someBlock)(void) = ^ {
// NSLog(@"这是一个块");
// };
//
// someBlock();
//2 带参数
// int (^intBlock) (int a, int b) = ^(int aaa, int bbb) {
// return aaa + bbb;
// };
//
// NSLog(@"%d", intBlock(900, 98));
//3 在声明它的范围里,所有变量都可为其捕获。
int addi = 1000;
int (^intBlock) (int a, int b) = ^(int aaa, int bbb) {
return aaa + bbb + addi;
};
NSLog(@"%d", intBlock(900, 98));
//4 内联块
NSArray *array = @[@0, @1, @2, @3, @4, @5];
__block NSInteger count = 0;
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
/*
NSOrderedAscending 小于
NSOrderedSame 等于
NSOrderedDescending 大于
*/
if ([obj compare:@2] == NSOrderedAscending) {
count++;
}
}];
NSLog(@"%ld", (long)count);
//self 也是对象,块在捕获它时也会将其保留,所以一般block容易产生“保留循环”
全局块、栈块及堆块
//全局块: 由于运行该块所需的全部信息 都能在编译期确定,所以可以称为“全局块”。如下
void (^someBlock)() = ^ {
NSLog(@"这是一个块");
};
// //栈块
// void (^block) ();
//
// if (count == 2) {
// block = ^{
// NSLog(@"这是一个栈块");
// };
// }else {
// block = ^{
// NSLog(@"这是一个栈块");
// };
// }
//
// block();//此处却在堆。不安全
//改良
//堆块
void (^block) ();
if (count == 2) {
block = [^{
NSLog(@"这是一个栈块");
} copy];
}else {
block = [^{
NSLog(@"这是一个栈块");
} copy];
}
block();//把块复制到堆,就安全了
要点总结
- 块(block)是C、OC、C++中的词法闭包。
- 块 可以接收参数,也可返回值。
- 块 可以分配到栈和堆上,也可以是全局的。分配到栈上的块可拷贝到堆里,这样的话,就和标准的OC对象一样,具有引用计数了。
38. 为常用的块类型创建 typedef
//定义
typedef void(^EOCCompletionHandler)(NSData *data, NSError *error);
//做参数。
- (void)startWithCompletionHandler:(EOCCompletionHandler)completion;
要点总结
- 以 typedef重新定义 块类型,可令块类型用起来更加简单。
- 定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型名称冲突。
- 不妨为同一个块类型定义多种类型别名。如果要重构的代码中使用了块类型的某个别名,那么只需修改响应 typedef 中的块签名即可,无须改动其它 typedef。
39. 用 handle 块降低代码分散程度
块,有时可以代替代理(委托)方法
要点总结
- 在创建对象时,可以使用 内联的handler块 将相关业务一并声明。
- 在多个实例需要监控时,若采用委托模式,那么经常需要根据传入的对象来切换。而若改用 handler块 来实现,则可以直接将块和相关对象放在一起。
- 设计API时如果用到了 handler块,那么可以增加一个参数,是调用者可以通过此参数来决定 块在哪个队列上执行任务。
40. 用块引用其所属对象时不要出现保留环
避免保留环的封装方法
1:
_networkFetcher = [[EOCNetworkFetcher alloc] initWithUrl:[NSURL URLWithString:@"www.baidu.com"]];
[_networkFetcher startWithCompletionHandler:^(NSData *data, NSError *error) {
_fetcherData = data;
//避免保留环的方法1
_networkFetcher = nil;
}];
2:
/** 避免保留环的方法2 */
- (void)p_requestCompleted {
if (_handler) {
_handler(_downloadData, nil);
}
self.handler = nil;
}
要点总结
- 如果 块所捕获的对象 直接或间接地保留了块本身,那么就得担心保留环问题。
- 一定要找个合适的时机解除保留环,而不能把责任推给API的调用者。
41. 多用派发队列,少用同步锁
.h
{
NSString *_someString;
}
- (NSString *)someString;
- (void)setSomeString:(NSString *)someString;
/*************************************/
.m
{
dispatch_queue_t _queue;
}
- (instancetype)init {
if (self = [super init]) {
//_queue = dispatch_queue_create("EOC by Wzz", nil);
_queue = dispatch_get_global_queue(0, 0);
}
return self;
}
- (NSString *)someString {
__block NSString *localSomeString;
dispatch_sync(_queue, ^{
localSomeString = _someString;
});
return localSomeString;
}
- (void)setSomeString:(NSString *)someString {
dispatch_barrier_async(_queue, ^{
// [NSThread sleepForTimeInterval:3.0f];
_someString = someString;
});
}
要点总结
- 派发队列(dispatch queue)可用来表述同步语义(synchronization semantic),这种做法要比使用 @synchronized块 或 NSLock对象 更简单。
- 将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程。
- 使用同步队列及栅栏块,可以令同步行为更加高效。
42. 多用GCD,少用 performSelector 系列方法
要点总结
- performSelector 系统方法在内存管理方面容易有疏失。它无法确定将要执行的选择子具体是什么,因而 ARC 编译器也就无法插入适当的内存管理方法。
- performSelctor 系列方法所能处理的选择子太过局限了,选择子的返回类型及发送给方法的参数个数都受到限制。
- 如果想把任务放到另一个线程上执行,那么最好不要用 performSelector 系列方法,而是应该把任务封装到块里,然后调用大中枢派发机制(GCD)的相关方法来实现。
43. 掌握GCD及操作队列的使用时机
使用 NSOperation 及 NSOperationQueue 的好处:
-
取消某个操作。
已启动的任务无法取消。
-
指定操作间的依赖关系。
-
通过键值观测机制监控 NSOperation 对象的属性。
-
指定操作的优先级
-
重用 NSOperation 对象
要点总结:
- 在解决多线程与任务管理问题时,派发队列并非是唯一解决方案;
- 操作队列提供了一套高层的 Objective-C API,能实现GCD所具备的绝大部门功能,而且还能完成一些更为复杂的操作,这些操作若改用GCD来实现,需另写代码。
44. 通过Dispatch Group机制,根据系统资源状况来执行任务
要点总结:
- 一系列任务可归入一个dispatch group之中。开发者可以在这组任务执行完毕时获得通知。
- 通过dispatch group,可以在并发式队列里同时执行多项任务。此时GCD会根据系统资源状况来调度这些并发执行的任务。开发者若自己来实现此功能,则需要编写大量代码。
45. 使用dispatch_once来执行只需执行一次的线程安全代码
要点总结
- 经常需要编写“只需执行一次的线程安全代码”。通过GCD所提供的dispatch_once函数,很容易就能实现此功能。
只需执行一次的线程安全代码: thread-safe single-code execution
- 标记应该生命在static或global作用域中。这样的话,在把只需要执行一次的块传给dispatch_once函数时,传进入的标记也是相同的。
46. 不要使用 dispatch_get_current_queue
要点总结
- dispatch_get_current_queue 函数的行为常常与开发者所预期的不同。此函数已被废弃,只应做调试只用。
- 由于派发队列是按层级来组织的,所以无法单用某个队列对象来描述“当前队列”这一概念。
- dispatch_get_current_queue 函数用于解决由不可重入的代码所引发的死锁,然而能用此函数解决的问题,通常也能该用“队列特定数据”来解决。