深入分析 Objective-C block、weakself、strongself 實(shí)現(xiàn)原理

Block是我們?cè)谌粘C編碼中經(jīng)常使用的特性,它可以非常便捷高效的編寫(xiě)和組織代碼,可以讓異步調(diào)用的代碼更加的精煉易讀。但是在日常開(kāi)發(fā)過(guò)程中我們大部分情況都是寫(xiě)著教科書(shū)一般的代碼來(lái)確保編碼的正確,下面我們通過(guò)block的源碼分析來(lái)看看block的實(shí)現(xiàn)原理。文章有點(diǎn)長(zhǎng),請(qǐng)耐心點(diǎn)哦,相信你一定有所收獲的。


正常我們?cè)谑褂胋lock時(shí)會(huì)寫(xiě)出如下的代碼:

- (void)function{
  __weak typeof(self) weakself = self; //創(chuàng)建一個(gè)指向當(dāng)前對(duì)象的弱引用
  [teseObject callFunc:^{
    __strong typeof(self) strongself = weakself; //block內(nèi)部定義一個(gè)指向當(dāng)前對(duì)象的強(qiáng)引用
    [strongself callFunc_1:....];
    [strongself callFunc_2:....];
    ....
}];
}

現(xiàn)在我們來(lái)帶著問(wèn)題分析一下上面的這個(gè)代碼:
1. 可不可以直接使用self?
不可以。因?yàn)檫@樣block會(huì)強(qiáng)持有self對(duì)象,造成循環(huán)引用,從而導(dǎo)致內(nèi)存泄露。

2. 可不可以直接使用weakself?
看情況。由于weakself不會(huì)持有對(duì)象,因此不會(huì)造成循環(huán)引用的問(wèn)題,但是使用weakself卻會(huì)造成block執(zhí)行不一致問(wèn)題,試想一下上面的代碼,當(dāng)調(diào)用“callFunc_1”的時(shí)候weakself是有效的,但是當(dāng)調(diào)用“callFunc_2”的時(shí)候weakself可能已經(jīng)是nil了,這樣就造成了block內(nèi)執(zhí)行不一致從而導(dǎo)致意想不到的結(jié)果

3. 循環(huán)引用是不是都是壞人?
答案是否定的。當(dāng)block開(kāi)始執(zhí)行的時(shí)候,strongself會(huì)去取self對(duì)象的值,如果此時(shí)self已經(jīng)為nil,那么整個(gè)block執(zhí)行期間strongself都是nil,如果self有效那么strongself就是利用了循環(huán)引用的特性保證了在block執(zhí)行的時(shí)候self對(duì)象不會(huì)被析構(gòu),保證block執(zhí)行的一致性。其實(shí)我們?cè)诰帉?xiě)業(yè)務(wù)代碼的時(shí)候(很多第三方開(kāi)源類(lèi)庫(kù))中會(huì)利用到循環(huán)引用的這一特性來(lái)保證block中引用的對(duì)象在block執(zhí)行的時(shí)候依然有效,但是切忌使用這樣黑魔法的時(shí)候要在block執(zhí)行結(jié)束后打破循環(huán)引用。由于strongself是block內(nèi)部定義的變量,在block執(zhí)行結(jié)束會(huì)由系統(tǒng)回收從而打破循環(huán)引用

總結(jié):使用strongself可以消除循環(huán)引用帶來(lái)的內(nèi)存泄漏,也可以保證block執(zhí)行過(guò)程中的一致性。所以正常的業(yè)務(wù)代碼我們都會(huì)使用上面的標(biāo)準(zhǔn)方式編寫(xiě),只有特殊的情況才會(huì)利用block的特性寫(xiě)一些黑魔法的代碼。


接下來(lái)我們就來(lái)通過(guò)解讀block的源碼來(lái)看看block到底干了些什么,首先我們先來(lái)看看下面這些長(zhǎng)得像面試題的東西,如果執(zhí)行的結(jié)果大家覺(jué)得疑惑就繼續(xù)讀下去,如果沒(méi)有任何疑惑也可以繼續(xù)讀下去指正一下:

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

        __block NSInteger block_count = 10;
        NSInteger(^block_sum)(void)=^{
            return block_count;
        };
        block_count = 20;
        NSLog(@"\\n %ld",block_sum()); //結(jié)果: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);  //結(jié)果:aaabbbccc   

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

依然讓我們帶著問(wèn)題來(lái)分析上面的代碼:

1. 不加__block就不能修改變量?
答案是否定的。首先我們要理解這里所說(shuō)的修改的概念:修改指針 or 修改真實(shí)值。在block內(nèi)部對(duì)一個(gè)沒(méi)有加__block的變量進(jìn)行重新賦值,編譯器會(huì)報(bào)錯(cuò)。其實(shí)編譯器做法很簡(jiǎn)單——不準(zhǔn)修改這個(gè)變量所指向的那個(gè)區(qū)域的內(nèi)容,對(duì)于值類(lèi)型那個(gè)區(qū)域存放的是真實(shí)值,對(duì)于引用類(lèi)型那個(gè)區(qū)域存放的是指向另一個(gè)內(nèi)存的指針值。而對(duì)于引用類(lèi)型真實(shí)值的修改編譯器是不會(huì)做任何限制的。

總結(jié):當(dāng)在block內(nèi)修改一個(gè)值類(lèi)型變量的時(shí)候,需要加上 “__block”,在block內(nèi)修改一個(gè)引用類(lèi)型變量的時(shí)候分情況討論,如果需要將這個(gè)變量完全指向另一個(gè)內(nèi)存對(duì)象,加上“__block”, 如果只是單純的修改指針?biāo)赶虻膶?duì)象則不需要使用“__block”。


上面都是從理論的角度來(lái)解釋Objective-C中block的使用規(guī)則,下面來(lái)通過(guò)C的代碼來(lái)看看block的實(shí)現(xiàn)源碼,下面的每一段代碼是和Objective-C代碼相對(duì)應(yīng)的:

--------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;
  //構(gòu)造函數(shù)
  __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++函數(shù)實(shí)現(xiàn)
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變量聲明和賦值寫(xiě)成了一行,這樣方便對(duì)比C代碼中的main函數(shù)并有下面的幾點(diǎn)發(fā)現(xiàn):

  1. “^”變成了“*”,可以看出block其實(shí)是C中的函數(shù)指針。
  2. 最重要的代碼是:結(jié)構(gòu)體__main_block_impl_0 和 函數(shù)__main_block_func_0
  • __main_block_impl_0 是block的真面目:它是一個(gè)結(jié)構(gòu)體,count變量就是被block捕獲的外部變量在結(jié)構(gòu)體中的新定義;__block_impl可以看成面向?qū)ο罄锏幕?lèi)其中定義了block結(jié)構(gòu)體的通用屬性,其中最重要的時(shí)FuncPtr函數(shù)指針;__main_block_impl_0(...)這個(gè)就是block結(jié)構(gòu)體的構(gòu)造函數(shù)了,函數(shù)的參數(shù)中有一個(gè)“NSInteger count”,這個(gè)就是構(gòu)建block實(shí)例時(shí)傳入的count值,從這個(gè)地方就可以看出在block變量在初始化賦值的時(shí)候就已經(jīng)將值類(lèi)型直接放進(jìn)了結(jié)構(gòu)體,所以后續(xù)的改動(dòng)都不會(huì)影響block所捕獲的這個(gè)count值。
  • __main_block_func_0 是Objective-C中定義的block體,是block真正要執(zhí)行的函數(shù),當(dāng)執(zhí)行block時(shí)會(huì)通過(guò)上面block結(jié)構(gòu)體中得FuncPtr指針找到這個(gè)函數(shù)來(lái)執(zhí)行。它直接返回的就是結(jié)構(gòu)體中的count,所以也證明了后面的修改是不會(huì)影響block捕獲的變量值。
  1. 再來(lái)看main函數(shù)里的代碼就清晰很多了,最重要的就是第二句——“NSInteger(block)(void)=((NSInteger ()())&__main_block_impl_0((void )__main_block_func_0, &__main_block_desc_0_DATA, count));”* 先通過(guò)結(jié)構(gòu)體的構(gòu)造函數(shù)初始化結(jié)構(gòu)體,傳入預(yù)先定義好的block真正要執(zhí)行的函數(shù)指針,block的描述和值變量count。然后通過(guò)“&”取結(jié)構(gòu)體實(shí)例的地址并通過(guò)((NSInteger ()())進(jìn)行指針的強(qiáng)制類(lèi)型轉(zhuǎn)換成函數(shù)指針付給“block”指針變量。最后一句代碼“((NSInteger ()(__block_impl ))((__block_impl )block)->FuncPtr)((__block_impl )block)”是用來(lái)執(zhí)行block的,代碼很清晰,將函數(shù)指針轉(zhuǎn)成__block_impl** 結(jié)構(gòu)體指針并執(zhí)行其中FuncPtr指向的函數(shù)。

下面我們?cè)倏匆幌录由稀癬_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 代碼--------
//定義一個(gè)保存變量的結(jié)構(gòu)體
struct __Block_byref_count_0 {
  void *__isa;
__Block_byref_count_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger count;
};

//block的結(jié)構(gòu)體
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++函數(shù)
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函數(shù)
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的析構(gòu)函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->count, 8/*BLOCK_FIELD_IS_BYREF*/);}

//block的描述結(jié)構(gòu)體
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 };

代碼分析:

  1. 查看main函數(shù),對(duì)比之前的代碼可以很清晰的看到了不一樣的地方,原先定義“NSInteger count=10”的地方變成了一個(gè)結(jié)構(gòu)體(** __Block_byref_count_0**)指針的定義和初始化代碼并將count變量傳給了結(jié)構(gòu)體的構(gòu)造函數(shù),所以可以看出“__block”的作用就是定義一個(gè)新的結(jié)構(gòu)體來(lái)包裹原來(lái)的變量。
  2. 查看block的結(jié)構(gòu)體定義“** __main_block_impl_0**”,count變量類(lèi)型也變成了一個(gè)結(jié)構(gòu)體指針。
  3. 查看block執(zhí)行函數(shù)“** __main_block_func_0**”,里面的count變量已經(jīng)一個(gè)結(jié)構(gòu)體類(lèi)型了,所以這下我們可以結(jié)合之前的理論知識(shí)--block內(nèi)是可以改變指針?biāo)赶虻哪莻€(gè)對(duì)象值,在block外面修改count的時(shí)候事實(shí)上也是修改的結(jié)構(gòu)體指針?biāo)附Y(jié)構(gòu)體對(duì)象的內(nèi)部值。

總結(jié):沒(méi)有使用“__block”時(shí),內(nèi)部是直接使用了該變量,對(duì)于值類(lèi)型變量是直接定義新變量并賦值相同,對(duì)于引用類(lèi)型變量是定義一個(gè)新變量并copy它。當(dāng)使用“__block”時(shí),會(huì)增加一個(gè)結(jié)構(gòu)體將變量包起來(lái),對(duì)于值類(lèi)型變量就相當(dāng)于變成指針,對(duì)于引用類(lèi)型相當(dāng)于變成了指針的指針。

下面的代碼是對(duì)于全局static變量在block中使用的代碼解析:
為了簡(jiǎn)單清晰,我只保留了block結(jié)構(gòu)體和block執(zhí)行函數(shù)

--------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結(jié)構(gòu)體
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函數(shù)
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);
}

代碼分析:

  1. 查看bloc?k的結(jié)構(gòu)體可以發(fā)現(xiàn)并沒(méi)有捕獲copy變量而是在結(jié)構(gòu)體的執(zhí)行方法中直接使用了全局靜態(tài)變量,所以執(zhí)行時(shí)才去取值,一直都是獲取變量的最新值.

下面的代碼是在類(lèi)中使用類(lèi)變量或?qū)傩詴r(shí)的代碼解析:
這里定義了一個(gè)“TestObject”的測(cè)試類(lèi),在類(lèi)的“myFunction”中定義了一個(gè)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)));
}

代碼分析:

  1. 看第一個(gè)代碼塊就可以很清楚的看到了block結(jié)構(gòu)體定義了一個(gè)TestObject變量并在初始化方法中將該變量賦值從而實(shí)現(xiàn)了對(duì)原來(lái)self對(duì)象的copy并持有了它,這樣就形成了循環(huán)引用了。

總結(jié):通過(guò)上面對(duì)局部值變量、局部引用類(lèi)型變量、全局變量和類(lèi)變量的理論解釋和源代碼分析,相信大家對(duì)block已經(jīng)能夠徹底掃盲,關(guān)于weakself和strongself的源碼分析留給大家思考吧。文章主要是梳理了一下我們?nèi)粘J褂弥械囊恍└拍詈驮?,具體使用還是看具體場(chǎng)景,希望對(duì)大家有幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容