《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. 块的内部结构
  • 块对象的内存布局

    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块,且有错误的块也放在一起
    倾向于:

    而不是
  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,打破保留环

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

  1. 使用get,set方法做同步操作的时候,可以使用GCD,少用同步锁 @synchronized(self) 等方法。原因是GCD更高效,不会阻塞执行异步派发的线程,同步锁会产生死锁现象。
  2. 使用同步队列及栅栏块,可以令同步行为更叫高效。
    栅栏执行的逻辑是,栅栏前面的队列可以并发执行,当遇到栅栏的时候,等到前面的队列全部执行完后,再执行栅栏里面的操作,栅栏执行完后,在执行栅栏后的操作。
    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选择不释放,所以可能导致内存泄漏。

情况二


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

  1. 总结


第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

没看,不适用的函数了

点击进入 第七章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容