Effective Objective-C 2.0重读笔记---2
1. 很多时候我们需要保证读写数据的安全性,这时候最好不要使用@synchronized同步块,因为同步块中的代码必须单独执行,这有可能会使当前的代码等许多无关的代码执行完毕才能继续执行,降低程序运行效率。此外还有NSLock ,NSRecursiveLock这些锁,但是这些锁也应该少用,
最好的办法就是用GCD队列保证数据的安全性,而且GCD基于XUN内核,提供了很多底层优化
dispatch_queue_t serialQ = dispatch_queue_create("testGCDQueue",DISPATCH_QUEUE_SERIAL); dispatch_barrier_async(serialQ, ^{ // safe });
dispatch_queue_t serialQ = dispatch_queue_create("testGCDQueue",DISPATCH_QUEUE_SERIAL); dispatch_barrier_sync(serialQ, ^{ // safe });
如果块中的代码量比较少,你会发现asyn居然比syn慢,这是因为asyn需要拷贝块,但是当块里面逻辑运算负责的时候据要看具体情况了
保证数据安全一般是在set和get方法里,所以一般而且数据是允许多读单写的
如果使用并发队列,可以再set里面使用syn在get里面使用asyn,但是读写有可能会交叉进行,所以这样不是很安全
GCD里面提供了一种栅栏块,这种块会等之前的任务块全部执行完毕以后再执行,而栅栏块之后的块需要等到栅栏块执行完毕之后才执行,这样便能提高安全性还能并发执行
dispatch_barrier_async(cerialQ, ^{ // safe });
2.performSelector系列方法在内存管理方面有所疏漏,他无法确定将要执行的选择自具体是什么,因而ARC编译器也就无法插入适当的内存管理方法
而且Per还有很大局限性,per所能实现的功能GCD或者operation都能实现
3.dipatch_group机制可以将队列分组,调用者可以获悉这组任务何时执行完毕
dispatch_queue_t aDQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); // Add a task to the group dispatch_group_async(group, aDQueue, ^{ printf("task 1 \n"); }); dispatch_group_async(group, aDQueue, ^{ printf("task 2 \n"); }); dispatch_group_async(group, aDQueue, ^{ printf("task 3 \n"); }); printf("wait 1 2 3 \n"); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); printf("task 1 2 3 finished \n")
上面的代码就是如何等待一组任务完成,wait函数会阻塞当前线程DISPATCH_TIME_FOREVER是永远等待,可以传你自己的值比如说穿个1000,如果线程执行的时间大于1000,则会返回非0值,,如果小于1000,则会返回0
但是我们在开发中如果是UI线程是千万不能阻塞的,
所以GCD提供了另一个函数dispatch_group_notify()这个是不阻塞线程的通知结束,而且还可以指定组里面的任务完成后,执行哪个块,在哪个队列里执行
还有一种是利用enter函数和leave函数向组里添加任务(这两个一定要成对出现)否则该队列里的任务总是玩成不了
- (void)test1{ dispatch_queue_t aDQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(int i = 0; i < 100; i++) { dispatch_group_enter(group); dispatch_async(aDQueue, ^{ NSLog(@"--%d--",i); dispatch_group_leave(group); }); } dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"finish"); }
enter要放在外面,leave要放在块里最后一句上,这样才能保证正常执行,
4.用dispatch_once执行只需要执行一次的代码
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ });
实现单例时经常会用到上面的代码,这个是@synchronized性能的两倍
5.另外不要使用dispatch_get_current_queue获取当前队列。因为队列是有层级的,你获取的队列可能包括在另一个队列里面。如果用了同步方法,并且在当前队列的上级队列李阿敏执行block,就产生死锁。P183
6.多使用枚举块,少用for循环。遍历NSArray,NSDictionary或者NSSet的时候枚举块的效率比for高,尤其是NSDictionary的时候,
OC1.0的时候使用的NSEnumerator来遍历集合类(NSEnumerator *enumeratoe = [array reverseObjectEnumerator]; 可以执行反向遍历)
NSArray *array = @[@"1",@"2",@"3"]; NSEnumerator *enumeratoe = [array objectEnumerator]; id object; while (object= [enumeratoe nextObject]) { NSLog(@"%@",object); }
NSDictionary *dic = @{@"1":@"yi",@"2":@"er",@"3":@"san"}; NSEnumerator *enumeratoe = [dic keyEnumerator]; id key; while (key = [enumeratoe nextObject]) { id value = dic[key]; NSLog(@"%@",value); }
NSSet就不贴了,gen NSArray一样
OC2.0引入了快速遍历
就是我们常用的for in
for in 也可以执行反向遍历
for (id obj in [array reverseObjectEnumerator]) { NSLog(@"%@",obj); }
基于集合类块的方式也很好用,这种方式尤其是对于字典来说效率很高,因为不需要额外创建一个key的array,而且不用去hash查找,虽然hash效率很高
7.书上讲如何从CFArray等CoreFoundation框架中的数据结构,自定义数组和字典,自己设置内存管理语义,不过貌似用的不多~
8.构建缓存的时候最好使用NSCache而非NSDictionary
NSCache类是专门为缓存设计的,在用作缓存的时候很多方面优于NSDictionary,
--1.当系统资源快要耗尽的时候NSCache可以自动删除缓存,而且他还会优先删除最长最久未使用的数据
--2.另外NSCache不会自动拷贝键,而NSDictionary要实现此功能需要复杂的代码(有时候键的类型不支持自动拷贝)
--3.并且NSCache是线程安全的,而NSDictionary则不具备此优势
--4.可以给NSCache指定总缓存对象数和总缓存大小
指定NSCache开销值时应注意,如果加入的对象是NSData等这种可以直接读取出大小的,可以用。其他的最好不要用,因为计算开销值要访问磁盘等操作会耗费资源和时间
而使用NSCache的目的就是节省资源和时间
NSCache用法很简单直接去看头文件就可以了,跟NSDictionary很像
有一个NSData的子类可以根据需要从缓存里直接删除
NSPurgeableData,当该类对象销毁时会自动从缓存中删除,通过NSCache的evictsObjectsWithDiscardedContent属性可以开启此功能
NSPurgeableData通过 beginContentAccess方法和endContentAccess方法为自己管理(类似于计数器)调用begin然后调用end变回回收
9.类加入运行期系统的时候会调用load方法,对于每个类和每个分类来说都会调用此方法调用顺序是类-》分类
如果要使用load方法,应当十分小心,因为你不知道此时哪些类已经装载好了,哪些类还没有被装载
此外load方法不遵从继承调用规则,如果子类没有实现load,不管其父类是否实现,都不会调用这个方法
load方法应该十分精简或者不调用,因为程序执行load的时候会阻塞,其真正的用途不是实现功能而是调试代码*(看看分类是否已经加载)
跟load相似的方法是initialize方法,应用程序在首次使用某个类的时候会调用这个方法,使用才会调,如果一直不使用就一直不调用
此时的系统不像load时那么脆弱,initialize是遵从继承体系了,如果子类未实现而父类实现了。便会调用父类的方法,此时可以通过iskindofclass等方法判断是哪个类,进而做一些处理
10. NStimer会保留期执行函数中所在的对象,注意释放,