iOS-block(一)-初探

基本概念

什么是block?《Objective-C高级编程》这本书里是这样定义的:

带有自动变量(局部变量)的匿名函数。顾名思义,匿名函数就是没有名称的函数。也被称为闭包(closure)或者Anonymous function

我们可以理解为block就是一个没有名称的函数。定义block的方式和定义函数的方式是相似的,而block还可以作为参数使用。当block被调用其块内的代码才会被执行。

定义

根据block的定义,我们可以知道,block的主要组成是返回值和参数。其表达式如下:

^+返回值类型+参数列表+表达式

按照是否存在返回值和参数,我们可以将block的定义分为以下几种:

    1. 无返回值+无参数
void(^myBlock)(void) = ^void(void) {
    
};
// 可以简写成:
void(^myBlock)(void) = ^ {
    
};
    1. 无返回值+有参数
void(^myBlock)(int a) = ^void(int num) {
    
};
// 可以简写成:
void(^myBlock)(int a) = ^(int num) {
    
};
    1. 有返回值+无参数
int(^myBlock)(void) = ^int(void) {
    return 10;
};
// 可以简写成:
int(^myBlock)(void) = ^int {
    return 10;
};
    1. 有返回值+有参数
int(^myBlock)(int a, int b) = ^int(int a, int b) {
    return a + b;
};

当参数和返回值为void可以忽略。

在实际开发中,我们经常使用typedef来定义block:

typedef int(^myBlock)(int a, int b);
myBlock mb = ^int(int a, int b) {
        return a + b;
};
NSLog(@"==myBlock==%d==", mb(1, 2));

block与外界变量的关联

下面我们来看一个例子:

typedef int(^myBlock)(int a, int b);

int d = 10;
myBlock mb = ^int(int a, int b) {
    return a + b + d;
};
d = 5;
NSLog(@"==myBlock==%d==", mb(1, 2));

我们在mb内部使用外部变量int d,随后又给d重新赋值,此时调用mb(1, 2)block内部d的取值是5还是10呢?运行程序,控制台输出13。此例子说明了block具有截获外部变量的能力,而且截获之后,变量在block的值是固定的,不会随着外部的改变而改变。

我们接着在其外部定义一个变量e:

int e = 0;

当我们在block内部对e进行赋值操作的时候,编译器会提示错误:

Variable is not assignable (missing __block type specifier)

意为变量不可以被分配使用,是因为缺少__block修饰符。那么我们为上述变量de的声明加上__block修饰符,我们立刻看到编辑器没有错误了。运行程序,此时控制台输出8,这说明此时block中的取值不再是10而是5了。它不但被我们分配使用了,可以随着外界的改变而改变了,甚至我们可以随意的在block内部修改d的值了,这到底是为什么呢?

从表面上看,没有被__block修饰的变量,我们在block内部使用的时候,只是截获其当时的值,所以其不会再改变,而被__block修饰的变量,我们截获该变量的地址,所以它不论怎们改变,我们都能截获到。

block分类

iOS中,我们依据内存情况将block分为6中

  • _NSConcreteGlobalBlock:全局block,不访问外界变量(包括堆中和栈中的变量)
void (^block)(void) = ^{
    NSLog(@"==block==");
};
  • _NSConcreteMallocBlock:堆block,存在于堆内存中,是带一个引用计数的对象,需要自己进行内存管理。变量本身在栈中,因为block能够自动截获变量,为了访问到变量,会将变量从堆内存中copy成栈内存中。
int a = 10;
void (^block)(void) = ^{
    NSLog(@"==a==%d==", a);
};
  • _NSConcreteStackBlock:栈block,存于栈内存中,超出其作用域则马上进行销毁。作为方法或者函数的参数的时候不会被copy到堆上。
NSLog(@"%@",^{
    NSLog(@"==block==");
});
  • _NSConcreteAutoBlock
  • _NSConcreteFinalizingBlock
  • _NSConcreteWeakBlockVariable

前3种在日常开发中是很常见的,后3种是系统级别的block,一般比较少用。

我们知道程序在编译的时候内存的分布有:堆区(heap)、栈区(stack)、文字常量区、程序代码区、全局区/静态区。

image

这张图也就解释了为什么我们使用block作为属性的时候修饰符都是用copy_NSConcreteGlobalBlock是全局block,它只能存在于一个函数的内部,并不能作为属性;而_NSConcreteStackBlock是栈block,存于栈内存中,超出其作用域则马上进行销毁,当我们使用copy就会将其copy到堆内存中,这样会延长block的生命周期,防止出现异常;而_NSConcreteMallocBlock本身就是堆block,使用copy也不会对其有影响。

block的使用

block既然是一个匿名函数,那么它就可以作为函数使用,当然,它也可以作为作为函数的参数、或者函数的返回值调用。

上面的例子大多数都是block作为函数使用,我们也就不再赘述了。下面我们就看看其他两种情况:

首先我们先给block设置一个别名,这样看着简单明了一些。

typedef int(^SumBlock)(int a, int b);
  1. block作为函数的参数
- (void)blockAsParameter:(SumBlock)mb {
    NSLog(@"==blockAsParameter实现==%@==", mb);
    mb(1, 2);
}

可以调用一下,然后运行程序:

[self blockAsParameter:^int(int a, int b) {
        NSLog(@"==blockAsParameter调用==%d==", a + b);
        return a + b;
}];

// 控制台输出
==blockAsParameter实现==<__NSGlobalBlock__: 0x106a4d090>==
==blockAsParameter调用==3==
  1. block作为函数的返回值
- (SumBlock)blockAsReturns {
    NSLog(@"==blockAsReturns==");
    return ^int(int a, int b) {
        return a + b;
    };
}

SumBlock mb2 = [self blockAsReturns];
NSLog(@"==blockAsReturns==%d==", mb2(1, 2));

==blockAsReturns==
==blockAsReturns==3==

其实说的通俗点,block就是封装一段代码块,这段代码块可以像变量一样被使用。

block的循环引用问题

我们在日常使用block的时候一定要注意一个问题,那就是循环引用。因为block经常是作为变量被self持有,或者是block的持有者被self作为变量持有,然而当我们在block内部使用self的时候就会造成循环引用。我们来看一个例子:

@property (nonatomic, assign) int num1;
@property (nonatomic, assign) int num2;
@property (nonatomic, copy) SumBlock propertyBlock;

self.num1 = 1;
self.num2 = 2;
self.propertyBlock = ^int(int a, int b) {
    NSLog(@"==a==%d==b==%d==", self.num1, self.num2);
    return a + b;
};

编译器会直接提示:

Capturing 'self' strongly in this block is likely to lead to a retain cycle

意为在block中强引用self可能会造成循环引用。上述例子中,num1num2propertyBlock作为属性被self持有,而我们又在block里使用了num1num2,这就相当propertyBlock又持有了self,这就造成了循环引用。强持有会对引用计数进行处理,循环引用会导致对象无法被释放,就影响了对象的引用计数,会造成内存问题。那么这个问题如何解决呢?

__weak typeof(self) weakSelf = self;
self.propertyBlock = ^int(int a, int b) {
    NSLog(@"==a==%d==b==%d==", weakSelf.num1, weakSelf.num2);
    return a + b;
};

这样就解决了循环引用问题。那么weakSelf是如何解决循环引用的呢?由于现在引入了weakSelf,持有的情况就变成了weakSelf持有selfself持有block,而block又持有weakSelf,但是需要注意的是weakSelf持有self是弱引用,只是一个指向,引用计数并没有发生改变,所以就打破了循环引用。

但是使用__weak需要注意一点,就是对象的释放时间。

self.propertyBlock = ^int(int a, int b) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"==a==%d==b==%d==", weakSelf.num1, weakSelf.num2);
    });
}

如果我们进入页面之后,立即退出页面,控制台输出:

==a==0==b==0==

这说明,dealloc之后持有的对象却是已经被释放了。如果我们想等到打印结果输出之后,再进行dealloc该怎么处理。

  1. 使用__strong

我们可以在block里面使用__strong再将weakSelf转化为强引用即可。此时虽然strongSelf为强引用,但是只是在block作用域内的,当block内任务执行完毕,自然也会释放,和外界并没有任何关系。

__strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"==num==%d==", strongSelf.num);
});
  1. self作为参数传入block

blcok具有截获变量的能力,当参数传入blockblockcopy一份使用,此时copy出来的变量和原来的就没有关系。这样也打破了循环引用。

typedef int(^MinusBlock)(int a, int b, ViewController *vController);

self.num = 10;
self.mb = ^int(int a, int b, ViewController *vController) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"==num==%d==", vController.num);
    });
    return a - b;
};
self.mb(2, 1, self);

block的基础就介绍到这里,下一章我们再来看看block的底层分析。

参考文献:
Objective-C高级编程 iOSOS X多线程和内存管理》

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

推荐阅读更多精彩内容