block 各个修饰符的说明

一.ARC下用块(block)的循环引用问题样例探究:

当页面跳转时,看界面是否有循环引用,如果控制器不走dealloc方法,说明有循环引用。

情况一:

- (void)case1 {

    NSLog(@"case 1 Click");

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        self.name = @"case 1";

    });

}

情况一:执行了dealloc,不泄露,此情况虽然是block,但未形成保留环block -> self

情况二:

- (void)case2

 {

    __weak typeof(self) weakSelf = self;

    [self.teacher requestData:^(NSData *data) {

        typeof(weakSelf) strongSelf = weakSelf;

       strongSelf.name = @"case 2";

    }];

}

情况二:执行了dealloc,不泄露,此情况就是内存泄漏后的一般处理了 self ->teacher ->block ->strongSelf,后面那个strongSelf和原来的self并没有直接关系,因为strongSelf是通过weakSelf得来的,而weakSelf又没有强引用原来的self。

情况三:

- (void)case3 {

    NSLog(@"case 3 Click");

    [self.teacher requestData:^(NSData *data) {

        self.name = @"case 3";

    }];

}

情况三:未执行dealloc,内存泄漏,此情况就是最典型的循环引用了,形成保留环无法释放,self ->teacher ->block ->self

情况四:

- (void)case4 {

    NSLog(@"case 4 Click");

    [self.teacher requestData:^(NSData *data) {

        self.name = @"case 4";

        self.teacher = nil;

    }];

}

情况四:执行了dealloc,不泄露,虽然也是保留环,但通过最后一句,使self不再强引用teacher,打破了保留环

情况五:

- (void)case5 {

    NSLog(@"case 5 Click");

    Teacher *t = [[Teacher alloc] init];

    [t requestData:^(NSData *data) {

        self.name = @"case 5";

    }];

}

情况五:执行了dealloc,不泄露,未形成保留环 t ->block ->self

情况六:

- (void)case6 {

    NSLog(@"case 6 Click");

    [self.teacher callCase6BlackEvent];

    self.teacher.case6Block = ^(NSData *data) {

        self.name = @"case 6";

        //下面两句代码任选其一

        self.teacher = nil;

//        self.teacher.case6Block = nil;

    };

}

情况六:执行了dealloc,不泄露,最后两句代码任选其一即可防止内存泄漏,self.teacher 或者 case6Block 置为空都可以打破 retain cycle。

PS: 虽然情况四、情况六的写法都可以防止内存泄漏,不过为了统一,个人建议最好还是按照普通写法即情况二的写法。

二.__block和__weak的区别:

   1.__block不管是在ARC还是在MRC模式下,都可以使用,还可以修饰基本数据类型

  2.__weak只能使用在ARC模式下,并且只能修饰对象(NSString),不可以修饰基本数据类型

 3.__weak不可以改变变量值,__block可以改变变量值。

 4.__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。

三.__block修饰变量在block内部改变其变量值的原理

Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

eg:

__blockinta =0;

NSLog(@"定义前:%p", &a);//栈区

void(^foo)(void) = ^{

 a =1;

NSLog(@"block内部:%p", &a);//堆区

};

NSLog(@"定义后:%p", &a);//堆区

foo();

2016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679]定义前:0x16fda86f8

2016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679]定义后:0x155b22fc8

2016-05-1702:03:33.559LeanCloudChatKit-iOS[1505:713679]block内部: 0x155b22fc8

“定义后”和“block内部”两者的内存地址是一样的,我们都知道 block 内部的变量会被 copy 到堆区,“block内部”打印的是堆地址,因而也就可以知道,“定义后”打印的也是堆的地址。

那么如何证明“block内部”打印的是堆地址?

把三个16进制的内存地址转成10进制就是:

定义后前:6171559672

block内部:5732708296

定义后后:5732708296

中间相差438851376个字节,也就是 418.5M 的空间,因为堆地址要小于栈地址,又因为iOS中一个进程的栈区内存只有1M,Mac也只有8M,显然a已经是在堆区了。

这也证实了:a 在定义前是栈区,但只要进入了 block 区域,就变成了堆区。这才是__block关键字的真正作用。

理解到这是因为堆栈地址的变更,而非所谓的“写操作生效”,这一点至关重要,要不然你如何解释下面这个现象:

你可能感兴趣的:(block 各个修饰符的说明)