美文网首页
block 各个修饰符的说明

block 各个修饰符的说明

作者: 撸码者 | 来源:发表于2018-01-03 14:33 被阅读240次

    一.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 各个修饰符的说明

          本文链接:https://www.haomeiwen.com/subject/bazcnxtx.html