深入分析 Objective-C block、weakself、strongself 實現原理

本文轉自:http://www.jianshu.com/p/a5dd014edb13

Block是我們在日常OC編碼中經常使用的特性,它可以非常便捷高效的編寫和組織代碼,可以讓異步調用的代碼更加的精煉易讀。但是在日常開發過程中我們大部分情況都是寫着教科書一般的代碼來確保編碼的正確,下面我們通過block的源碼分析來看看block的實現原理。文章有點長,請耐心點哦,相信你一定有所收穫的。
正常我們在使用block時會寫出如下的代碼:

- (void)function{
  __weak typeof(self) weakself = self; //創建一個指向當前對象的弱引用
  [teseObject callFunc:^{
    __strong typeof(self) strongself = weakself; //block內部定義一個指向當前對象的強引用
    [strongself callFunc_1:....];
    [strongself callFunc_2:....];
    ....
}];
}
現在我們來帶着問題分析一下上面的這個代碼:
1. 可不可以直接使用self?
不可以。因爲這樣block會強持有self對象,造成循環引用,從而導致內存泄露。

2. 可不可以直接使用weakself?
看情況。由於weakself不會持有對象,因此不會造成循環引用的問題,但是使用weakself卻會造成block執行不一致問題,試想一下上面的代碼,當調用“callFunc_1”的時候weakself是有效的,但是當調用“callFunc_2”的時候weakself可能已經是nil了,這樣就造成了block內執行不一致從而導致意想不到的結果

3. 循環引用是不是都是壞人?
答案是否定的。當block開始執行的時候,strongself會去取self對象的值,如果此時self已經爲nil,那麼整個block執行期間strongself都是nil,如果self有效那麼strongself就是利用了循環引用的特性保證了在block執行的時候self對象不會被析構,保證block執行的一致性。其實我們在編寫業務代碼的時候(很多第三方開源類庫)中會利用到循環引用的這一特性來保證block中引用的對象在block執行的時候依然有效,但是切忌使用這樣黑魔法的時候要在block執行結束後打破循環引用。由於strongself是block內部定義的變量,在block執行結束會由系統回收從而打破循環引用

總結:使用strongself可以消除循環引用帶來的內存泄漏,也可以保證block執行過程中的一致性。所以正常的業務代碼我們都會使用上面的標準方式編寫,只有特殊的情況纔會利用block的特性寫一些黑魔法的代碼。
接下來我們就來通過解讀block的源碼來看看block到底幹了些什麼,首先我們先來看看下面這些長得像面試題的東西,如果執行的結果大家覺得疑惑就繼續讀下去,如果沒有任何疑惑也可以繼續讀下去指正一下:

        NSInteger count = 10;
        NSInteger(^sum)(void)=^{
            return count;
        };
        count = 20;
        NSLog(@"\\n %ld",sum()); //結果:10        

        __block NSInteger block_count = 10;
        NSInteger(^block_sum)(void)=^{
            return block_count;
        };
        block_count = 20;
        NSLog(@"\\n %ld",block_sum()); //結果:20

        NSMutableString *mutable_string = [NSMutableString stringWithString:@"aaa"];
        void(^mutable_append)(void)=^{
            [mutable_string appendString:@"ccc"];
        };
        [mutable_string appendString:@"bbb"];
        mutable_append();
        NSLog(@"\\n %@",mutable_string);  //結果:aaabbbccc   

        NSString *string = @"aaa";
        NSString*(^append)(void)=^{
            return [string stringByAppendingString:@"ccc"];
        };
        string = @"bbb";
        NSLog(@"\\n %@",append());  //結果:aaaccc

        __block NSString *block_string = @"aaa";
        NSString*(^block_append)(void)=^{
            return [block_string stringByAppendingString:@"ccc"];
        };
        block_string = @"bbb";
        NSLog(@"\\n %@",block_append()); //結果: bbbccc
依然讓我們帶着問題來分析上面的代碼:

1. 不加__block就不能修改變量?
答案是否定的。首先我們要理解這裏所說的修改的概念:修改指針 or 修改真實值。在block內部對一個沒有加__block的變量進行重新賦值,編譯器會報錯。其實編譯器做法很簡單——不準修改這個變量所指向的那個區域的內容,對於值類型那個區域存放的是真實值,對於引用類型那個區域存放的是指向另一個內存的指針值。而對於引用類型真實值的修改編譯器是不會做任何限制的。

總結:當在block內修改一個值類型變量的時候,需要加上 “block”,在block內修改一個引用類型變量的時候分情況討論,如果需要將這個變量完全指向另一個內存對象,加上“block”, 如果只是單純的修改指針所指向的對象則不需要使用“__block”。
上面都是從理論的角度來解釋Objective-C中block的使用規則,下面來通過C的代碼來看看block的實現源碼,下面的每一段代碼是和Objective-C代碼相對應的:

--------Objective-C 代碼--------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSInteger count = 10;
        NSInteger(^block)(void)=^{  return count; };
        NSLog(@"\\n %ld",block());
    return 0;
}

--------C 代碼--------
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSInteger count;
  //構造函數
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _count, int flags=0) : count(_count) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//block 的C++函數實現
static NSInteger __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSInteger count = __cself->count; // bound by copy

            return count;
        }

//block 的描述信息
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

        NSInteger count = 10;
        NSInteger(*block)(void)=((NSInteger (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, count));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_main_6bdb2c_mi_0,((NSInteger (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block));
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
代碼分析:
爲了便於理解我將Objective-C代碼中得block變量聲明和賦值寫成了一行,這樣方便對比C代碼中的main函數並有下面的幾點發現:

“^”變成了“*”,可以看出block其實是C中的函數指針。
最重要的代碼是:結構體__main_block_impl_0 和 函數__main_block_func_0
__main_block_impl_0 是block的真面目:它是一個結構體,count變量就是被block捕獲的外部變量在結構體中的新定義;__block_impl可以看成面向對象裏的基類其中定義了block結構體的通用屬性,其中最重要的時FuncPtr函數指針;__main_block_impl_0(...)這個就是block結構體的構造函數了,函數的參數中有一個“NSInteger count”,這個就是構建block實例時傳入的count值,從這個地方就可以看出在block變量在初始化賦值的時候就已經將值類型直接放進了結構體,所以後續的改動都不會影響block所捕獲的這個count值。
__main_block_func_0 是Objective-C中定義的block體,是block真正要執行的函數,當執行block時會通過上面block結構體中得FuncPtr指針找到這個函數來執行。它直接返回的就是結構體中的count,所以也證明了後面的修改是不會影響block捕獲的變量值。
再來看main函數裏的代碼就清晰很多了,最重要的就是第二句——“NSInteger(block)(void)=((NSInteger ()())&main_block_impl_0((void *)main_block_func_0, &__main_block_desc_0_DATA, count));” 先通過結構體的構造函數初始化結構體,傳入預先定義好的block真正要執行的函數指針,block的描述和值變量count。然後通過“&”取結構體實例的地址並通過((NSInteger ()())進行指針的強制類型轉換成函數指針付給“block”指針變量。最後一句代碼“((NSInteger ()(block_impl *))((block_impl )block)->FuncPtr)((__block_impl )block)”是用來執行block的,代碼很清晰,將函數指針轉成*__block_impl 結構體指針並執行其中FuncPtr**指向的函數。
下面我們再看一下加上“__block”之後的代碼

--------Objective-C 代碼--------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        __block NSInteger count = 10;
        NSInteger(^block)(void)=^{  return count; };
        NSLog(@"\\n %ld",block());
    return 0;
}
--------C 代碼--------
//定義一個保存變量的結構體
struct __Block_byref_count_0 {
  void *__isa;
__Block_byref_count_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger count;
};

//block的結構體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_count_0 *count; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_count_0 *_count, int flags=0) : count(_count->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//block的C++函數
static NSInteger __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_count_0 *count = __cself->count; // bound by ref

            return (count->__forwarding->count);
        }
//block的copy函數
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->count, (void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);}

//block的析構函數
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);}

//block的描述結構體
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*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;

        __attribute__((__blocks__(byref))) __Block_byref_count_0 count = {(void*)0,(__Block_byref_count_0 *)&count, 0, sizeof(__Block_byref_count_0), 10};
        NSInteger(*block)(void)=((NSInteger (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_count_0 *)&count, 570425344));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_main_10c601_mi_0,((NSInteger (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block));
    }
    return 0;
}

static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
代碼分析:

查看main函數,對比之前的代碼可以很清晰的看到了不一樣的地方,原先定義“NSInteger count=10”的地方變成了一個結構體( __Block_byref_count_0)指針的定義和初始化代碼並將count變量傳給了結構體的構造函數,所以可以看出“__block”的作用就是定義一個新的結構體來包裹原來的變量。
查看block的結構體定義“ __main_block_impl_0”,count變量類型也變成了一個結構體指針。
查看block執行函數“ __main_block_func_0”,裏面的count變量已經一個結構體類型了,所以這下我們可以結合之前的理論知識--block內是可以改變指針所指向的那個對象值,在block外面修改count的時候事實上也是修改的結構體指針所指結構體對象的內部值。
總結:沒有使用“block”時,內部是直接使用了該變量,對於值類型變量是直接定義新變量並賦值相同,對於引用類型變量是定義一個新變量並copy它。當使用“block”時,會增加一個結構體將變量包起來,對於值類型變量就相當於變成指針,對於引用類型相當於變成了指針的指針。
下面的代碼是對於全局static變量在block中使用的代碼解析:
爲了簡單清晰,我只保留了block結構體和block執行函數

--------Objective-C 代碼--------
static NSString *string = @"hello";
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSString*(^block)(void)=^{  
          NSString *appendString = @"world";
          return [string stringByAppendingString: appendString];
    };
        NSLog(@"\\n %ld",block());
    return 0;
}

--------C 代碼--------
static NSString *string = (NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_main_2919dd_mi_0;

//最終的block結構體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//真正的block函數
static NSString * __main_block_func_0(struct __main_block_impl_0 *__cself) {
            NSString *appendString = (NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_main_2919dd_mi_1;
            return ((NSString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)string, sel_registerName("stringByAppendingString:"), (NSString *)appendString);
}
代碼分析:

查看bloc k的結構體可以發現並沒有捕獲copy變量而是在結構體的執行方法中直接使用了全局靜態變量,所以執行時纔去取值,一直都是獲取變量的最新值.
下面的代碼是在類中使用類變量或屬性時的代碼解析:
這裏定義了一個“TestObject”的測試類,在類的“myFunction”中定義了一個block變量並初始化

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

        (*(NSString **)((char *)self + OBJC_IVAR_$_TestObject$_test)) = (NSString *)&__NSConstantStringImpl__var_folders_16_pkj1k8l97qbcp79805czcy580000gn_T_TestObject_44e9ab_mi_0;
    }
static void __TestObject__myFunction_block_copy_0(struct __TestObject__myFunction_block_impl_0*dst, struct __TestObject__myFunction_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

static struct __TestObject__myFunction_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __TestObject__myFunction_block_impl_0*, struct __TestObject__myFunction_block_impl_0*);
  void (*dispose)(struct __TestObject__myFunction_block_impl_0*);
} __TestObject__myFunction_block_desc_0_DATA = { 0, sizeof(struct __TestObject__myFunction_block_impl_0), __TestObject__myFunction_block_copy_0, __TestObject__myFunction_block_dispose_0};

static void _I_TestObject_myFunction(TestObject * self, SEL _cmd) {
    ((void (*)(id, SEL, Block))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__TestObject__myFunction_block_impl_0((void *)__TestObject__myFunction_block_func_0, &__TestObject__myFunction_block_desc_0_DATA, self, 570425344)));
}
代碼分析:

看第一個代碼塊就可以很清楚的看到了block結構體定義了一個TestObject變量並在初始化方法中將該變量賦值從而實現了對原來self對象的copy並持有了它,這樣就形成了循環引用了。
總結:通過上面對局部值變量、局部引用類型變量、全局變量和類變量的理論解釋和源代碼分析,相信大家對block已經能夠徹底掃盲,關於weakself和strongself的源碼分析留給大家思考吧。文章主要是梳理了一下我們日常使用中的一些概念和原理,具體使用還是看具體場景,希望對大家有幫助。

本文轉自:http://www.jianshu.com/p/a5dd014edb13

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章