Block

2020-04-10  本文已影响0人  游城十代2dai
  1. Block 的本质?
  1. __block 的作用? 注意什么?
  1. Block 的属性修饰符为什么是 copy?
  1. Block 在修改 NSMutableArray 的时候需不需要添加 __block?
  1. 在 Block 内部使用 __strong 目的是什么?

PS: 由于 Block 的内容不仅仅在编译时期, 还有运行时也有相应的系统管理, 当使用__weak __strong的时候编译源码要用这个命令

$ xcrun -sdk iphoneos -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0 main.m -o main-arm64.cpp

以下是我测试相关内容的代码, 依旧是创建 Command Line Tool , 通过对 Block 的 本质--捕捉对象--类型--内存--循环引用 测试 :

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

void _block_nature(void);
void _block_capture(void);
void _block_type(void);
void _block_memory(void);
void _block_circle_reference(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
//        _block_nature();
//        _block_capture();
//        _block_type();
//        _block_memory();
        _block_circle_reference();
    }
    return 0;
}



struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __Block_byref_age_0 {
 void *__isa;
    /**
     * 这个 forwarding 指针是指向这个结构体的地址, 因为后面再用到 age 的时候都是通过这个指针访问的
     * ___block_nature_block_impl_0 这个里面的 age 可能出现在栈上, 但是整体被 copy 过, 就会在堆上, 栈上的 age 访问堆中的 age 才可以
     */
 struct __Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

struct ___block_nature_block_desc_0 {
  size_t reserved;
  size_t Block_size;
};

struct ___block_nature_block_impl_0 {
  struct __block_impl impl;
  struct ___block_nature_block_desc_0* Desc;
  struct __Block_byref_age_0 *age; // by ref
};


void _block_nature(void) {
    // Block 本质上也是一个 OC 对象, 它的内部有 isa 指针
    // Block 内封装了函数调用以及环境的 OC 对象
    __block int age = 10;
    void (^block)(int, int) = ^(int a, int b){
        NSLog(@"age --- %d", age);
    };
    
    age = 20;
    
    
    block(2, 5);
//  实际调用的结构   ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 2, 5);
//  函数被赋值给 FuncPtr, 函数调用的时候就是在用 FuncPtr

    
    struct ___block_nature_block_impl_0 *block_imp = (__bridge struct ___block_nature_block_impl_0 *)block;
    
    NSLog(@"debug --- 请看 block_struct.png 文件");
}

/*
 * _block_nature 编译后的代码
 
 struct ___block_nature_block_impl_0 {
   struct __block_impl impl;
   struct ___block_nature_block_desc_0* Desc;
   __Block_byref_age_0 *age; // by ref
   ___block_nature_block_impl_0(void *fp, struct ___block_nature_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
   }
 };
 
 * Block 的结构体
 
 struct __block_impl {
   void *isa;
   int Flags;
   int Reserved;
   void *FuncPtr;
 };

 
 */

int gVar1 = 10;
static int gVar2 = 10;

void _block_capture(void) {
    auto int lVar1 = 10;
    static int lVar2 = 10;
    
    void (^block)(void) = ^{
        NSLog(@"gVar1 = %d, gVar2 = %d, lVar1 = %d, lVar2 = %d", gVar1, gVar2, lVar1, lVar2);
    };
    
    gVar1 = 20;
    gVar2 = 20;
    lVar1 = 20;
    lVar2 = 20;
    
    block();
    
}
/**
 * 由下面的源码分析可得:
 * 1. block 对作用域内的变量或进行捕获, 而全局变量不会进行捕获
 * 2. OC 类内部的 block 如果包含 self, 会进行捕获, 因为 OC 的方法会被转换为 C 代码, 默认前两个参数为 self 和 SEL
 * - (void)test() {
 *  void  (^block)(void) = ^{
 *   NSLog(@"%@", self);
 *  }
 * }
 */

/*
 * 全局的变量在编译的时候没有被 block 结构体捕获成为自己的成员变量
 int gVar1 = 10;
 static int gVar2 = 10;

 struct ___block_capture_block_impl_0 {
   struct __block_impl impl;
   struct ___block_capture_block_desc_0* Desc;
   
 // 原本的 auto 修饰的局部变量被 block 捕获成为自己的变量, 但是只有值
   int lVar1;
 // 原本的 static 修饰的全局变量被 block 捕获成为自己的变量, 捕获到的是变量的地址
   int *lVar2;
   ___block_capture_block_impl_0(void *fp, struct ___block_capture_block_desc_0 *desc, int _lVar1, int *_lVar2, int flags=0) : lVar1(_lVar1), lVar2(_lVar2) {
     impl.isa = &_NSConcreteStackBlock;
     impl.Flags = flags;
     impl.FuncPtr = fp;
     Desc = desc;
   }
 };
 
 // block 保存下来的函数方法
 static void ___block_capture_block_func_0(struct ___block_capture_block_impl_0 *__cself) {
   int lVar1 = __cself->lVar1; // bound by copy
   int *lVar2 = __cself->lVar2; // bound by copy

         NSLog((NSString *)&__NSConstantStringImpl__var_folders_z5_7pz8l4jj29d86h_7fg45h7580000gn_T_main_f4f131_mi_2, gVar1, gVar2, lVar1, (*lVar2));
 }
 
 // 我的 _block_capture 实现
 void _block_capture(void) {
     auto int lVar1 = 10;
     static int lVar2 = 10;

     void (*block)(void) = ((void (*)())&___block_capture_block_impl_0((void *)___block_capture_block_func_0, &___block_capture_block_desc_0_DATA, lVar1, &lVar2));

     gVar1 = 20;
     gVar2 = 20;
     lVar1 = 20;
     lVar2 = 20;

     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

 }
 
 */


void _block_type(void) {
    // Block 是 OC 对象, 也有 isa 那就可以调用 class 方法, 有以下三种类型, 但是一切以运行时为主
    void(^block1)(void) = ^ {
        
    };
    
    Class block1Class = [block1 class];
    NSLog(@"block1Class --- %@", block1Class); // __NSGlobalBlock__
    
    int lVar = 10;
    void(^block2)(void) = ^ {
        printf("%d", lVar);
    };
    
    Class block2Class = [block2 class];
    NSLog(@"block2Class --- %@", block2Class); // __NSMallocBlock__

    
    NSLog(@"block3Class --- %@", [^{ printf("%d", lVar); } class]); // __NSStackBlock__
    
    __block int lVar2 = 10;
    void(^block4)(void) = ^ {
        lVar2 = 20;
    };
    
    Class block4Class = [block4 class];
    NSLog(@"block4Class --- %@", block4Class); // __NSMallocBlock__
    // block1 可以看出凡是没有访问了 auto 的变量就是 __NSGlobalBlock__
    // block2 block4 其实也是 StackBlock, 只不过 ARC 内部做了其他操作, 简单说 copy 了就会变成 __NSMallocBlock__
    // 其中全局的被 copy 依旧是全局的, 只有在栈上的才会被放入堆中, 并且对相应的 auto 对象变量进行一次 retain 也就是强引用
    // 这就是为什么对象持有 block 要用 copy
    
    // xcrun -sdk iphoneos -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0 main.m -o main-arm64.cpp
}


/**
 * Block 的内存管理
 *
 */
void _block_memory(void) {
    __block int zwy_age = 10;
    NSObject *zwy_objc = [[NSObject alloc] init];
    NSObject *zwy_objc2 = [[NSObject alloc] init];
    __weak NSObject *weak_objc2 = zwy_objc2;
    void (^zwy_block)(void) = ^ {
        [zwy_objc description];
        [weak_objc2 description];
        zwy_age = 20;
    };
    
    zwy_block();
    
    /**
     * 当 block 被拷贝到堆上时会对其捕获的变量进行一次操作如下:
     * _Block_object_assign 函数会对 __block (MRC 不会强引用 OC 对象) 和 __strong 的变量进行强引用, 对 __weak 的进行弱引用
     * 当 block 被废弃的时候也会对这些变量进行一次 dispose 操作
     */
    /*
     __Block_byref_zwy_age_2 *zwy_age; // __block 修饰的变量
     NSObject *__strong zwy_objc; // block 中的 oc 对象
     NSObject *__weak weak_objc2; // block 中的 __weak oc 对象
     
     static void ___block_memory_block_copy_0(struct ___block_memory_block_impl_0*dst, struct ___block_memory_block_impl_0*src) {
     _Block_object_assign((void*)&dst->zwy_objc, (void*)src->zwy_objc, 3);
     _Block_object_assign((void*)&dst->weak_objc2, (void*)src->weak_objc2, 3);
     _Block_object_assign((void*)&dst->zwy_age, (void*)src->zwy_age, 8);
     }
    */
}

typedef void(^Block)(void);

@interface Person : NSObject

@property (nonatomic, copy) Block block;
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
- (void)dealloc {
    NSLog(@"%s", __func__);
}
@end

void _block_circle_reference(void) {
    { // 产生了循环引用, 即 per 强引用 block, block 强引用 per, 所以出了这个作用域 per 不会 dealloc
        Person *per = [[Person alloc] init];
        per.age = 10;
        per.block = ^{
            NSLog(@"%d", per.age);
        };
    }
    
    { // 解决办法 __weak __unsafe_unretained
        Person *per = [[Person alloc] init];
        per.age = 10;
        __weak Person *weak_per = per;
        per.block = ^{
            NSLog(@"%d", weak_per.age);
        };
    }
    
    { // 解决办法
        __block Person *per = [[Person alloc] init];
        per.age = 10;
        per.block = ^{
            NSLog(@"%d", per.age);
            per = nil; // 必须 nil
        };
        per.block(); // 必须调用
    }
    
    sleep(1);
}
上一篇下一篇

猜你喜欢

热点阅读