《EffectiveObjective-c 2.0》第六章 块与大中枢派发

第37条:理解“块”这一概念

  1. 块的基础知识
  • 模型 return_type (^block_name)(parameters)
  • 默认情况下,为块所捕获的变量,是不可以在块里修改的
int additional = 5;
    int (^addOption)(int a, int b) = ^(int a, int b){
        additional = 7;//Variable is not assignable (missing __block type specifier)
        return a + b + additional;
    };
    int result = addOption(2, 5);

除非加上__block关键字,但是如果是实例变量就可以直接修改,且不用加__block

  • 实例变量读取和写入操作,在块中的访问方式self.anInstanceVariable_anInstanceVariable都是一样的,所以不要认为使用_anInstanceVariable这种访问方式时,在块中就不保留self对象了。防止“保留环“
  1. 块的内部结构
  • 块对象的内存布局
    《EffectiveObjective-c 2.0》第六章 块与大中枢派发_第1张图片

    invoke变量是一个函数指针,指向块的实现代码。
    descriptor变量是指向结构体的指针,每个块里都包含此结构体,其中声明了块对象的总体大小,还声明了copy与dispose这两个辅助函数所对应的函数指针
    invoke函数为何需要把块对象作为参数传进来呢?原因在于,块执行时,需要从内存中把这些捕获到的变量读出来。
  • 全局块、栈块及堆块
    编译器会给每个块分配好栈内存,然而等离开了相应的范围之后,编译器有可能把分配给块的内存腹写掉。为了解决此问题,可给块发送copy消息以拷贝之。这样的话,就可以把块从栈复制到堆了,拷贝后的块,可以在定义它的那个范围之外使用。而且,一旦复制到堆上,块就成了带引用计数的对象了。后续的复制操作都不会真的执行复制,只是递增块对象的引用计数了。如果不在使用这个块,那就应将其释放,在ARC环境下会自动释放,而手动管理引用计数则需要自己来调用release方法。当引用计数降为0后,“分配在堆上的块”会像其他对象一样,为系统所回收。而“分配在栈上的块”则无须明确释放,因为栈内存本来就会自动回收。

第38条:为常用的块类型创建typedef

  1. 以typedef重新定义块类型,可令块变量用起来更加简单。
  2. 定义新类型时应遵从现有的命名习惯,勿使用其名称与别的类型相冲突
typedef void (^VCComplete)(BOOL flag,int value);

//使用
VCComplete complete = ^(BOOL flag,int value){
        
    };

第39条:用handle块降低代码分散程度

  1. 在创建对象时,可以使用内联的handler块将相关业务逻辑一并声明。
  2. 在有多个实例需要监控时,如果采用委托模式,那么需要经常要根据传入的对象来切换,而若改用handler块来实现,则可直接将块与相关对象放在一起。(推荐使用handler块,且有错误的块也放在一起
    倾向于:
    《EffectiveObjective-c 2.0》第六章 块与大中枢派发_第2张图片

    而不是
    《EffectiveObjective-c 2.0》第六章 块与大中枢派发_第3张图片
  3. 设计API时如果用到了handler块,那么可以增加一个参数,使调用者可以通过此参数来决定应该把块安排到哪个队列上执行。

第40条:用块引用其所属对象时不要出现保留环

  1. 使用块所捕获的对象直接或间接地保留了块本身,那么就得当心保留环问题。
  2. 一定要找个适当的时机解除保留环,而不能把责任推给API调用者。
    下面情况注意:
//保留环
int (^addOption)(int a, int b) = ^(int a, int b){
        _additional = 7;//_additional实例变量,_additional等效于self. additional,所以该块捕获到了self对象
        return a + b + _additional;
    };

块复制过后,调用执行块后,要把块置为nil,打破保留环

《EffectiveObjective-c 2.0》第六章 块与大中枢派发_第4张图片

第41条:多用派发队列,少用同步锁

  1. 使用get,set方法做同步操作的时候,可以使用GCD,少用同步锁 @synchronized(self) 等方法。原因是GCD更高效,不会阻塞执行异步派发的线程,同步锁会产生死锁现象。
  2. 使用同步队列及栅栏块,可以令同步行为更叫高效。
    栅栏执行的逻辑是,栅栏前面的队列可以并发执行,当遇到栅栏的时候,等到前面的队列全部执行完后,再执行栅栏里面的操作,栅栏执行完后,在执行栅栏后的操作。
    《EffectiveObjective-c 2.0》第六章 块与大中枢派发_第5张图片
    WX20170817-180958.png

    栅栏块的效果

第42条:多用GCD,少用performSelector系列方法

  1. 情况一
    使用performSerlector在下面这种情况下,会提示警告
 BOOL fool = YES;
    SEL selector;
    if(fool){
        selector = @selector(fetchData);
    }else{
        selector = @selector(fetchData1);
    }
    [self performSelector:selector];
//警告:PerformSelector may cause a leak because its selector is unknown

警告的原因是:编译器并不知道将要调用的选择子是什么,因此,也就不了解其方法签名及返回值,甚至是否有返回值都不清楚,而且,编译器不知道方法名,所以就没有办法运用ARC的内存管理规则来判断返回值是不是应该释放,ARC选择不释放,所以可能导致内存泄漏。

情况二

《EffectiveObjective-c 2.0》第六章 块与大中枢派发_第6张图片

这里的 newObject, copy这两个创建的对象需要手动释放, someProperty不需要收到释放,参考上一章的第30条。

  1. 总结


    《EffectiveObjective-c 2.0》第六章 块与大中枢派发_第7张图片

第43条:掌握GCD及操作队列的使用时机

  1. 在解决多线程与任务管理问题时,派发队列并非唯一方案。
  2. 操作队列提供了一套高层的Objective-C API,能实现纯GCD所具备的绝大部分功能,而且还能完成一些更为复杂的操作,那些操作若改用GCD来实现,则需另外编写代码。
  3. NSOperationNSOperationQueue的好处如下:
  • 取消某个操作。NSOperation可以在对象上调用cancel方法,来取消尚未执行的任务,已经启动的任务无法取消,GCD无法取消队列,GCD框架是“安排好任务后,就不需要管了”。
  • 指定操作间的依赖关系。下载“清单文件”后才能下载其他文件,那么就需要先下载“清单文件”。
  • 通过键值观测机制(KVO)来监听NSOperation对象的属性。NSOperation很多属性都可以通过KVO来进行监听,如isCancelledisFinished等属性。如果想在某个任务变更其状态时得到通知,或是想用比GCD更为精细的方式来控制所要执行的任务,那么键值观测机制会有用。
  • 指定操作的优先级。操作的优先级表示此操作与队列中的其他操作之间的优先级的关系。GCD的队列确实也有优先级,不过那是针对整个队列来说的,不是针对每个块来说的。
  • 重用NSOperation对象。NSOperation对象在执行时可以充分利用存于其中的信息,而且还可以随意调用定义在类中的方法。这些NSOperation类可以在代码中多次使用。

第44条:通过Dispatch Group 机制,根据系统资源状况来执行任务

  1. dispatch group 能够把任务分组,调用者可以等待这组任务执行完毕,可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。最重要的是执行操作后,可以得到通知,以便可以根据结果处理相应操作。重要用法:就是把将要并发执行的多个任务合为一组,于是调用者就可以知道这些任务何时才能全部执行完毕
// 1. 创建组
dispatch_group_t group = dispatch_group_create();

// 2. 异步调用,dispatch_group_async没啥特殊用途,就是把队列归属到一个组里面,同dispatch_async
dispatch_group_async(dispatch_group_t group,
    dispatch_queue_t queue,
    dispatch_block_t block);
// 或者是指定任务所属的组
void dispatch_group_enter(dispatch_group_t group);//加入到组中
void dispatch_group_leave(dispatch_group_t group);//从组中移除队列
//前者是分组里正要执行的任务递增,后者使之递减,必须成对出现。如果调用enter之后,没有相应的leave操作,那么这组任务永远执行不完。

// 3. 等待组执行完,阻塞线程,dispatch_group_wait前面的先执行,等时间超过timeout,再执行dispatch_group_wait后面的代码,起到阻塞作用
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

// 4. 等到组中的线程都执行完后,再执行这个通知
void dispatch_group_notify(dispatch_group_t group,
    dispatch_queue_t queue,
    dispatch_block_t block);

// 5. 遍历某个collection,每个任务可以并发执行,且是阻塞线程的,dispatch_apply后面的任务要等到该函数遍历执行完后,才放开线程。
void dispatch_apply(size_t iterations, dispatch_queue_t queue,
        DISPATCH_NOESCAPE void (^block)(size_t));

第45条:使用dispatch_once来执行只需运行一次的线程安全代码

  1. 使用dispatch_once可以简化代码更高效,且彻底保证线程安全。它没有使用重量级的同步机制,若是那样做的话,每次运行代码前都要获取锁,相反,此函数采用“原子访问”来查询标记,判断其所对应的代码是否已经执行过。
+ (id)sharedInstance{
    static WCCPerson *sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] init];
    });
    return sharedInstance;
}

+ (id)sharedInstance{
    static WCCPerson *sharedInstance = nil;
    @synchronized(self) {
        if (!sharedInstance) {
            sharedInstance = [[self alloc] init];
        }
    }
    return sharedInstance;
}
//两者执行的时间差不多
    CFAbsoluteTime startTime =CFAbsoluteTimeGetCurrent();
    WCCPerson *persion = [WCCPerson sharedInstance];
    CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);
    //once 0.019968 0.015020 0.009000  
    //sync 0.010967 0.011027 0.010014
    NSLog(@"Linked in %f ms", linkTime *1000.0);

第46条:不要使用dispatch_get_current_queue

没看,不适用的函数了

点击进入 第七章

你可能感兴趣的:(《EffectiveObjective-c 2.0》第六章 块与大中枢派发)