Block

一、block定义

  1. 概念
    block是将函数及其上下文封装起来的对象。
    block也是一个指针,保存的是一段代码块在内存中的空间 (栈内存)
//blcok定义:后者return_type可省,参数为void参数可省略
return_type (^blockName)(var_type) = ^return_type (var_type varName) {
    //  statements
};
blockName(var);

//利用typedef简化Block的声明:
typedef return_type (^BlockTypeName)(var_type);
  1. block的使用场景
  • 把block保存到对象中,恰当时机的时候才去调用
- (void)block1
{
    Person *p = [[Person alloc] init];
    void(^block)() = ^() {
        NSLog(@"执行对象中block");
    };
    p.operation = ^(){
        NSLog(@"执行对象中block");
    };
    p.operation = block;
    _p = p;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _p.operation();
}

// block:ARC使用strong,非ARC使用copy
// block类型:void(^)()
@property (nonatomic, strong) void(^operation)();
  • 把block当做方法的参数使用,外界不调用,都是方法内部去调用,Block实现交给外界决定.
- (void)block2
{
    Person *p = [[Person alloc] init];
    // 传入block给参数的Block赋值
    [p eat:^{
        NSLog(@"吃东西");
    }];
}
//Person中eat方法
- (void)eat:(void (^)())block
{
    block();
}
  • 把block当做方法的返回值,目的就是为了代替方法.block交给内部实现,外界不需要知道Block怎么实现,只管调用
- (void (^)(int))run
{
    return ^(int meter){
        NSLog(@"跑了%d米",meter);
    };
}
- (void)block3
{
    Person *p = [[Person alloc] init];
    p.run(2);
   // void(^run)() = p.run;
   //run();
}

二、block捕获变量

1. block使用外部变量,会将外界变量拷贝一份到堆内存(在给block块分配内存空间的时候),调用block之前修改变量,不影响block内的该变量取值。该block是不允许更改该变量的
- (void)test01
{
    int a = 10;
    void(^block)(int x) = ^(int x){
        NSLog(@"== %d", a);//10, a的地址和外面a不同
       //variable is not assignable (missing __block type specifier )不允许修改a的值
    };
    a = 20;
    block(a);
}
2. __block的作用
  • 基本类型
    局部变量 : 在声明前加__block,可以直接读写该变量,是地址传递,会影响外界值。会否拷贝该值分为ARC、MRC二种情况
    静态变量、全局变量:不会对原来的值进行copy,直接读写,地址不变
- (void)test01
{
    //int sum = 5;  //( variable is not assignable (missing __block type specifier ))
    __block int sum = 5;
    NSLog(@"1---%p --- %d", &sum, sum);
    sum = 10;
    void (^sumBlock) (int m, int n) = ^(int m, int n) {
        sum = m + n;
        NSLog(@"2---%p --- %d", &sum, sum);
    };
    sum = 15;
    sumBlock(1, 2);
    NSLog(@"3---%p --- %d", &sum, sum);

    __block NSString *name = @"小明";
    NSLog(@"1---%@---%p", name, &name);
    void (^stringBlock) (void) = ^(void) {
        name = @"小丽";
        NSLog(@"2---%@---%p", name, &name);
    };
    name = @"ls";
    stringBlock();
    NSLog(@"3---%@---%p", name, &name);
}

//ARC 
 //1---0x7ffee12688a8 --- 5
// 2---0x60000118ced8 --- 3
// 3---0x60000118ced8 --- 3
// 1---小明---0x7ffee1268848
// 2---小丽---0x600001f8da68
// 3---小丽---0x600001f8da68

//MRC
//1---0x7ffee0d888a8 --- 5
//2---0x7ffee0d888a8 --- 3
//3---0x7ffee0d888a8 --- 3
//1---小明---0x7ffee0d88848
//2---小丽---0x7ffee0d88848
//3---小丽---0x7ffee0d88848

如果想在block内修改某局部变量需加__block. MRC 环境下block在使用过程中不会对原来值进行copy,可以直接修改该变量 ,ARC环境下会对原值进行copy,内存地址发生变化。
block可以直接修改全局和静态变量 ,不会copy该变量的值

  • 指针类型的变量
- (void)test02
{
    Person *people = nil;
    people = [[Person alloc] init];
    people.name = @"zhangsan";
    
    NSLog(@"1---%@---%p--%@", people, &people, people.name);
    
    void (^peopleBlock) (void) = ^(void) {
        NSLog(@"2---%@---%p--%@", people, &people, people.name);
        people.name = @"wangwu";
        /*
         people = [[Person alloc] init];
         people.name = @"zhaoliu";
         */
    };
    people.name = @"lisi";
    peopleBlock();
    NSLog(@"3---%@---%p--%@", people, &people, people.name);
}

ARC、MRC不使用__block
//1---<Person: 0x6000021ab3b0>---0x7ffeef9c28a8--zhangsan
//2---<Person: 0x6000021ab3b0>---0x600002de3500--lisi
//3---<Person: 0x6000021ab3b0>---0x7ffeef9c28a8--wangwu

MRC使用__block
//1---<Person: 0x6000027ec760>---0x7ffeeec238a8--zhangsan
//2---<Person: 0x6000027ec760>---0x7ffeeec238a8--lisi
//3---<Person: 0x6000027ec760>---0x7ffeeec238a8--wangwu

ARC使用__block
//1---<Person: 0x6000034d8390>---0x7ffee03568a8--zhangsan
//2---<Person: 0x6000034d8390>---0x600003881018--lisi
//3---<Person: 0x6000034d8390>---0x600003881018--wangwu

不加__block:
MRC 和 ARC block内都是对(原来指针的copy),也就是有两个不同的指针,指向同一个对象。在block内可以更改对象的属性值,但是不可以更改对象
使用__block:
MRC环境block中不会对原来的指针进行copy,所以可以更改属性,也可以更改对象本身 。
ARC环境则是新增指针地址并赋值给原指针(对原对象的copy?内存地址也发生变化?)。
指针类型全局和静态变量block内可以直接修改,不会copy值和指针。

三、关键字__weak, __strong,copy

  • __weak
- (void)test03
{
    Person *p = [[Person alloc]init];
    p.name = @"myObject";
    
    NSLog(@"1---%@---%p--%@", p, &p,p.name);
    __weak  Person *weakObj = p;
    NSLog(@"2---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    
    void(^testBlock)(void) = ^(){
        NSLog(@"3---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    };
    
    testBlock();
    p = nil; // 这边值nil 用来判断block是否复制了对象
    testBlock();
}

不使用__weak(另MRC 是没有__weak关键字的)
//1---<Person: 0x600000c20d50>---0x7ffee5e148a8--myObject
//2---<Person: 0x600000c20d50>---0x7ffee5e148a0--myObject
//3---<Person: 0x600000c20d50>---0x60000002e240--myObject
//3---<Person: 0x600000c20d50>---0x60000002e240--myObject

使用__weak
//1---<Person: 0x6000037f8d90>---0x7ffeee15d8a8--myObject
//2---<Person: 0x6000037f8d90>---0x7ffeee15d8a0--myObject
//3---<Person: 0x6000037f8d90>---0x600003becb30--myObject
//3---(null)---0x600003becb30--(null)

不使用 __weak, p = nil 后block块内打印出的对象仍不为空,说明block中新增了强指针引用
使用 __weak p = nil 后person对象为nil ,说明block内新增了弱指针引用,对象释放后 weakObj 也不在持有, 并会被置nil 防止野指针报错。

//Capturing 'self' strongly in this block is likely to lead to a retain cycle
- (void)test04
{
     __weak typeof(self)weakSelf = self;
    self.block = ^(NSString *name){
          NSLog(@"arr:%@", weakSelf);
//          [self weakTest];
        //  p.name = @"haha";
    };
    self.block(@"123");
}

block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题 。
一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,
简单说就是self.block = ^(Type var){
[self dosomething];
或者 self.otherVar = XXX;
或者 _otherVar = ...
};
block的这种循环引用会被编译器捕捉到并及时提醒
解决方法 __weak typeof(self)weakSelf = self;

  • __strong
    __weak 可能产生的问题: weakSelf 指针是没有对象持有权的,那么外部对象被提前释放了怎么办?block内部的执行岂不是会出错 ?这个问题又当如何解决呢? 神奇的 strong 关键字来了, 先看代码
- (void)test04
{
    Person* p = [[Person alloc]init];
    p.name = @"myObject";
    NSLog(@"1---%@---%p--%@", p, &p,p.name);
    
    __weak Person *weakObj = p;
    NSLog(@"2---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    
    void(^testBlock)(void) = ^(){
        __strong Person *strongObj = weakObj;
        NSLog(@"3---%@---%p--%@", strongObj, &strongObj,strongObj.name);
        NSLog(@"w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    };
    testBlock();
    p = nil;
    testBlock();
}

//1---<Person: 0x600001002fb0>---0x7ffeee9d28a8--myObject
//2---<Person: 0x600001002fb0>---0x7ffeee9d28a0--myObject
//3---<Person: 0x600001002fb0>---0x7ffeee9d27c8--myObject
//w---<Person: 0x600001002fb0>---0x600001c48260--myObject
//3---(null)---0x7ffeee9d27c8--(null)
//w---(null)---0x600001c48260--(null)

发现 __strong 修饰的对象仍被置nil 了 怎么回事呢 ?? 接着看.....

- (void)test05
{
    Person* p = [[Person alloc]init];
    p.name = @"myObject";
    NSLog(@"0p---%@---%p--%@", p, &p, p.name);
    __weak Person *weakObj = p;
     NSLog(@"0w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        __strong Person *strongObj = weakObj;
        
        NSLog(@"0s---%@---%p--%@", strongObj, &strongObj,strongObj.name); //先打印这行
        
        sleep(3); // 睡眠三秒确保 p 被置 nil 后执行接下来的代码
        
        NSLog(@"2w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
        NSLog(@"2s---%@---%p--%@", strongObj, &strongObj,strongObj.name);
        
    });
    
    sleep(1); //睡眠1秒让异步线程block块执行
    
    p = nil;  //执行过程中将 p 对象置 nil
    NSLog(@"1p---%@---%p--%@", p, &p, p.name);
    NSLog(@"1w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    
    sleep(4); //异步线程结束后 再打印出 person 对象
    
    NSLog(@"3w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
}

//0p---<Person: 0x6000031ac320>---0x7ffee1aa68a8--myObject
//0w---<Person: 0x6000031ac320>---0x7ffee1aa68a0--myObject
//0s---<Person: 0x6000031ac320>---0x70000f8fce08--myObject
//1p---(null)---0x7ffee1aa68a8--(null)
//1w---<Person: 0x6000031ac320>---0x7ffee1aa68a0--myObject
//2w---<Person: 0x6000031ac320>---0x600003de98e0--myObject
//2s---<Person: 0x6000031ac320>---0x70000f8fce08--myObject
//3w---(null)---0x7ffee1aa68a0--(null)

p = nil 后由__strong 修饰的对象仍然存在。在block执行过程中,如果对象用 __strong 修饰 block内部依然会继续强引用它 。__weak修饰的只要有强指针指向,会一直存在。上面的例子是因为下面的代码是后续执行的,所以打印出结果为 nil 。
block 内部的 __strong 会在执行期间进行强引用操作,保证在 block 内部 strongObj 始终是可用的。既避免了循环引用的问题,又可以在 block 内部持有该变量

我们平时在使用时,常常先判断 strongObj 是否为空,然后再执行后续代码,如下方式

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       __strong Person *strongObj = weakObj;
       if (strongObj) { 
            //
       }
});
  • copy:block变量定义时为什么用copy关键字
    默认情况下,block是存档在栈中,可能被随时回收,故需要copy操作。这也就是我们在定义block的时候用得时copy (arc 下也可以用strong), 而不是weak等

block默认存储于栈区,访问外界对象,不会对对象retain。copy会转移至堆,访问外界对象,会对对象retain,使用__block修饰,则不会对对象retain。
使用copy修饰block是将block转移至堆,而不是copy一份,使用copy保住外界对象,避免使用时对象已释放。copy操作后需要在对象的dealloc下对block进行block_release.

四、三种类型block

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

推荐阅读更多精彩内容