iOS 编写高质量Objective-C代码(六)—— Block专栏

《编写高质量OC代码》已顺利完成一二三四五六七八篇!
附上链接:
iOS 编写高质量Objective-C代码(一)—— 简介
iOS 编写高质量Objective-C代码(二)—— 面向对象
iOS 编写高质量Objective-C代码(三)—— 接口和API设计
iOS 编写高质量Objective-C代码(四)—— 协议与分类
iOS 编写高质量Objective-C代码(五)—— 内存管理机制
iOS 编写高质量Objective-C代码(六)—— block专栏
iOS 编写高质量Objective-C代码(七)—— GCD专栏
iOS 编写高质量Objective-C代码(八)—— 系统框架


本篇的主题是iOS中的 “Block的原理及应用”

先简单介绍一下今天的主角:block

  • block(块):是一种 “ 词法闭包 ”,通过block,开发者可将代码块像对象一样传递。

一、理解“block”的概念:

1. block的数据结构:

通过clang命令行工具(OC转C++),我们先来看一下block的内部数据结构大概是什么样子的?

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

解析:很显然,Block_layout是一个结构体:里面有一个isa指针,指向Class对象。还有一个函数指针,指向了块的实现代码。

block的数据结构

2. block的三种类型:全局块、栈块、堆块。

根据block在内存中的位置,block被分成三种类型:

类型 内存位置 介绍
__NSStackBlock__ 栈区 栈内有效,出栈后销毁。
__NSMallocBlock__ 堆区 copy到堆空间上。可以在定义的那个范围之外使用。
__NSGlobalBlock__ 全局区 不捕捉任何外部变量,全部信息在编译器就已确定。

  • 1. NSStackBlock 栈块:
    栈块保存于栈区,超出变量作用域,栈上的block以及声明的_block都会被销毁。

例如:

__block NSString *name = @"QiShare";
void (^block)(void) = ^{
    NSLog(@"%@ is an iOS team which loves to share technology.", name);
};
NSLog(@"block = %@", block);

小知识点:当block内部需要修改或访问外部变量时,外部变量需要额外用__block修饰。否则修改不了。

我们来看下打印:

ARC的场景

什么?居然是__NSMallocBlock__(堆块)?
那是因为ARC环境下,编译器自动帮我们加了copy操作。

这时我们关掉ARC:设置Objective-C Automatic Reference Counting = NO。再来看下打印:

MRC场景

  • 2. NSMallocBlock 堆块:
    堆block内存位于堆区,在变量作用域结束时依然可以使用。

通过上面的例子:
在ARC下,block会默认加上copy操作:变成__NSMallocBlock__


  • 3. NSGlobalBlock 全局块:
    块中无任何外界对象,所需的内存在编译时就可以确定,内存位于全局区。
    类似于“单例”,copy是一个空操作。

例如:

void (^qiShare)(void) = ^{
    
    NSLog(@"We love sharing.");
};
NSLog(@"%@",qiShare);

二、为常用的block类型创建typedef

为了增加代码的可读性可拓展性
需要为常用的block起个别名。

typedef为块起别名,也可令块变量用起来更加简单~
比如:

- (void)getDataWithToken:(NSString *)token success:(void (^)(id responseDic))success;

//! 以上要改成下面这种
typedef void (^SuccessBlock)(id responseDic);
- (void)getDataWithToken:(NSString *)token success:(SuccessBlock)success;

三、用handler块降低代码分散程度

在我们iOS开发中,经常会异步执行一些任务,等待任务执行结束后再通知对象调用相关方法。
一般有两种做法:

  • 第一种:使用NSNotificationCenter:NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
  • 第二种:使用委托协议:详情见iOS 编写高质量Objective-C代码(四)
  • 第三种:使用block回调:直接把block对象当做参数传给相关方法执行。

举个例子:AFNetworking的API设计及使用就是block回调

  • 接口设计:
- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
                       success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                       failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure {

    return [self POST:URLString parameters:parameters progress:nil success:success failure:failure];
}
  • 使用:
    AFHTTPSessionManager *manger =[AFHTTPSessionManager manager];
    NSString *urlString = @"";
    NSMutableDictionary *parameter= @{@"":@"",@"":@""};
    
    [manger POST:urlString
            parameters:parameter 
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
                NSLog(@"成功");
            } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
                NSLog(@"%@",error);
            }];
}

四、用block引用其所属对象时避免出现循环引用

在我们日常开发中,如果block使用不当,很容易导致内存泄漏。

  • 理由:如果block被当前ViewController(self)持有,这时,如果block内部再持有ViewController(self),就会造成循环引用。
  • 解决方案:在block外部弱化self,再在block内部强化已经弱化的weakSelf

For Example:

__weak typeof(self) weakSelf = self;

[self.operationQueue addOperationWithBlock:^{

    __strong typeof(weakSelf) strongSelf = weakSelf;

    if (completionHandler) {

        KTVHCLogDataStorage(@"serial reader async end, %@", request.URLString);      
        completionHandler([strongSelf serialReaderWithRequest:request]);
    }
}];

当然,也不是所有block中使用到self都要先弱化成weakSelf,再强化成strongSelf
只要block没有被self所持有的,在block中就可以使用self
比如下面:

[QiNetwork requestBlock:^(id responsObject) {
      NSLog(@"%@",self.name);
  }];

另外,内存泄漏检测相关详情请看:iOS 内存泄漏排查方法及原因分析

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