1. 程式人生 > >Object-C高階程式設計讀書筆記(5)——Block的物件型別擷取

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外部也得到相應的改變。