探祕Block(一):block的本質

原做於:2018-01-02
GitHub Repo:BoyangBloghtml

簡單概述

block是C語言的擴充功能,咱們能夠認爲它是 帶有自動變量的匿名函數c++

block是一個匿名的inline代碼集合:git

  • 參數列表,就像一個函數。
  • 是一個對象!
  • 有聲明的返回類型
  • 可得到義詞法範圍的狀態,。
  • 可選擇性修改詞法範圍的狀態。
  • 能夠用相同的詞法範圍內定義的其它block共享進行修改的可能性
  • 在詞法範圍(堆棧框架)被破壞後,能夠繼續共享和修改詞法範圍(堆棧框架)中定義的狀態

block怎麼寫

最簡單。github

int (^DefaultBlock1)(int) = ^int (int a) {
        return a + 1;
    };
    DefaultBlock1(1);
    
複製代碼

升級版。macos

// 利用 typedef 聲明block
typedef return_type (^BlockTypeName)(var_type);

// 做屬性
@property (nonatomic, copy ,nullable) BlockTypeName blockName;

// 做方法參數
- (void)requestForSomething:(Model)model handle:(BlockTypeName)handle;
複製代碼

block的實現

在LLVM的文件中,我找到了一份文檔,Block_private.h,這裏能夠查看到block的實現狀況數組

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};
struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

複製代碼

裏面的invoke就是指向具體實現的函數指針,當block被調用的時候,程序最終會跳轉到這個函數指針指向的代碼區。 而 Block_descriptor裏面最重要的就是 copy函數和 dispose函數,從命名上能夠推斷出,copy函數是用來捕獲變量並持有引用,而dispose函數是用來釋放捕獲的變量。函數捕獲的變量會存儲在結構體 Block_layout的後面,在invoke函數執行前所有讀出。安全

按照慣例,使用 clang -rewrite-objc 將一個代碼進行編譯轉換,將獲得一份C++代碼。刨除其餘無用的代碼:markdown

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}

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;
        (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    }
    return 0;
}
複製代碼

先看最直接的 __block_impl代碼,app

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
複製代碼

這裏是一個結構體,裏面的元素分別是框架

  • isa,指向所屬類的指針,也就是block的類型
  • flags,標誌變量,在實現block的內部操做時會用到
  • Reserved,保留變量
  • FuncPtr,block執行時調用的函數指針

接着, __main_block_impl_0由於包含了__block_impl,咱們能夠將它打開,直接當作

__main_block_impl_0{
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __main_block_desc_0 *Desc;
}
複製代碼

這麼一來,咱們能夠將block理解爲,一個OC對象、一個函數。

block的類型

咱們能夠知道,咱們常見的block是有三種:

  • __NSGlobalBlock
  • __NSStackBlock
  • __NSMallocBlock
void (^block)(void) = ^{
    NSLog(@"biboyang");
};
block();
複製代碼

static int age = 10;
    void(^block)(void) = ^{
        NSLog(@"Hello, World! %d",age);
    };
block();
複製代碼

像是這種,沒有對外捕獲變量的,就是GlobaBlock。

int b = 10;
    void(^block2)(void) = ^{
        NSLog(@"Hello, World! %d",b);
    };
    block2();
複製代碼

這種block,在MRC中,便是StackBlock。在ARC中,由於編譯器作了優化,自動進行了copy,這種就是MallocBlock了。

之因此作這種優化的緣由很好理解:

若是StackBlock訪問了一個auto變量,由於本身是存在Stack上的,因此變量也就會被保存在棧上。可是由於棧上的數據是由系統自動進行管理的,隨時都有可能被回收。很是容易形成野指針的問題。

怎麼解決呢?複製到堆上就行了!

ARC也是如此作的。它會自動將棧上的block複製到堆上,因此,ARC下的block的屬性關鍵詞其實使用strong和copy都不會有問題,不過爲了習慣,仍是使用copy爲好。

Blcok的類 副本源的配置存儲域 複製效果
__NSStackBlock
__NSGlobalBlock 程序的數據區域 無用
__NSMallocBlock 引用計數增長

系統默認調用copy方法把Block賦複製的四種狀況

  1. 手動調用copy
  2. Block是函數的返回值
  3. Block被強引用,Block被賦值給__strong或者id類型
  4. 調用系統API入參中含有usingBlcok的Cocoa方法或者GCD的相關API

ARC環境下,一旦Block賦值就會觸發copy,__block就會copy到堆上,Block也是__NSMallocBlock。ARC環境下也是存在__NSStackBlock的時候,這種狀況下,__block就在棧上。

如何截獲變量

這裏直接拿冰霜的文章來用

#import <Foundation/Foundation.h>

int global_i = 1;

static int static_global_j = 2;

int main(int argc, const char * argv[]) {
   
    static int static_k = 3;
    int val = 4;
    
    void (^myBlock)(void) = ^{
        global_i ++;
        static_global_j ++;
        static_k ++;
        NSLog(@"Block中 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
    };
    
    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog(@"Block外 global_i = %d,static_global_j = %d,static_k = %d,val = %d",global_i,static_global_j,static_k,val);
    
    myBlock();
    
    return 0;
}

複製代碼

運行結果

Block 外  global_i = 2,static_global_j = 3,static_k = 4,val = 5
Block 中  global_i = 3,static_global_j = 4,static_k = 5,val = 4
複製代碼

轉換的結果爲

int global_i = 1;

static int static_global_j = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_k;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_k = __cself->static_k; // bound by copy
  int val = __cself->val; // bound by copy

        global_i ++;
        static_global_j ++;
        (*static_k) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_0,global_i,static_global_j,(*static_k),val);
    }

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[]) {

    static int static_k = 3;
    int val = 4;

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));

    global_i ++;
    static_global_j ++;
    static_k ++;
    val ++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_6fe658_mi_1,global_i,static_global_j,static_k,val);

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

複製代碼

首先全局變量global_i和靜態全局變量static_global_j的值增長,以及它們被Block捕獲進去,這一點很好理解,由於是全局的,做用域很廣,因此Block捕獲了它們進去以後,在Block裏面進行++操做,Block結束以後,它們的值依舊能夠得以保存下來。

在__main_block_impl_0中,能夠看到靜態變量static_k和自動變量val,被Block從外面捕獲進來,成爲__main_block_impl_0這個結構體的成員變量了。 在執行Block語法的時候,Block語法表達式所使用的自動變量的值是被保存進了Block的結構體實例中,也就是Block自身中。

這麼來就清晰了不少,自動變量是以值傳遞方式傳遞到Block的構造函數裏面去的。Block只捕獲Block中會用到的變量。因爲只捕獲了自動變量的值,並不是內存地址,因此Block內部不能改變自動變量的值。

修改自動變量

截獲變量並修改有兩種方法 __block指針法(不過__block法歸根結底,其實也是操做指針)。 這裏描述一下指針法:

NSMutableString * str = [[NSMutableString alloc]initWithString:@"Hello,"];
    
    void (^myBlock)(void) = ^{
        [str appendString:@"World!"];
        NSLog(@"Block中 str = %@",str);
    };
    NSLog(@"Block外 str = %@",str);
    myBlock();
    
    const char *text = "hello";
    void(^block)(void) = ^{
        printf("%caaaaaaaaaaa\n",text[2]);
    };
    block();
複製代碼

直接操做指針去進行截獲,不過通常來說,這種方法多用於C語言數組的時候。使用OC的時候多數是使用__block。

這裏寫一個__block的捕獲代碼,使用剛纔的方法再來一次:

1.普通非對象的變量

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;//指向真正的block
 int __flags;
 int __size;
 int i;//對象
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

        (i->__forwarding->i) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_3b0837_mi_0,(i->__forwarding->i));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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[]) {
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

複製代碼

咱們能夠發現這裏多了兩個結構體

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};
複製代碼

這個實例內,包含了 __isa 指針、一個標誌位 __flags 、一個記錄大小的 __size 。最最重要的,多了一個 __forwarding 指針和 val 變量. 這裏長話短說,出來了一個新的 __forwarding指針,指向告終構體實例自己在內存的地址。

block經過指針的持續傳遞,將使用的自動變量值保存到了block的結構體實例中。在block體內修改 __block0變量,經過一系列指針指向關係,最終指向了__Block_byref_age_0結構體內與局部變量同名同類型的那個成員,併成功修改變量值。

在棧中, __forwarding指向了本身自己,可是若是複製到了堆上,__forwarding就指向複製到堆上的block,而堆上的block中的 __forwarding這時候指向了本身。

2.對象的變量

//如下代碼是在ARC下執行的
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
     
    __block id block_obj = [[NSObject alloc]init];
    id obj = [[NSObject alloc]init];

    NSLog(@"block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
    
    void (^myBlock)(void) = ^{
        NSLog(@"***Block中****block_obj = [%@ , %p] , obj = [%@ , %p]",block_obj , &block_obj , obj , &obj);
    };
    
    myBlock();
   
    return 0;
}
複製代碼

轉換以後

struct __Block_byref_block_obj_0 {
  void *__isa;
__Block_byref_block_obj_0 *__forwarding;//指向真正的block
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id block_obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id obj;
  __Block_byref_block_obj_0 *block_obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _obj, __Block_byref_block_obj_0 *_block_obj, int flags=0) : obj(_obj), block_obj(_block_obj->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_block_obj_0 *block_obj = __cself->block_obj; // bound by ref
  id obj = __cself->obj; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_1,(block_obj->__forwarding->block_obj) , &(block_obj->__forwarding->block_obj) , obj , &obj);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->block_obj, (void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->block_obj, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 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[]) {

    __attribute__((__blocks__(byref))) __Block_byref_block_obj_0 block_obj = {(void*)0,(__Block_byref_block_obj_0 *)&block_obj, 33554432, sizeof(__Block_byref_block_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};

    id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_45_k1d9q7c52vz50wz1683_hk9r0000gn_T_main_e64910_mi_0,(block_obj.__forwarding->block_obj) , &(block_obj.__forwarding->block_obj) , obj , &obj);

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, (__Block_byref_block_obj_0 *)&block_obj, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

複製代碼

在轉換出來的源碼中,咱們也能夠看到,Block捕獲了__block,而且強引用了,由於在__Block_byref_block_obj_0結構體中,有一個變量是id block_obj,這個默認也是帶__strong全部權修飾符的。

根據打印出來的結果來看,ARC環境下,Block捕獲外部對象變量,是都會copy一份的,地址都不一樣。只不過帶有__block修飾符的變量會被捕獲到Block內部持有。

在ARC中,對於聲明爲__block的外部對象,在block內部會進行retain,以致於在block環境內能安全的引用外部對象。

3. 實例變量

以前一直沒有想到過一個問題:

咱們知道不該該在block中使用實例變量,是由於會發生循環引用;那爲何會發生循環引用呢? 通常咱們會理解爲,一個_age的實例變量,其實是self->_age。那麼若是往下深究下去呢?

談談ivar的直接訪問的啓發,我也開始探索一下這裏的緣由。

寫以下的代碼:

#import <Foundation/Foundation.h>
#import "objc/runtime.h"

typedef void(^MyBlock)(void);

@interface MyObject : NSObject
@property (nonatomic) NSUInteger BRInteger;
@property (nonatomic, copy) NSString *BRString;
@property (nonatomic, copy) MyBlock BRBlock;

- (void)inits;

@end

@implementation MyObject
- (void)inits
{
    self.BRBlock = ^{
        _BRInteger = 5;
        _BRString = @"Balaeniceps_rex";
    };
}
@end int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MyObject *object = [MyObject new];
        [object inits];
    }
    return 0;
}
複製代碼

使用 clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations main.m命令進行轉換。獲得如下的代碼(爲了簡便,將代碼作了省略):

typedef void(*MyBlock)(void);


#ifndef _REWRITER_typedef_MyObject
#define _REWRITER_typedef_MyObject
typedef struct objc_object MyObject;
typedef struct {} _objc_exc_MyObject;
#endif

//對於每一個ivar,都有對應的全局變量
extern "C" unsigned long OBJC_IVAR_$_MyObject$_BRInteger;
extern "C" unsigned long OBJC_IVAR_$_MyObject$_BRString;
extern "C" unsigned long OBJC_IVAR_$_MyObject$_BRBlock;
//內部的結構
struct MyObject_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSUInteger _BRInteger;
    NSString *__strong _BRString;
    __strong MyBlock _BRBlock;
};

// @property (nonatomic) NSUInteger BRInteger;
// @property (nonatomic, copy) NSString *BRString;
// @property (nonatomic, copy) MyBlock BRBlock;

// - (void)inits;

/* @end */


// @implementation MyObject

struct __MyObject__inits_block_impl_0 {
    struct __block_impl impl;
    struct __MyObject__inits_block_desc_0* Desc;
    MyObject *const __strong self;
    
    //注意這裏捕捉了self
    __MyObject__inits_block_impl_0(void *fp, struct __MyObject__inits_block_desc_0 *desc, MyObject *const __strong _self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

//block的函數方法(也就是方法layout中第四行的那個)
static void __MyObject__inits_block_func_0(struct __MyObject__inits_block_impl_0 *__cself) {
    MyObject *const __strong self = __cself->self; // bound by copy
    //這裏是經過self的地址,那倒全局變量的偏移去獲取實例變量的地址
    (*(NSUInteger *)((char *)self + OBJC_IVAR_$_MyObject$_BRInteger)) = 5;
    (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_MyObject$_BRString)) = (NSString *)&__NSConstantStringImpl__var_folders_m1_05zb_zbd1g1f8k27nc6yn_th0000gn_T_main_e9db32_mi_0;
}
static void __MyObject__inits_block_copy_0(struct __MyObject__inits_block_impl_0*dst, struct __MyObject__inits_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

static struct __MyObject__inits_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __MyObject__inits_block_impl_0*, struct __MyObject__inits_block_impl_0*);
    void (*dispose)(struct __MyObject__inits_block_impl_0*);
} __MyObject__inits_block_desc_0_DATA = { 0, sizeof(struct __MyObject__inits_block_impl_0), __MyObject__inits_block_copy_0, __MyObject__inits_block_dispose_0};

static void _I_MyObject_inits(MyObject * self, SEL _cmd) {
    ((void (*)(id, SEL, MyBlock))(void *)objc_msgSend)((id)self, sel_registerName("setBRBlock:"), ((void (*)())&__MyObject__inits_block_impl_0((void *)__MyObject__inits_block_func_0, &__MyObject__inits_block_desc_0_DATA, self, 570425344)));
}

static NSUInteger _I_MyObject_BRInteger(MyObject * self, SEL _cmd) { return (*(NSUInteger *)((char *)self + OBJC_IVAR_$_MyObject$_BRInteger)); }
static void _I_MyObject_setBRInteger_(MyObject * self, SEL _cmd, NSUInteger BRInteger) { (*(NSUInteger *)((char *)self + OBJC_IVAR_$_MyObject$_BRInteger)) = BRInteger; }

static NSString * _I_MyObject_BRString(MyObject * self, SEL _cmd) { return (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_MyObject$_BRString)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_MyObject_setBRString_(MyObject * self, SEL _cmd, NSString *BRString) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyObject, _BRString), (id)BRString, 0, 1); }

static void(* _I_MyObject_BRBlock(MyObject * self, SEL _cmd) )(){ return (*(__strong MyBlock *)((char *)self + OBJC_IVAR_$_MyObject$_BRBlock)); }
static void _I_MyObject_setBRBlock_(MyObject * self, SEL _cmd, MyBlock BRBlock) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct MyObject, _BRBlock), (id)BRBlock, 0, 1); }
// @end

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        
        MyObject *object = ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("new"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)object, sel_registerName("inits"));
    }
    return 0;
}
複製代碼

咱們能夠發現,每一個實例變量都是被建立了對應的全局變量:

extern "C" unsigned long OBJC_IVAR_$_MyObject$_BRInteger;
extern "C" unsigned long OBJC_IVAR_$_MyObject$_BRString;
extern "C" unsigned long OBJC_IVAR_$_MyObject$_BRBlock;
複製代碼

下面是block的layout中的第四排的函數調用方法。

//block的函數方法(也就是方法layout中第四行的那個)
static void __MyObject__inits_block_func_0(struct __MyObject__inits_block_impl_0 *__cself) {
    MyObject *const __strong self = __cself->self; // bound by copy
    //這裏是經過self的地址,那倒全局變量的偏移去獲取實例變量的地址
    (*(NSUInteger *)((char *)self + OBJC_IVAR_$_MyObject$_BRInteger)) = 5;
    (*(NSString *__strong *)((char *)self + OBJC_IVAR_$_MyObject$_BRString)) = (NSString *)&__NSConstantStringImpl__var_folders_m1_05zb_zbd1g1f8k27nc6yn_th0000gn_T_main_e9db32_mi_0;
}
複製代碼

經過這裏,咱們其實也能發現,這裏是經過self的偏移去獲取實例變量的地址,也是和self息息相關的。

若是這個還不會證實實例變量中的self的做用的話,咱們接着往下看;

struct __MyObject__inits_block_impl_0 {
    struct __block_impl impl;
    struct __MyObject__inits_block_desc_0* Desc;
    MyObject *const __strong self;
    
    //注意這裏捕捉了self
    __MyObject__inits_block_impl_0(void *fp, struct __MyObject__inits_block_desc_0 *desc, MyObject *const __strong _self, int flags=0) : self(_self) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
複製代碼

在這個方法裏,咱們能夠發現,在block當中,其實也引用到MyObject,是一個強引用的self!而block的構造函數中也屢次引用了self。

咱們若是瞭解過property的話,也會知道實例變量是在編譯期就肯定地址了。內部實現的全局變量就表明了地址的offset。

來看一道問題

若是咱們把block設置爲nil,而後去調用,會發生什麼?

void (^block)(void) = nil;
block();
複製代碼

當咱們運行的時候,它會崩潰,報錯信息爲 Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)

置爲nil的block
咱們能夠發現,當把block置爲nil的時候,第四行的函數指針,被置爲NULL,注意,這裏是NULL而不是nil。

咱們給一個對象發送nil消息是沒有問題的,可是給若是是NULL就會發生崩潰。

  • nil:指向oc中對象的空指針
  • Nil:指向oc中類的空指針
  • NULL:指向其餘類型的空指針,如一個c類型的內存指針
  • NSNull:在集合對象中,表示空值的對象
  • 若obj爲nil:[obj message]將返回NO,而不是NSException
  • 若obj爲NSNull:[obj message]將拋出異常NSException

它直接訪問到了函數指針,由於前三位分別是void、int、int,大小分別是八、四、4,加一塊就爲16,因此在十六位中,就表示出0x10地址的崩潰。 若是是在32位的系統中,void的大小是4,崩潰的地址應該就是0x0c。

下一篇文章block(二):block的copy

引用

Blocks Programming Topics
Working with Blocks
fuckingblocksyntax.com

相關文章
相關標籤/搜索