OC中的block

2020-06-20  本文已影响0人  三国韩信

我们都知道block在oc中是很常用的,随处可见,越来越多的原先delegate的实现现在都用block去处理了,可见block的重要性。
那么什么是block呢?block在使用的时候要注意什么呢?
直接show code,看看底层block到底是变成了啥数据结果的

/*
block 被编译成了结构体,根据block捕获变量的情况,会有不同的情况。
struct Block_layout和struct Block_descriptor_1是一定存在的,struct Block_descriptor_2和struct Block_descriptor_3是可选的。
当block是全局block的时候,descriptor_2和descriptor_3是没有的。当block有捕获变量的时候,他两会存在,并会对block和捕获的变量做一下内存的操作。比如会把捕获的变量copy到堆上。
*/
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor; //
    // imported variables
};

struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
/* block 总共有6中,我们开发常用的有3种,_NSConcreteStackBlock、
_NSConcreteMallocBlock、_NSConcreteGlobalBlock。另外的3种多在系统级别才会被使用到。
*/
void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };

So,NSGlobalBlock、NSStackBlock、NSMallocBlock这3种又有啥区别呢

在MRC下:
NSGlobalBlock:没有访问(捕获)auto变量(局部变量)的block (数据区)
NSStackBlock:访问(捕获)auto变量(局部变量)的block (栈区)
NSMallocBlock: 对NSStackBlock做了一次copy操作后得到的block。(堆区)

在ARC下:
被强指针引用的block且引用了外部变量,那么会自动做一次copy操作,即把NSStackBlock上的block copy到NSMallocBlock上。即被strong,copy修饰的block且用了外部变量就是NSMallocBlock。

判断捕获对象释放:
NSStackBlock(栈上的block)会对捕获对象进行强引用。(在arc模式下,block作为函数的参数传递,此时的block是NSStackBlock)
NSMallocBlock(堆上的block)会对捕获的对象进行引用,捕获的对象也会被copy到堆空间上。(如果捕获的对象是strong类型的,就会强引用,如果是__weak 修饰的weak类型,就会弱引用)。

typedef void(^MyBlock)(void);
@interface ViewController2 ()
@property(nonatomic,copy)MyBlock myBlock;
@end
@implementation ViewController2

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    int a = 10;
    self.myBlock = ^{   // 被强指针引用且捕获了外部变量,会被copy到堆上,是NSMallocBlock
        a;
        // NSLog(@"%@",self);
        //__NSMallocBlock__
    };
    

    /*block 作为函数的参数来传递,是NSStackBlock,此时即便捕获了外部变量也不会被copy到堆上,
依然是NSStackBlock,也会对捕获的外部变量强引用。*/
    [self getMyhahahahBlock:^{
        // __NSStackBlock__
        a;
        NSLog(@"%@",self);
    }];
}

-(void)getMyhahahahBlock:(MyBlock)block{
    //self.myBlock = block;
    block();
    NSLog(@"%@",block);  
    
}

block访问外部变量有几种方式呢?

  1. __block修饰变量(__block 只能修饰auto的局部变量,不能修饰static变量,也不能修饰全局变量)
  2. static修饰变量
  3. 全局变量

那么__block 做了啥呢?
__block 修饰后,底层会把捕获的局部变量包装成一个对象,通过捕获这个变量来修改局部变量的值。

//__block修饰的变量会被封装成如下的结构体
struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;   // 对捕获的变量copy到内存。
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};
NSMutableArray *array = [NSMutableArray alloc]init];
MyBlock block  = ^{
    [array addObject: @"hello"];  
    /* 这个会报错么?
        被捕获的局部变量不是会被做一次copy操作,copy到堆上么?
        NSMutableArray对象被copy后,不是变成NSArray对象了么?
        NSArray对象能用addObject方法?
    */
    /* 对于这个疑惑,在看了底层源码之后发现,苹果baba不是简单的调了一次[array copy]函数。
        它其实是去堆上开辟了一个和array一样大的内存空间,
        然后把array的内存都搬到新的array上。所以不会存在上面说的情况。
    */
    // 先开辟空间,然后做内存移动。
    struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
    memmove(copy+1, src+1, src->size - sizeof(*src));
}

总结来说,如果是__block修饰的变量,在block内部被修改了,这个过程中存在了3层copy操作:

上一篇 下一篇

猜你喜欢

热点阅读