Objective-C 块(Block)的使用与理解

简书上的所有内容都可以在我的个人博客上找到(打个广告😅)


块(Block)是在 iOS 开发中很常用的一个工具,它可以使我们代码的业务逻辑更加的紧凑。本文会分两部分来讲块,第一部分是块的基础知识,第二部分是对块的本质的一些理解。

块的基础知识


块的声明

块的声明类似于函数指针,只不过把 * 换成了 ^ 。声明时最前面是返回类型,中间是块名,最后是参数类型。

// returnType (^blockName) (parameters)
int (^someBlock) (int, int);

块的实现

// ^returnType(parameters){/*code*/};
^int(int a, int b) {
    return a+b;
};

其中返回值是可以省略的,它可以自动判断,如果参数也是空的话我们可以简化成这样:

^{
    return 100;
};

块的使用

块的使用就像使用 C 函数一样

someBlock(1,2);

用 typedef 来简化块的声明

typedef int (^CYAddBlock) (int, int);
CYAddBlock addBlock = ^(int a, int b) {
    return a+b;
};
addBlock(1,2);

块能捕获在它声明范围里的所有变量

但是不能再块中修改捕获到的变量,如果我们尝试修改编译器会报错,无法通过编译。

int count = 0;
int (^someBlock) () = ^{
    return count;
};
int result = someBlock();   // result = 0

用 __block 修饰变量,可以在块中修改变量的值

__block int count = 0;
int (^someBlock) () = ^{
    return ++count;
};
int result = someBlock();   // result = 1

其实块在捕获变量时只是拷贝了一份变量的值,而用 __block 修饰后,拷贝的是变量的地址,所以我们就可以在块中修改变量了。这就和 C 语言中函数的参数是类似的道理。

小心产生循环引用

在一个类中,块能直接访问并修改实例变量的值,但是一定要注意不管是通过 self 来访问属性还是直接通过 _instanceVariable 来访问都会捕获 self。因为在直接访问 _instanceVariable 等效于这样:

self->_instanceVariable;

而一旦捕获了self, 我们就一定要注意循环引用导致的内存泄露了。我们在 CYClass 中声明了一个块和一个属性:

typedef void (^CYBlock) ();

@interface CYClass : NSObject
@property (nonatomic, copy)NSString *aString;
@property (nonatomic, copy)CYBlock aBlock;
@end

然后重写它的 init 和 dealloc 方法:

@implementation CYClass
- (instancetype)init
{
    if (self = [super init]) {
        _aString = @"Hello Cyrus";
        _aBlock = ^{
            NSLog(@"%@", _aString);
        };
    }
    return self;
}

- (void)dealloc {
    NSLog(@"CYClass deinit");
}
@end

然后我们实例化一个对象,紧接着就把它设为 nil 会发现 dealloc 方法并没有被调用,也就是说发生了内存泄露。

CYClass *c = [CYClass new];
c = nil;

就像这样:

我们可以通过 __weak 来打破这个循环,我们修改一下初始化方法:

- (instancetype)init
{
    if (self = [super init]) {
        _aString = @"Hello Cyrus";
        __weak typeof(self) weakSelf = self;
        _aBlock = ^{
            __strong typeof(self) strongSelf = weakSelf;
            NSLog(@"%@", strongSelf.aString);
        };
    }
    return self;
}

在执行之前的代码就会发现这个对象成功的销毁了。

2016-03-12 16:30:24.995 BlockExample[4777:111670] CYClass deinit

块的本质


块的结构

我们可以在这里找到 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 指针, 我们应该就能猜到 Block 也是一个对象。然后再看第四个变量是一个叫 invoke 的函数指针,它指向的就是 Block 具体实现的函数了,这个函数至少要接受一个 void* 类型的参数,这个参数就是块本身。再下面的 descriptor 指向的就是 Block_descriptor 结构体,里面就是一些 Block 的信息。而在这些变量下面存放的就是 Block 捕获的那些变量

块的类型

在 OC 中一共有3种块,分别是:

  • 全局块,不会访问任何外部变量
  • 栈块,保存在栈中, 当函数退出后就会被销毁,通过copy可以复制到堆上
  • 堆块,保存在堆中

我们在 MRC 的环境下运行下面的代码:

void (^globalBlock)()  = ^{ NSLog(@"global block"); };
NSLog(@"%@", globalBlock);
        
NSString *str1 = @"stack block";
void (^stackBlcok)() = ^{ NSLog(@"%@", str1); };
NSLog(@"%@", stackBlcok);
    
NSString *str2 = @"malloc block";
void (^mallockBlock)() = [^{ NSLog(@"%@", str2); } copy];
NSLog(@"%@", mallockBlock);

打印结果:

2016-03-13 16:23:50.676 BlockExample[1760:28902] <__NSGlobalBlock__: 0x1000042b0>
2016-03-13 16:23:50.677 BlockExample[1760:28902] <__NSStackBlock__: 0x7fff5fbff790>
2016-03-13 16:23:50.677 BlockExample[1760:28902] <__NSMallocBlock__: 0x100303fa0>

结果和我们预期的结果是一样的。我们在换成 ARC 的环境下运行一次:

2016-03-13 16:26:05.767 BlockExample[1805:29822] <__NSGlobalBlock__: 0x1000052f0>
2016-03-13 16:26:05.768 BlockExample[1805:29822] <__NSMallocBlock__: 0x100400340>
2016-03-13 16:26:05.769 BlockExample[1805:29822] <__NSMallocBlock__: 0x1004001b0>

我们发现原来的栈块也变成了堆块。这是因为在把一个快赋值给一个strong对象时 ARC 会自动帮我们执行一次copy。如果我们直接这样,那么还是会打印出 __NSStackBlock :

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

推荐阅读更多精彩内容