iOS Block 部分一

主要講解 Block 的底層實現(xiàn)原理;

Block部分一
Block部分二
Block部分三
Block知識點總結

基礎知識 C++中結構體的構造函數(shù)是怎么實現(xiàn)的?

很多 OC 對象低層都轉化為了C++的結構體, 由于不懂 C++的語法, 所以網(wǎng)上查了下一些C++結構體的基礎知識;
結構體的構造函數(shù)

 typedef struct Test{
    int id;
    string name;
     // 用以不初始化就構造結構體
    Test(){} ;
    //只初始化name
    Test(string _name) {
      name = _name;
    }
    /*
    同時初始化id,name , 把入?yún)⒌?id 和 name 為結構體中的 id 和 name 賦值;
    參考下方 OC 類的構造函數(shù)
    */
    Test(inr _id,string _name): id(_id),name(_name)}{};
   }; 

類似于我們的構造函數(shù)實現(xiàn);

///.h 文件聲明
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject

- (instancetype)initWithName:(NSString *)name
                         age:(NSInteger)age;
@end
NS_ASSUME_NONNULL_END
///.m文件實現(xiàn)
#import "Person.h"

@interface Person ()
@property (nonatomic, strong) NSString  *name;
@property (nonatomic, assign) NSInteger age;
@end

@implementation Person
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age {
    self = [super init];
    if (self) {
        self.name = name;
        self.age = age;
    }
    return  self;
}
@end

1. 什么是Block?

Block是一個對象, 對象中封裝了一個函數(shù)以及函數(shù)執(zhí)行的上下文;Block的調用本質是函數(shù)的調用;


2. Block的底層實現(xiàn)是什么?

block 的底層是一個結構體__XXXX_block_impl_0

測試代碼如下, 一個最簡單的Block; 通過指令轉換為C++文件后得到如下結果;

///viewDidLoad中寫一個最簡單的block
- (void)viewDidLoad {
    [super viewDidLoad];
    ///case1  block的底層結構
     ^{
        NSLog(@"Block內(nèi)部內(nèi)容;");
        NSLog(@"Block內(nèi)部內(nèi)容;");
        NSLog(@"Block內(nèi)部內(nèi)容;");
        NSLog(@"Block內(nèi)部內(nèi)容;");
      };
}

通過指令轉化為C++文件, 底層實現(xiàn)為

static void _I_ViewController1_viewDidLoad(ViewController1 * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController1"))}, sel_registerName("viewDidLoad"));
  /*
    Block的底層實現(xiàn)如下;  __XXXX_block_impl_0結構體即為block的底層結構
    通過__XXXX_block_impl_0構造函數(shù)來初始化這個結構體, 入?yún)蓚€參數(shù)
    __XXXX_block_func_0: 實際block中需要執(zhí)行的的代碼塊
    __XXXXX_block_desc_0_DATA: block 的一些信息
  */    
((void (*)())&__ViewController1__viewDidLoad_block_impl_0((void *)__ViewController1__viewDidLoad_block_func_0, &__ViewController1__viewDidLoad_block_desc_0_DATA));
}
===>
block 的底層為一個結構體__XXXX_block_impl_0;
///__XXXX_block_impl_0的結構如下
struct __ViewController1__viewDidLoad_block_impl_0 {
  ///bock 的實現(xiàn)信息
  struct __block_impl impl;
  ///block 的描述信息
  struct __ViewController1__viewDidLoad_block_desc_0* Desc;
  __ViewController1__viewDidLoad_block_impl_0(void *fp, struct 
  /// block 的構造函數(shù), 為結構體里面的變量賦值, 參考 OC 類的構造函數(shù)
__ViewController1__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    ///block中需要執(zhí)行的代碼塊, 具體放在內(nèi)存中的代碼段;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
===>
///實際block中的代碼塊(這個 block 中就是那4個 NSLog)
static void __ViewController1__viewDidLoad_block_func_0(struct __ViewController1__viewDidLoad_block_impl_0 *__cself) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_0);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_1);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController1_b5ce37_mi_3);
}

===>
///block 的實現(xiàn)信息
struct __block_impl {
  ///isa 指針
  void *isa;
  ///標志; 通過構造函數(shù)可以看到傳入的是0; 
  int Flags;
  ///保留字段
  int Reserved;
  /*
    block 中需要執(zhí)行的代碼塊封裝的函數(shù)地址;
   __XXXX_block_impl_0結構體的構造函數(shù)會為其賦值
*/
  void *FuncPtr;
};

===>
///block的一些描述信息
static struct __ViewController1__viewDidLoad_block_desc_0 {
  ///保留字段
  size_t reserved;
  ///block 所占的空間(看下面對sizeof(struct __main_block_impl_0)即可得知)
  size_t Block_size;
} __ViewController1__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController1__viewDidLoad_block_impl_0)};

總結底層的幾個結構體和函數(shù)
XXXX_block_impl_0 : 就是block對象的底層結構體;
__block_impl: 就是封裝了block具體實現(xiàn)的結構體;
XXXX_block_func_0: 就是封裝了block內(nèi)部執(zhí)行代碼塊的函數(shù);
XXXX_block_desc_0: 就是封裝了block的一些基本信息;

注意: 如果訪問了auto變量(ARC), 因為對block進行了copy到堆區(qū)的操作, 所以通過clang后會出現(xiàn)兩套block, 而調用部分會變成 XXXX_block_impl_1, XXXX_block_func_1等, 因為這個是對XXXX_block_impl_0, XXXX_block_func_1的拷貝后的;


3. 如何定義Block并且通過名字調用的?

在弄清這個問題首先印證一個問題;一個結構體嵌套結構體時, 如果第一個變量是結構體, 則可以通過內(nèi)存地址直接訪問內(nèi)層結構體中的變量;代碼如下:

#import "ViewController2.h"
@interface ViewController2 ()
@end
///case2的印證結構體嵌套時使用
struct SubStruct {
    int a;
};
struct SuperStruct {
    struct SubStruct  subs;
    int b;
};
@implementation ViewController2
- (void)viewDidLoad {
    [super viewDidLoad];
    struct SuperStruct supers = {{10}, 2};
    NSLog(@"%p    %p", &(supers.subs), &supers);
    ///(強制轉換為int值)(強制轉換為SubStruct)&取supers的地址->訪問SubStruct中變量
    int subStruct_a = (int)((struct SubStruct *)&supers)->a;
    NSLog(@"通過外層結構體地址強行訪問內(nèi)存結構體的變量 %d", subStruct_a);
 }

兩個地址是一樣的; 由于substructsuperstruct 的第一個變量, 所以substruct和外層的superstruct地址是一樣的;

2020-06-24 17:19:15.005827+0800 BlockMore1[6213:90176] 0x7ffee413cc88    0x7ffee413cc88

可以通過內(nèi)存地址方式superstruct調用內(nèi)層substruct變量;

2020-06-24 17:19:15.005918+0800 BlockMore1[6213:90176] 通過外層結構體地址強行訪問內(nèi)存結構體的變量 10

通過下面代碼來探究block的調用方式;

    ///case2  定義一個 block 然后調用
    void(^Case2Block)(void) =  ^{
        NSLog(@"Block內(nèi)部內(nèi)容;");
        NSLog(@"Block內(nèi)部內(nèi)容;");
        NSLog(@"Block內(nèi)部內(nèi)容;");
        NSLog(@"Block內(nèi)部內(nèi)容;");
    };
    Case2Block();

通過指令轉換為C++文件后的代碼為

      ///Case2Block的定義過程
     void(*Case2Block)(void) = ((void (*)())&__ViewController2__viewDidLoad_block_impl_0((void *)__ViewController2__viewDidLoad_block_func_0, &__ViewController2__viewDidLoad_block_desc_0_DATA));
      ///Case2Block的實際調用
     ((void (*)(__block_impl *))((__block_impl *)Case2Block)->FuncPtr)((__block_impl *)Case2Block);

直接看不夠直觀; 我們將一些強制轉換的相關代碼去掉;

  • 定義部分
    強制轉換相關的代碼部分去掉后,定義block過程偽代碼大致如下, 調用__XXXX_block_impl_0的構造函數(shù), 兩個入?yún)?br> __XXXX_block_func_0: 實際block中需要執(zhí)行的代碼塊,
    &__XXXX_block_desc_0_DATA: 這個 block一些信息;
    實際第三個參數(shù)flags, 由于構造函數(shù)中直接賦值 flags=0(case1的講解), 所以這里不用傳入;
    最后的結果就是講生成的結構體的通過&取地址然后賦值給Case2Block指針;
    簡化后為: void(*Case2Block) = &__XXXX_block_impl_0(__XXXX_block_func_0,&__XXXX_block_desc_0_DATA)
  • 調用部分
    強制轉換相關的代碼部分去掉后,調用block過程偽代碼大致如下
    Case2Block的地址就是__XXXX_block_impl_0地址, 通過上面結構體嵌套的論證, 我們可以知道
    這種方式最后調用的是__block_impl結構體中的FuncPtr(就是實際Case2Block中的需要執(zhí)行的代碼塊);
    簡化后為: Case2Block->FuncPtr(Case2Block)

4. 什么是 Block 的變量捕獲(capture)?

首先看下方代碼, 我們都知道最后的 打印結果為 a = 100, b = 200, c = 3, d = 400; 原因是 a是全局變量, b是全局靜態(tài)變量, c是局部變量, d是靜態(tài)局部變量; 但是為什么會這樣? 我們從源碼角度分析下為什么;


#import "ViewController3.h"
@interface ViewController3 ()
@end

int a = 1;//全局變量, 整個工程內(nèi)都有效;
static int b = 2;//靜態(tài)全局變量, 只在定義它的文件內(nèi)有效;
@implementation ViewController3
- (void)viewDidLoad {
    [super viewDidLoad];
    /******************************************************************/
    ///case3   block 的變量捕獲
    int c = 3;//局部變量, 只在定義他的函數(shù)內(nèi)有效;1
    static int d = 4;//靜態(tài)局部變量, 只在定義他的函數(shù)內(nèi)有效, 且內(nèi)存只分配一次;
    /*
    block將局部變量捕獲到block內(nèi)部, 主要一個是捕獲值, 一個是捕獲址;全局變量不捕獲;
    */
    void(^Case3Block)(void) =  ^{
        NSLog(@"a = %d, b = %d, c = %d, d = %d", a, b, c, d);
    };
    a = 100;
    b = 200;
    c = 300;
    d = 400;
    Case3Block();
}
@end

2020-06-25 09:50:45.294219+0800 BlockMore1[2775:24440] a = 100, b = 200, c = 3, d = 400

將上方的代碼通過指令編譯為C++文件后, 我們可以看到Case3Block的相關代碼如下

/*
注意: 全局變量a和b底層實現(xiàn)也是如此, 并沒有發(fā)生變化;
*/
int a = 1;
static int b = 2;
/*
Case3Block的底層實現(xiàn),已經(jīng)變化, block內(nèi)部重新定義兩個變量, 一個全新的c變量, 和一個地址變量d; 
*/
struct __ViewController3__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController3__viewDidLoad_block_desc_0* Desc;
  int c;
  int *d;
  __ViewController3__viewDidLoad_block_impl_0(void *fp, struct __ViewController3__viewDidLoad_block_desc_0 *desc, int _c, int *_d, int flags=0) : c(_c), d(_d) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

看過上方源碼后相信就可以很直觀的得知:
全局變量, block內(nèi)部并不會捕獲創(chuàng)建新的變量進行存儲, 所以更改全局變量后,仍然是直接調用全局變量;
局部變量: block會進行變量捕獲, 局部變量是捕獲值, 靜態(tài)局部變量捕獲址;


補充: self是全局變量還是局部變量?block內(nèi)是否會捕獲self?

self是局部變量, block內(nèi)部會捕獲self;
通過底層代碼驗證捕獲self;

@interface ViewController31 () {
    int  _age;
}
@property (nonatomic, assign) int  count;
@end
@implementation ViewController31
- (void)viewDidLoad {
    [super viewDidLoad];
    ///不論是訪問成員變量還是訪問屬性, 最終都是會將self捕獲到self內(nèi)部;
    ^ {
        NSLog(@"age = %d,  count = %d", _age, self.count);
    };
static void _I_ViewController31_viewDidLoad(ViewController31 * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController31"))}, sel_registerName("viewDidLoad"));
    ///block的定義
((void (*)())&__ViewController31__viewDidLoad_block_impl_0((void *)__ViewController31__viewDidLoad_block_func_0, &__ViewController31__viewDidLoad_block_desc_0_DATA, self, 570425344));
}

///block的實現(xiàn)
struct __ViewController31__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController31__viewDidLoad_block_desc_0* Desc;
  ///新增變量*self, 用來存儲self
  ViewController31 *self;
  __ViewController31__viewDidLoad_block_impl_0(void *fp, struct __ViewController31__viewDidLoad_block_desc_0 *desc, ViewController31 *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

通過底層代碼驗證self是局部變量;

- (void)test31 {
    NSLog(@"實例方法self指向當前實例對象");
}

+ (void)test32 {
    NSLog(@"類方法self指向當前類對象");
}

所有的實例方法中底層默認傳入*self指向此實例對象; 所以可以得知self是局部變量; 類方法中調用self是指向當前類對象;但是無論是實例方法還是類方法轉換為C++文件可以看到都是有入?yún)?code>self參數(shù)的;

 /*
每個實例方法的底層, 是默認會入?yún)蓚€參數(shù)
*self  指向當前的實例對象
_cmd 指向當前的方法
  */
static void _I_ViewController31_test31(ViewController31 * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController31_ab9aa9_mi_1);
}

 /*
每個類方法的底層, 是默認會入?yún)蓚€參數(shù)
  self 指向當前的類對象
_cmd 指向當前的方法
  */
static void _C_ViewController31_test32(Class self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_x9__266tpqd76sb1c4cm_mvpjz40000gn_T_ViewController31_ab9aa9_mi_2);
}

通過指令將OC文件轉換為C++文件
指令: xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件.m -o 文件-arm64.cpp
如果需要鏈接其他框架, 使用-framework參數(shù); 例:-framework UIKit


參考文章和下載鏈接
測試代碼
iOS clang指令報錯問題總結
Apple 一些源碼的下載地址
C++中結構體的構造函數(shù)
全局變量、靜態(tài)全局變量、靜態(tài)局部變量和普通局部變量的區(qū)別

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

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