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会保留期执行函数中所在的对象,注意释放,

 

posted @ 2014-08-05 16:10  784692237  阅读(349)  评论(0编辑  收藏  举报