Block探索

咱們使用的block分三種:靜態block(沒有使用任何外部變量),棧block(使用外部臨時變量),堆block(使用外部成員變量或者屬性)。git

  1. 靜態blockgithub

下面是測試的源碼函數

@interface MBlockObj : NSObject

@end

#import "MBlockObj.h"

@implementation MBlockObj

- (void)testMBlock {
    
    void (^blockM)(void) = ^{
        
        int i = 0;
        i ++;
        
    };
    blockM();
    
}

@end

用終端執行 clang -rewrite-objc MBlockObj.m 命令後,我刪去多餘代碼,獲得下面這部分測試

struct __MBlockObj__testMBlock_block_impl_0 {
  struct __block_impl impl;
  struct __MBlockObj__testMBlock_block_desc_0* Desc;
  __MBlockObj__testMBlock_block_impl_0(void *fp, struct __MBlockObj__testMBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MBlockObj__testMBlock_block_func_0(struct __MBlockObj__testMBlock_block_impl_0 *__cself) {

        int i = 0;
        i ++;

}

static struct __MBlockObj__testMBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __MBlockObj__testMBlock_block_desc_0_DATA = { 0, sizeof(struct __MBlockObj__testMBlock_block_impl_0)};

static void _I_MBlockObj_testMBlock(MBlockObj * self, SEL _cmd) {

    void (*blockM)(void) = ((void (*)())&__MBlockObj__testMBlock_block_impl_0((void *)__MBlockObj__testMBlock_block_func_0, &__MBlockObj__testMBlock_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blockM)->FuncPtr)((__block_impl *)blockM);

}

能夠看到,咱們的 blockM 被變成了atom

((void (*)())&__MBlockObj__testMBlock_block_impl_0((void *)__MBlockObj__testMBlock_block_func_0, &__MBlockObj__testMBlock_block_desc_0_DATA));

__MBlockObj__testMBlock_block_impl_0 須要兩個參數, __MBlockObj__testMBlock_block_func_0 主要是對方法的實現內容,第二個參數是對block的一些信息描述。code

附:測試代碼中用OC對象測試,結果也是類似。對象

 

2.棧block作用域

測試代碼get

- (void)testMBlock {
    
    int i = 0;
    
    void (^blockM)(void) = ^{
        
        printf("%d", i);
        
    };
    blockM();
    
}

clang出來的代碼是這樣的cmd

struct __MBlockObj__testMBlock_block_impl_0 {
  struct __block_impl impl;
  struct __MBlockObj__testMBlock_block_desc_0* Desc;
  int i;
  __MBlockObj__testMBlock_block_impl_0(void *fp, struct __MBlockObj__testMBlock_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MBlockObj__testMBlock_block_func_0(struct __MBlockObj__testMBlock_block_impl_0 *__cself)
{
  int i = __cself->i; // bound by copy

    printf("%d", i);

}

static struct __MBlockObj__testMBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __MBlockObj__testMBlock_block_desc_0_DATA = { 0, sizeof(struct __MBlockObj__testMBlock_block_impl_0)};

static void _I_MBlockObj_testMBlock(MBlockObj * self, SEL _cmd) {

    int i = 0;

    void (*blockM)(void) = ((void (*)())&__MBlockObj__testMBlock_block_impl_0((void *)__MBlockObj__testMBlock_block_func_0, &__MBlockObj__testMBlock_block_desc_0_DATA, i));
    ((void (*)(__block_impl *))((__block_impl *)blockM)->FuncPtr)((__block_impl *)blockM);

}

因爲訪問臨時變量 i,因而結構體發生變化,多了一個對應的成員變量,而測試代碼執行的任務是打印 i,而 __MBlockObj__testMBlock_block_func_0 中 printf 打印的 i,實際上是作了一次值傳遞。

附:若是把臨時變量換成OC對象,好比NSArray的話,clang出來的代碼又有變化

struct __MBlockObj__testMBlock_block_impl_0 {
  struct __block_impl impl;
  struct __MBlockObj__testMBlock_block_desc_0* Desc;
  NSArray *mArr;
  __MBlockObj__testMBlock_block_impl_0(void *fp, struct __MBlockObj__testMBlock_block_desc_0 *desc, NSArray *_mArr, int flags=0) : mArr(_mArr) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MBlockObj__testMBlock_block_func_0(struct __MBlockObj__testMBlock_block_impl_0 *__cself)
{
  NSArray *mArr = __cself->mArr; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8x_br8kd9yd70g5q66ssb9jhblh0000gn_T_MBlockObj_6efbe8_mi_0, mArr);
    
}
static void __MBlockObj__testMBlock_block_copy_0(struct __MBlockObj__testMBlock_block_impl_0*dst, struct __MBlockObj__testMBlock_block_impl_0*src)
{
    _Block_object_assign((void*)&dst->mArr, (void*)src->mArr, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __MBlockObj__testMBlock_block_dispose_0(struct __MBlockObj__testMBlock_block_impl_0*src)
{
    _Block_object_dispose((void*)src->mArr, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __MBlockObj__testMBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __MBlockObj__testMBlock_block_impl_0*, struct __MBlockObj__testMBlock_block_impl_0*);
  void (*dispose)(struct __MBlockObj__testMBlock_block_impl_0*);
} __MBlockObj__testMBlock_block_desc_0_DATA = { 0, sizeof(struct __MBlockObj__testMBlock_block_impl_0), __MBlockObj__testMBlock_block_copy_0, __MBlockObj__testMBlock_block_dispose_0};

static void _I_MBlockObj_testMBlock(MBlockObj * self, SEL _cmd) {

    NSArray* mArr = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"));

    void (*blockM)(void) = ((void (*)())&__MBlockObj__testMBlock_block_impl_0((void *)__MBlockObj__testMBlock_block_func_0, &__MBlockObj__testMBlock_block_desc_0_DATA, mArr, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blockM)->FuncPtr)((__block_impl *)blockM);

}

這裏主要是多了 __MBlockObj__testMBlock_block_copy_0 和 __MBlockObj__testMBlock_block_dispose_0 兩個方法。

前者的方法名了有個 copy ,但不要覺得是執行了 copy 一類的操做。這個方法裏面的具體實現,是調用了 _Block_object_assign 方法,_Block_object_assign 方法會根據傳進來的實際參數,判斷是block對象(flags對應是7),仍是OC對象(flags對應是3),仍是引用(flags對應是8)。若是是OC對象,那麼會進行retain,而後再賦值。

後者的方法是一個析構函數做用,但也一樣判斷實際參數類型,若是是OC對象,會進行release。

 

3.堆block

測試代碼

@interface MBlockObj : NSObject

@property (nonatomic, strong) NSArray* mArr;

@end

#import "MBlockObj.h"

@implementation MBlockObj

- (void)testMBlock {
    
    _mArr = [NSArray array];
    
    void (^blockM)(void) = ^{
        
        NSLog(@"mArr %@", _mArr);
        
    };
    blockM();
    
}

@end

clang出來的代碼

struct __MBlockObj__testMBlock_block_impl_0 {
  struct __block_impl impl;
  struct __MBlockObj__testMBlock_block_desc_0* Desc;
  MBlockObj *self;
  __MBlockObj__testMBlock_block_impl_0(void *fp, struct __MBlockObj__testMBlock_block_desc_0 *desc, MBlockObj *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __MBlockObj__testMBlock_block_func_0(struct __MBlockObj__testMBlock_block_impl_0 *__cself)
{
  MBlockObj *self = __cself->self; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_8x_br8kd9yd70g5q66ssb9jhblh0000gn_T_MBlockObj_58d1bf_mi_0, (*(NSArray **)((char *)self + OBJC_IVAR_$_MBlockObj$_mArr)));

}
static void __MBlockObj__testMBlock_block_copy_0(struct __MBlockObj__testMBlock_block_impl_0*dst, struct __MBlockObj__testMBlock_block_impl_0*src)
{
    _Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __MBlockObj__testMBlock_block_dispose_0(struct __MBlockObj__testMBlock_block_impl_0*src)
{
    _Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __MBlockObj__testMBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __MBlockObj__testMBlock_block_impl_0*, struct __MBlockObj__testMBlock_block_impl_0*);
  void (*dispose)(struct __MBlockObj__testMBlock_block_impl_0*);
} __MBlockObj__testMBlock_block_desc_0_DATA = { 0, sizeof(struct __MBlockObj__testMBlock_block_impl_0), __MBlockObj__testMBlock_block_copy_0, __MBlockObj__testMBlock_block_dispose_0};

static void _I_MBlockObj_testMBlock(MBlockObj * self, SEL _cmd) {

    (*(NSArray **)((char *)self + OBJC_IVAR_$_MBlockObj$_mArr)) = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"));

    void (*blockM)(void) = ((void (*)())&__MBlockObj__testMBlock_block_impl_0((void *)__MBlockObj__testMBlock_block_func_0, &__MBlockObj__testMBlock_block_desc_0_DATA, self, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blockM)->FuncPtr)((__block_impl *)blockM);

}

好吧,上面的代碼看出來了,即便咱們是訪問成員變量,也是經由self去獲取到成員變量去訪問的,而上面的已經解釋過, _Block_object_assign 會retain實際參數,就是說會retain self,在某些狀況下會形成循環引用。

 

附1:__block是作了什麼操做?

各位能夠作測試代碼,而後clang一下,能夠看到 __block 所修飾的變量,會轉成一個結構體,具體的做用,是將這個變量從棧上覆制到堆上。

附2:爲何類的屬性或成員變量能夠直接修改,而臨時變量須要聲明爲 __block 才能夠修改?

附1的問題的解釋裏,臨時變量在聲明爲 __block 後,從棧複製到堆上,而類的屬性或成員變量,自己就是在堆上,或者能夠理解爲各自的做用域不一樣。

附3:這是block實現過程的源碼  https://github.com/mackyle/blocksruntime

附4:block 調用析構函數,釋放其所持有的變量的時機?

block 調用析構函數,釋放其所持有的變量,是在 block 被設置爲 nil,或者被 release 的時候。因此若是是一個臨時的block(測試代碼中所看到的 block),會在方法調用結束的時候,把block及其持有變量進行釋放;而 block 做爲屬性,除非主動置nil,不然會在其所在類調用 dealloc 的時候釋放。

附5:block屬性經常使用copy修飾,其實使用strong也是同樣的。

相關文章
相關標籤/搜索