Object-C高階程式設計讀書筆記(5)——Block的物件型別擷取
在之前的部落格中,我們探討了Block對於普通型別資料的擷取,其實現很簡單,就是在Block物件中儲存一份值拷貝。
那麼,對於OC中的物件型別(包括系統自帶型別NSArray,NSString和自定義物件型別),Block又是怎麼儲存的呢?在《OC高階程式設計》書中對於該部分,可能是由於XCode編譯器版本不同的原因,有些錯誤,現在就我個人的理解,總結一下。
一個事實
在ARC有效時,id型別以及物件型別變數必定附加所有權修飾符,預設為__strong修飾符。
Block對於不同所有權物件變數的儲存
既然知道了物件型別變數或id型別變數有所有權修飾符的不同,那麼,對於Block中物件型別的儲存,也可以分為對於__strong物件(預設)和__weak物件的儲存。
程式碼1:
typedef void(^blk_t)(id); int main(int argc, const char * argv[]) { blk_t blk; { id array = [[NSMutableArray alloc] init]; id array2 = array; blk = ^(id obj){ [array2 addObject:obj]; NSLog(@"Now the count of array = %ld", (long)[array2 count]); }; } blk([[NSString alloc] init]); blk([[NSString alloc] init]); blk([[NSString alloc] init]); return 0; }
輸出結果為
上面的NSString物件被成功的插入到了Block物件中的array2中,說明Block儲存住了array2物件。Block中的array2並沒有因為Block外的array2和array的作用域結束而釋放。
程式碼2
輸出結果typedef void(^blk_t)(id); int main(int argc, const char * argv[]) { blk_t blk; { id array = [[NSMutableArray alloc] init]; id <span style="color:#FF0000;">__weak</span> array2 = array; blk = ^(id obj){ [array2 addObject:obj]; NSLog(@"Now the count of array = %ld", (long)[array2 count]); }; } blk([[NSString alloc] init]); blk([[NSString alloc] init]); blk([[NSString alloc] init]); return 0; }
array2中的元素並沒有增加,我們再將Block中的array2打印出來,會發現此時array2已經被置為null,可見,當加上__weak所有權修飾符的變數置於Block中後,該變數會因為Block變數外的變數的釋放而釋放。
那麼對於外部傳入到Block中的__strong物件變數與__weak物件變數,Block又是怎麼處理的呢?
同樣,我們用clang命令將程式碼編譯為C++程式碼
__strong array2對應的程式碼
typedef void(*blk_t)(id);
<span style="color:#FF0000;">struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
id array2;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array2, int flags=0) : array2(_array2) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};</span>
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
id array2 = __cself->array2; // bound by copy
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array2, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_3c4b88_mi_0, (long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array2, sel_registerName("count")), array2);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array2, (void*)src->array2, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array2, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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[]) {
blk_t blk;
{
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
id array2 = array;
blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array2, 570425344));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
return 0;
}
__weak array2對應的程式碼
typedef void(*blk_t)(id);
<span style="color:#FF0000;">
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__weak id array2;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __weak id _array2, int flags=0) : array2(_array2) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};</span>
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
__weak id array2 = __cself->array2; // bound by copy
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array2, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_bbd239_mi_0, (long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array2, sel_registerName("count")), array2);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array2, (void*)src->array2, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array2, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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[]) {
blk_t blk;
{
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
id __attribute__((objc_gc(weak))) array2 = array;
blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array2, 570425344));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
return 0;
}
發現轉換後的程式碼基本毫無區別,唯一的區別是在Block的impl中。類似普通型別變數,在Block的impl中同樣添加了對應的成員變數來儲存外部傳入的變數,這裡為
id array2;
當傳入Block中的是__strong型別物件時,Block會將對應變數宣告為stong屬性
id array2;
當傳入Block中的是__weak型別物件時,Block會將對應變數宣告為weak屬性
__weak id array2;
這樣就解釋了為什麼程式碼1中的array2沒被釋放(此時Block對該物件保持引用。即使Block外部對應的物件都被釋放,在Block中仍然存在一份引用,物件並不會被系統真正釋放)
而在程式碼2中,由於在Block中是weak變數,當Block外的物件釋放時,由於Block本身並沒有對該物件保持引用,導致物件引用計數為0,物件釋放,Block中的weak array2為置為nil。
總結一下,在Block對於物件型別的變數擷取遵循以下原則:
1.傳入Block中若為__strong型別物件,Block中對應新增strong成員變數保持對其引用
2.傳入Block中的若無__weak型別物件,Block對應新增weak成員變數,並不會保持其引用,當外部計數為0時,weak物件置為nil。
Block迴圈引用
瞭解了上面Block對於物件型別變數的擷取規則後,我們就可以理解下面在使用Block成員變數時,常見的一個迴圈引用問題。
有如下程式碼
typedef void(^blk_t)(void);
@interface MyObjec:NSObject
@property(nonatomic, strong) blk_t blk;
@end
@implementation MyObject
-(instancetype) init
{
self = [super init];
if(self)
{
_blk = ^{ NSLog(@"I am %@", self);};
}
return self;
}
@end
上面會引起迴圈引用 導致MyObject物件永遠不會被釋放。
這是為什麼呢?
細看MyObject物件的宣告,其包含一個strong的屬性blk,即MyObject物件持有blk。
而在blk的定義中,
_blk = ^{ NSLog(@"I am %@", self);};
在Block中傳入了self物件(ARC下預設物件均為__strong所有權型別)。結合上面的討論,當__strong物件型別傳入Block中時,Block會對其強引用。
如此,MyObject物件對blk強引用,blk對self(即MyObject物件)強引用,迴圈引用。
要打破迴圈引用,我們只需要修改程式碼為
typedef void(^blk_t)(void);
@interface MyObjec:NSObject
@property(nonatomic, strong) blk_t blk;
@end
@implementation MyObject
-(instancetype) init
{
self = [super init];
if(self)
{
id __weak tmp = self;
_blk = ^{ NSLog(@"I am %@", tmp);};
}
return self;
}
@end
這樣,由於傳入Block中的是一個__weak物件,因此Block不會對其強引用,而是相應的用weak儲存。又因為blk變數是屬於MyObject物件的屬性,因此也不用擔心在呼叫Block時,tmp被釋放的問題。
__block關鍵字物件擷取
在物件宣告的前面,我們同樣可以加上__block變數來表明其為一個block變數。
即
typedef void(^blk_t)(id);
int main(int argc, const char * argv[]) {
blk_t blk;
{
id array = [[NSMutableArray alloc] init];
id __block __weak array2 = array;
blk = ^(id obj){
[array2 addObject:obj];
NSLog(@"Now the count of array = %ld %@", (long)[array2 count], array2);
};
}
blk([[NSString alloc] init]);
blk([[NSString alloc] init]);
blk([[NSString alloc] init]);
return 0;
}
在這裡我們定義了一個__weak的__block變數 array2。
通過clang轉換為C++程式碼,如下:
typedef void(*blk_t)(id);
<span style="color:#FF0000;">struct __Block_byref_array2_0 {
void *__isa;
__Block_byref_array2_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
__weak id array2;
};</span>
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_array2_0 *array2; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_array2_0 *_array2, int flags=0) : array2(_array2->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
__Block_byref_array2_0 *array2 = __cself->array2; // bound by ref
((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)(array2->__forwarding->array2), sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_758659_mi_0, (long)((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)(array2->__forwarding->array2), sel_registerName("count")), (array2->__forwarding->array2));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array2, (void*)src->array2, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array2, 8/*BLOCK_FIELD_IS_BYREF*/);}
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[]) {
blk_t blk;
{
id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
<span style="color:#FF0000;">__Block_byref_array2_0 array2</span> = {(void*)1,(__Block_byref_array2_0 *)&array2, 33554432, sizeof(__Block_byref_array2_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, array};
blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array2_0 *)&array2, 570425344));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("alloc")), sel_registerName("init")));
return 0;
}
我們發現,id __block __weak array2 變數轉換為了
<span style="color:#FF0000;">__Block_byref_array2_0 array2</span>
型別。再看其定義
<span style="color:#FF0000;">struct __Block_byref_array2_0 {
void *__isa;
__Block_byref_array2_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
__weak id array2;
};</span>
這種結構與我們之前對於__block普通變數的解析類似,只不過這裡的變數值前面添加了__weak。這樣,__block中並不對array2做強引用,當Block外部的array2物件釋放時,Block內部對應的array2也會釋放。上述程式碼的執行結果為:
當我們將array2變數宣告為__block __strong型別的呢?對應的,在__block中,array2會被強引用,具體細節就不再複述了。
補充:Block對於類成員變數物件,global物件,區域性static物件 的截獲方式
在做專案時候,發現一個有意思的問題,就是對於Object類中,如果在一個Block中使用類的成員變數,則Block不會對該變數進行單獨儲存,而會直接使用該變數。如下程式碼
typedef void(^blk_t)(void);
@interface MyObj:NSObject
@property(nonatomic, strong) NSString *name;
-(void) showName;
@end
@implementation MyObj
-(void) showName
{
blk_t blk = ^{
NSLog(@"My name is %@", _name);
};
blk();
}
@end
int main(int argc, const char * argv[]) {
MyObj *obj = [MyObj new];
obj.name = @"John";
[obj showName];
return 0;
}
我們翻譯成c++實現後, 再看showName方法中的Block的定義
struct __MyObj__showName_block_impl_0 {
struct __block_impl impl;
struct __MyObj__showName_block_desc_0* Desc;
MyObj *self;
__MyObj__showName_block_impl_0(void *fp, struct __MyObj__showName_block_desc_0 *desc, MyObj *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這裡雖然Block中使用的是_name屬性,但在Block中儲存的其實是MyObj的物件。
有趣的是,雖然之前規定在Block中只能夠改變__block變數的值,但對於物件而言,其屬性是可以在Block中改變的。由於Block中是直接引用的物件本身,在屬性在Block中改變的同事,物件本身的屬性也就被改變了。
我們再看對於global物件,區域性static物件,Block又是如何擷取他們的。
#import <Foundation/Foundation.h>
typedef void(^blk_t)(void);
NSString *str1 = @"global string";
int main(int argc, const char * argv[]) {
static NSString *str2 = @"static string";
// NSString *str3 = @"noraml string";
blk_t blk = ^{
NSLog(@"global string is %@, static string is %@", str1, str2);
str1 = @"new global";
str2 = @"new static";
// str3 = @"new normal string";
};
blk();
NSLog(@"After block the gloabl string is %@, static string is %@", str1, str2);
return 0;
}
翻譯為C++實現,檢視blk的定義
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSString **str2;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString **_str2, int flags=0) : str2(_str2) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSString **str2 = __cself->str2; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_8caa8b_mi_3, str1, (*str2));
str1 = (NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_8caa8b_mi_4;
(*str2) = (NSString *)&__NSConstantStringImpl__var_folders_fg_ngx6t9ks0dq9dgld2wyty5br0000gn_T_main_8caa8b_mi_5;
}
發現,對於global物件str1,Block中根本沒有儲存,而是直接使用的str1本身。對於區域性static物件,Block也沒有直接進行引用,而是對應一個指標的指標。不管怎麼樣的方式,我們均可在Block中直接改變其值,並能夠在Block外得到對應的改變。
那麼對於普通的區域性變數str3呢?我們將註釋去掉,會發現XCode會報一個缺少__block關鍵字錯誤,不讓我們編譯。
看來,對於類的成員變數,global變數,區域性static變數,Block對於這些物件的截獲,也是有不同的方式的。同時對於這些變數物件,即使不新增__block關鍵字,也可以在Block中直接修改其值並在Block外部也得到相應的改變。