MJiOS底層筆記--Block

本文屬筆記性質,主要針對本身理解不太透徹的地方進行記錄。ios

推薦系統直接學習小碼哥iOS底層原理班---MJ老師的課確實不錯,強推一波。c++


基本認識

block實際上爲一個代碼塊

只有在末尾執行()纔會調用bash

^{
    NSLog(@"this is a block!");
    NSLog(@"this is a block!");
    NSLog(@"this is a block!");
    NSLog(@"this is a block!");
};
複製代碼

承接一個block

// 不帶參數的block。^(){能夠簡化成^{

void (^block)() =  ^(){
    
};
block();


// 帶參數的block
void (^block)(int, int) =  ^(int a , int b){
    NSLog(@"a=%d,b=%d",a,b);
};
block(20, 10);
複製代碼

對於void (^block)(int, int)的含義是返回值 (^名稱)(參數1, 參數2)函數


block的本質

封裝了函數調用以及調用環境的OC對象學習

1. block本質上也是OC對象,內部也存在isa指針

2. block內部封裝了函數調用,以及函數調用所需的環境(參數)

具體經過cpp文件可知:

int age = 20;

void (^block)(int, int) =  ^(int a , int b){
    NSLog(@"this is a block! -- %d", age);

};


block(10, 10);
複製代碼

block最終將會被轉化成以下格式的結構體(每一個block的細節不一樣)ui

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //block描述信息(大小等)
  int age;  //封裝了函數調用所需環境(內部定義了一個age變量)
  
  
  //c++的構造函數  age(_age)表示_age將會自動賦值給age
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
  
};


static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    int age = __cself->age; // 將block當初捕獲的變量,賦值給執行函數

    //函數調用
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
 
}


struct __block_impl {
  void *isa;  //代表block也屬於OC對象
  int Flags;
  int Reserved;
  void *FuncPtr;
};
複製代碼

其中函數調用會被單獨封裝成__main_block_func_0方法。在block定義時,傳入block結構體。this

void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
複製代碼

而在block結構體中,會被賦值給impl.FuncPtr = fp;,將函數地址存儲在block內部。spa

最終,在調用block時,獲取FuncPtr,傳入參數執行調用。3d

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
複製代碼

簡化一下cpp的block語句

構建一個block結構體指針

void (*block)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

/**
去掉強轉的語法以後

將函數地址,block描述(大小等)信息,須要被捕獲的變量。
傳入構建block的方法`__main_block_impl_0`中進行構建
最後將`__main_block_impl_0`block結構體返回,並將其持有
**/
void (*block)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age));
複製代碼

調用block結構體

((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);


/**
去掉強轉的語法以後

從結構體中取出函數地址,傳入參數並調用。
傳入blcok是由於每個block對象內部所捕獲的變量不一樣
**/
block->FuncPtr(block, 10, 10);

複製代碼

變量捕獲

爲了保證block內部可以正常訪問外部的變量,block有個變量捕獲機制

auto變量

c語言中的局部變量,默認都爲auto變量。因此auto代指局部變量

因爲auto變量的生命超出做用域就會被銷燬。爲保證block可以正常執行,auto變量在被block捕獲時,會將傳遞給block的構造函數。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        auto int a = 10;
        void (^block)(void) = ^{
            NSLog(@"age is %d", a); //age is 10
        };
        a = 20;
        block();
    }
    return 0;
}



struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //block信息(大小等)
  int age;  //封裝了函數調用所需環境(內部定義了一個age變量)
  
  
  //c++的構造函數  age(_age)表示_age將會自動賦值給age
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
  
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
    int age = __cself->age; // 將block當初捕獲的變量,賦值給執行函數

    //函數調用
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_3f4c4a_mi_0, age);
 
}



// main中構建block。將a的值傳遞給構造函數
void (*block)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a));
複製代碼

static變量

因爲靜態變量的生命常駐於內存,但使用僅限於做用域內部。因此靜態變量在被block捕獲時,只要將指向變量值的指針(地址)傳遞給構造函數,即能保證block正常執行。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int b = 10;
        void (^block)(void) = ^{
            NSLog(@"height is %d", b); //height is 20
        };
        b = 20;
        block();
    }
    return 0;
}


struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int *b; //b爲指針類型
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int *_b, int flags=0) :  b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};


static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
    
    int *b = __cself->b; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_fd2a14_mi_0, (*b));
}


// main中構建block.將b的指針傳遞給構造函數
void (*block)() = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, &b));

複製代碼

全局變量

因爲全局變量的生命常駐於內存,而且使用不受做用域限制。因此全局變量並不須要被捕獲,在執行block的函數調用時,直接使用全局變量,即能保證block正常執行。

int age_ = 10;
static int height_ = 10;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"age_ is %d ,height_ is %d", age_,height_);
        };
        age_ = 20;
        height_ = 20;
        block();
    }
    return 0;
}



int age_ = 10;
static int height_ = 10;

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

    // 執行函數中,直接使用全局變量
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_6f323a_mi_0, age_,height_);
        }

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 (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));  //在執行block構造函數時,並未將全局變量傳遞進去
        age_ = 20;
        height_ = 20;
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

複製代碼

block的類型

block與class

經過class方法,也能夠證實block的本質是OC對象

void test()
{
    void (^block)(void) = ^{
        NSLog(@"Hello");
    };
    
    NSLog(@"%@", [block class]);//__NSGlobalBlock__ (其餘類型的block爲__NSStackBlock__或__NSMallocBlock__)
    NSLog(@"%@", [[block class] superclass]);//__NSGlobalBlock
    NSLog(@"%@", [[[block class] superclass] superclass]);//NSBlock
    NSLog(@"%@", [[[[block class] superclass] superclass] superclass]);//NSObject
}
複製代碼

block的isa

在block結構體中,isa會被指定成_NSConcreteXXXBlock的值。這個值即是block的類型。

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *obj;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼

三種block類型

block有3種類型,能夠經過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型

  • NSGlobalBlock ( _NSConcreteGlobalBlock )

  • NSStackBlock ( _NSConcreteStackBlock )

  • NSMallocBlock ( _NSConcreteMallocBlock )

從上到下爲:低地址 --》高地址 非ARC下的block來源

block與copy

在非ARC下

絕大部分block默認都是做爲__NSStackBlock__存在於棧上(沒有訪問auto變量的block會存在於全局區)。

__NSStackBlock__在超出做用域後,block結構體有可能會被修改污染。

這時須要將__NSStackBlock__進行copy將其轉移到堆中進行管理。

以後__NSStackBlock__將轉變爲__NSMallocBlock__

這也是爲何block做爲屬性都會聲明爲copy(不過ARC下聲明爲strong也會自動copy,文檔上說是爲了語義)

對於不一樣類型的block,調用copy會有不一樣操做

ARC下的block

在ARC環境下,編譯器會根據狀況自動將棧上的block複製到堆上,好比如下狀況

  1. block做爲函數返回值時
MJBlock myblock()
{
    return ^{
        NSLog(@"---------");
    };
}
複製代碼
  1. 將block賦值給__strong指針
MJBlock block = ^{
    NSLog(@"---------%d", age);
};
複製代碼
  1. block做爲Cocoa API中方法名含有usingBlock的方法參數時
[@[] enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    
}];
複製代碼
  1. block做爲GCD API的方法參數時
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
});
複製代碼

block與對象類型的auto變量

(這裏都說ARC環境下)

在block捕獲對象類型的auto變量時。__main_block_desc_0結構體內部會多出__main_block_copy_0__main_block_dispose_0函數,在block移動到堆空間時堆對象進行適當的return和release。

先貼一份cpp代碼(auto變量)

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [NSObject new]; //OC對象
        void (^block)(void) = ^{
            NSLog(@"obj is %@", obj); //捕獲
        };
        obj = nil;
        block();
    }
    return 0;
}




//block結構體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  //若是外部爲__weak內部也會爲__weak
  NSObject * __strong obj; //對於auto變量,結構體中保存變量與本來相同。
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *obj = __cself->obj; // bound by copy

  NSLog((NSString *)&__NSConstantStringImpl__var_folders_tz_hcmmb5t57v1cr81ydm6s5s140000gn_T_main_d73acf_mi_0, obj);
}


//當block從棧移動到堆中時,執行此方法。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {

    //會根據auto變量的修飾符(__strong、__weak、__unsafe_unretained)作出相應的操做,造成強引用(retain)或者弱引用
    _Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

//當block從堆中移除(釋放)時,執行此方法。
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    //release引用的變量
    _Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);
}


//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};


// main函數
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        
        //在構造block時,將OC對象傳入
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));

        
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

複製代碼

對象的捕獲方面

基本也符合上面的規律,以局部變量爲例。

只是對於對象而言,傳遞的都是指針。 並且爲了保證OC對象在block內部正常訪問,會被強引用以延長對象生命週期。

對於棧上的block

若是block是在棧上,將不會對auto變量產生強引用

當block被拷貝到堆上

會執行內部的_Block_object_assign函數,結構體內所捕獲的auto對象的類型(__strong/__weak)決定如何對其進行引用。

當block從堆中移除(釋放)

會執行內部的_Block_object_dispose函數,將結構體內所捕獲的auto對象進行release。


__block

編譯器會將__block變量包裝成一個對象(__Block_byref_age_0),被聲明的值做爲對象的屬性存在。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        
        MJBlock block = ^{
            __strong int myage = age;
            age = 20;
            NSLog(@"age is %d", age);
        };

        
        block();
        
    }
    return 0;
}


//__block對象結構體
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

//block結構體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSObject *p;
  
  //再也不是int age;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_p, __Block_byref_age_0 *_age, int flags=0) : p(_p), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// block執行函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    __Block_byref_age_0 *age = __cself->age; // bound by ref
    
    //從__Block_byref_age_0結構體中得到age變量,而且修改
    (age->__forwarding->age) = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_e2457b_mi_0);
}


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

    
    //__block int age = 10;
    __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
    //等價於
    __Block_byref_age_0 age = {0,
                                &age,
                                0,
                                sizeof(__Block_byref_age_0),
                                10};
        
        
    //構建block結構體
    MJBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, p, (__Block_byref_age_0 *)&age, 570425344));
    //等價於
        MJBlock block = &__main_block_impl_0(__main_block_func_0,
                                         &__main_block_desc_0_DATA,
                                         p,
                                         &age,
                                         570425344);

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    }
    return 0;
}
複製代碼

外部使用__block對象時

實際上都是對結構體中的age指針進行操做,而不是結構體age。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        age = 50;
    }
    return 0;
}



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

        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        (age.__forwarding->age) = 50;
    }
    return 0;
}
複製代碼

__forwarding指針

__forwarding指向__block所包裝的對象實體,以確保使用時的正確性。

  • 當block在棧中,__forwarding所指實體爲棧上的__block包裝對象。

  • 當block移動到堆上,__block包裝對象在堆中也會被複制一份。而兩者的__forwarding指針都指向堆中的__block包裝對象。

__block修飾OC對象

OC對象的強弱引用不會體如今block結構體中(都是strong),而是體如今__Block_byref結構體中。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        
        __block __weak JSPerson *weakPerson = person;
        MJBlock block = ^{
            NSLog(@"%p", weakPerson);
        };

        block();

    }
    return 0;
}


struct __Block_byref_weakPerson_0 {
  void *__isa; // 8
__Block_byref_weakPerson_0 *__forwarding; // 8
 int __flags; // 4
 int __size; // 4
 void (*__Block_byref_id_object_copy)(void*, void*); // 當block被移動到堆,會對__Block_byref對象進行return(須要注意MRCblock進入堆中時不會retain該變量)
 void (*__Block_byref_id_object_dispose)(void*); // 當block從堆中移除,會對__Block_byref對象進行release
 MJPerson *__weak weakPerson; //__block包裝的結構體中實際爲weak引用。
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_weakPerson_0 *weakPerson; // // 依舊是strong引用
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakPerson_0 *_weakPerson, int flags=0) : weakPerson(_weakPerson->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製代碼

三個已知的內存地址段

NSLog(@"數據段:age %p", &age);
NSLog(@"棧:a %p", &a);
NSLog(@"堆:obj %p", [[NSObject alloc] init]);
複製代碼
相關文章
相關標籤/搜索