美文网首页iOS 开发 Objective-C
iOS 底层 day10 block 的 __block 和 循

iOS 底层 day10 block 的 __block 和 循

作者: 望穿秋水小作坊 | 来源:发表于2020-09-01 16:19 被阅读0次

    一、__block 的本质

    1. 观察如下代码,会报错吗?如果想在 block 内修改 age 的值,有哪三种方式?
    typedef void (^SPBlock)(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            int age = 10;
            SPBlock block = ^ {
                age = 20;
                NSLog(@"%d",age);
            };
            block();
            NSLog(@"%d",age);
        }
        return 0;
    }
    
    • 会报错,这种情况下 block 内部是对 age 的值捕获,无法修改外部的 age
    • 将 age 变成静态局部变量
    • 将 age 变成全局变量
    • 将 age 加上 __block 修饰
    2. __block 修饰符是什么?
    • __block 可以用于解决 block 内部无法修改 auto 变量的问题
    • __block 不能修饰全局变量、静态变量(static)
    • 编译器会将 __block变量包装成一个对象
    3. 下面代码中的 array没有使用__block` 修饰,能够正常使用吗?
    typedef void (^SPBlock)(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSMutableArray *array = [[NSMutableArray alloc] init];
            SPBlock block = ^ {
                [array addObject:@"1"];
            };
            block();
            NSLog(@"%@",array);
        }
        return 0;
    }
    
    • 可以正常运行,不需要 __block 修饰
    • block 中的代码块,只是使用 array ,并没有修改 array 的值,所以不需要使用 __block 修饰
    • 如果 block 代码块中加入 array = nil;,去修改 array 变量的值,那么就会编译报错
    4. __block 修饰 auto 的 非OC 对象变量,本质做了什么事情?
    修饰一般变量代码逻辑图
    • 我们发现 __block 修饰的 age 变量被编译器包装成了 __Block_byref_age_0 对象

    • __main_block_desc_0 中有 copydispose 说明对 "age" 对象做了内存管理

    • 有一个有趣的地方,(age->__forwarding->age) = 20; 这句是拿到 age 对象的 forwarding 然后再拿到 age,从上面的代码可得知 forwarding 其实就是指向 &age。那为什么不直接拿到 age 而要绕一圈呢?

    • 这是因为一开始 age 被包装的这个对象,没有被 block 捕获时,是放在栈区的。经过 block 捕获并 copy 后,age对象 也会被拷贝堆区。这时候堆区和栈区都有 age 对象,我们无法确定我们取得 age 对象 是堆区的还是栈区的。然后我们巧妙的经过 forwarding绕一圈,保证无论堆区还是的 forwarding 都指向 堆区的 age 对象。这样我们才能准确无误的拿到我们想要的age 对象

    forwarding图解
    5. 在问题 4 中,那么多 age,我们如何确定拿到的 age 是哪个 age 呢?
    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    
    struct __Block_byref_age_0 {
     void *__isa;  // 8字节
     struct __Block_byref_age_0 *__forwarding;// 8字节
     int __flags; // 4字节
     int __size; // 4字节
     int age; // 这个 age 相对于__isa的地址需要加24 字节
    };
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    };
    
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      struct __Block_byref_age_0 *age; // by ref
    };
    
    
    typedef void (^SPBlock)(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block int age = 10;
            SPBlock block = ^ {
                age = 20;
            };
            struct __main_block_impl_0 lsBlock =*(__bridge struct __main_block_impl_0*)block;
            NSLog(@"断点处");
        }
        return 0;
    }
    
    • 思路,我们将 block 的结构体拿出来,然后将我们 OC 中的 block 转换成 lsBlock
    • 然后从结构体 __Block_byref_age_0 计算出 age 相对于 __isa 的地址,应该是 24 字节,也就是 0x18
      -然后我们在控制台,打印如下信息:
    (lldb) p/x lsBlock->age
    (__Block_byref_age_0 *) $7 = 0x0000000100525260
      Fix-it applied, fixed expression was: 
        lsBlock.age
    (lldb) p/x &age
    (int *) $8 = 0x0000000100525278
    (lldb) p/x 0x0000000100525260 +  0x18
    (long) $9 = 0x0000000100525278
    
    • 借此,我们可以确定,我们在代码中的 age地址值 就是 __Block_byref_age_0 结构体中的 age地址值
    6. __block 修饰 oc对象,比较复杂,暂时先不讨论,后面有需要再补充。

    二、block 相关的循环引用

    1. 下面代码,会导致循环引用吗?
    #import "Person.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *person = [[Person alloc] init];
            person.age = 20;
            person.block = ^{
                NSLog(@"Age is %d", person.age);
            };
            
            person.block();
        }
        return 0;
    }
    
    • 会导致循环引用
    • 因为 person.block 捕获了 person 对象,并且会强应用 person 对象
    • person.block 又被 person 强引用
    • 所以 personperson.block 之间形成了循环引用
    2. 问题 1 你有哪几种方案解决循环引用?
    • 方案一使用__weak解决: __weak typeof(person) weakPerson = person;
    • 方案二使用__unsafe_unretained解决: __unsafe_unretained typeof(person) weakPerson = person;
    • 方案三使用__block比较麻烦,性能比较低,不推荐,但是能解决:
    #import "Person.h"
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            __block Person *person = [[Person alloc] init];
            person.age = 20;
            person.block = ^{
                NSLog(@"Age is %d", person.age);
                person = nil;
            };
            person.block();
        }
        return 0;
    }
    
    • 方案三需要注意:①必须调用 person.block(); ②必须手动 person = nil;
    3. __weak__unsafe_unretained 都能解决循环引用,那么有什么区别?
    • __weak :不会产生强引用,安全,指向的对象销毁时,会自动让指针置为 nil
    • __unsafe_unretained:不会产生强引用,不安全,执行的对象销毁时,指针存储的地址值不变
    4. 下面的这种写法,经常见到,__strong 有什么作用?
    - (void)test {
        __weak typeof(self) weakSelf = self;
        self.block = ^{
            __strong typeof(self) strongSelf = weakSelf;
            NSLog(@"Age is %d", strongSelf->_age);
        };
    }
    
    • 这样写主要目的是为了,让 test 函数调用期间保证 self 不会被突然释放,保证了代码的安全性。
    • 其次,编译器不支持 weakSelf->_age 这样访问

    相关文章

      网友评论

        本文标题:iOS 底层 day10 block 的 __block 和 循

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