主要講解 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);
}
兩個地址是一樣的; 由于substruct是 superstruct 的第一個變量, 所以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ū)別