美文网首页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