block常見的使用方式以下:ios
// 無參無返回值
void(^MyBlockOne)(void) = ^(void) {
NSLog(@"無參數, 無返回值");
};
MyBlockOne();
// 有參無返回值
void (^MyBlockTwo)(int a) = ^(int a) {
NSLog(@"a = %d", a);
};
MyBlockTwo(10);
//有參有返回值
int (^MyBlockThree)(int, int) = ^(int a, int b) {
NSLog(@"return %d", a + b);
return 10;
};
MyBlockThree(10, 20);
// 無參有返回值
int (^MyBlockFour)(void) = ^(void) {
NSLog(@"return 10");
return 10;
};
// 聲明爲某種類型
typedef int (^MyBlock) (int, int);
@property (nonatomic, copy) MyBlock block;
複製代碼
結論: block
的內部存在isa
指針,其本質就是封裝了函數調用
和函數調用環境
的OC對象
。objective-c
main
函數中定義一個block
,以下swift
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^(void) {
NSLog(@"this is first block");
};
block();
}
return 0;
}
複製代碼
終端進入項目所在目錄,經過xcrun
命令將OC代碼
轉爲C++代碼
:markdown
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
複製代碼
轉換結果以下:iphone
// 1. 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;
}
};
// block 內部impl結構體,存儲isa指針,block方法的地址。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 方法地址
};
// block 的描述信息,如:block的大小
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c441779a345740a98accb31ac195d61f~tplv-k3u1fbpfcp-zoom-1.image)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)};
// 2. block 的方法實現
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_main_cf18a7_mi_0);
}
// 3. main方法的實現
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));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
將生成的main
方法簡化後得:async
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
block->FuncPtr(block);
}
return 0;
}
複製代碼
看簡化後的代碼,你是否是有疑問, 爲何block->FuncPtr(block)
這句話能調用成功,明明FuncPtr
是__block_impl
類型裏的成員,爲何能夠直接使用block調用
?函數
緣由其實很簡單,由於在block
結構體__main_block_impl_0
內,__block_impl
是第一個成員變量,所以block
的地址和impl
的地址是相同的。二者能夠進行強制轉換。oop
根據轉換結果:測試
OC
中定義的block
底層其實就是一個C++ 的結構體__main_block_impl_0
。結構體有兩個成員變量impl
、Desc
,分別是結構體類型 __block_impl
、__main_block_desc_0
。__block_impl
內包含了isa
指針和指向函數實現的指針FuncPtr
。__main_block_desc_0
內 Block_size
成員存儲着Block的大小
。由上可知,block
內部有一個isa
指針,所以,block本質其實就是一個OC對象
。ui
若是block
是一個OC對象,那它最終確定繼承自NSObject
類(NSProxy除外
),所以咱們能夠直接打印出block
繼承鏈看一下就知道了。
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^(void) {
NSLog(@"this is first block");
};
NSLog(@"class = %@", [block class]);
NSLog(@"superclass = %@", [block superclass]);
NSLog(@"superclass superclass = %@", [[block superclass] superclass]);
NSLog(@"superclass superclass superclass = %@", [[[block superclass] superclass] superclass]);
}
return 0;
}
輸出結果:
2020-07-28 19:25:24.475317+0800 LearningBlock[39445:591948] class = __NSGlobalBlock__
2020-07-28 19:25:24.475707+0800 LearningBlock[39445:591948] superclass = __NSGlobalBlock
2020-07-28 19:25:24.475762+0800 LearningBlock[39445:591948] superclass superclass= NSBlock
2020-07-28 19:25:24.475808+0800 LearningBlock[39445:591948] superclass superclass superclass= NSObject
複製代碼
block
的繼承鏈: __NSGlobalBlock
-> NSBlock
-> NSObject
能夠看出block最終繼承自NSObject的
。isa指針
其實就是由NSObject
來的。 所以block
本質就是一個OC
對象。
爲了保證block
內部可以正常訪問外部的值,block
有個變量捕獲的機制。下面來一塊兒來探索如下block的變量捕獲機制
。
代碼:
int a = 10; // 全局變量, 程序運行過程一直存在內存。
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int b = 20; // 局部變量,默認是auto修飾,通常能夠不寫auto,所在做用域結束後會被銷燬。
static int c = 30; // 靜態變量,程序運行過程當中一直存在內存。
void(^block)(void) = ^(void) {
NSLog(@"a = %d, b = %d, c = %d", a, b, c);
};
// 觀察調用block時,a,b,c 的值是多少呢?
a = 11;
b = 21;
c = 31;
block(); // 調用block
}
return 0;
}
打印輸出:
2020-07-28 19:43:41.729849+0800 LearningBlock[39648:603167] a = 11, b = 20, c = 31
複製代碼
由打印結果來看,b
沒有改變, 而a
和c
的值都發生了變化。 緣由是什麼呢?下面一塊兒看下
運行下面的轉換語句,將當前的OC代碼轉換C++, 方便咱們看到更本質的東西:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
複製代碼
轉換後的代碼以下:
int a = 10; // 全局變量
struct __main_block_impl_0 { // block的結構體
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int b; // 新生成的成員變量b,用於存放外部局部變量b的值
int *c; // 新生成的成員變量c,指針類型, 用於存儲外部靜態局部變量c的引用。
// 構造函數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _b, int *_c, int flags=0) : b(_b), c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int b = __cself->b; // 經過cself進行訪問內部的成員變量b
int *c = __cself->c; // 經過cself獲取靜態局部變量c的引用
// 直接訪問全局變量a
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_256a11_mi_0, a , b, (*c));
}
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;
auto int b = 20;
static int c = 30;
void (*Myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b, &c));
a = 11;
b = 21;
c = 31;
((void (*)(__block_impl *))((__block_impl *)Myblock)->FuncPtr)((__block_impl *)Myblock);
}
return 0;
}
複製代碼
有上能夠觀察到:
block
結構體__main_block_impl_0
內部生成了新的成員變量b
和*c
, 分別用於存放外部傳進來的b
和c的地址
,這就是咱們所說的捕獲
。而對於全局變量a
則沒有進行捕獲,在使用時是直接訪問的。
由此可得出:
block
內部對auto
和static
類型的變量進行了捕獲
,可是不會捕獲全局變量
。auto
和static
變量都進行了捕獲,可是不一樣的是,auto
變量是值傳遞,而static
變量則是地址傳遞。所以當外部的static
變量值發生變化時,block
內部也跟着會改變,而外部的auto
變量值發生變化,block
內部的值不會發生改變。相信你會有這樣的疑問,爲何block
會捕獲auto
和static
類型的局部變量,而不會捕獲全局變量呢?(全局變量
表示不服,block
你怎麼搞區別對待呢?), 那麼block
的變量捕獲究竟有什麼講究呢?
實際上是這樣的
auto
類型的局部變量,其生命週期過短了,離開了其所在的做用域後,auto
變量的內存就會被系統回收了,而block
的調用時機是不肯定的,若是block
不對它進行捕獲,那麼當block運行時再訪問auto
變量時,由於變量已被系統回收,那麼就會出現壞內存訪問
或者獲得不正確的值
。static
變量,由於其初始化以後,在程序運行過程當中就會一直存在內存中,而不會被系統回收,可是因爲由於是局部變量的緣由,其訪問的做用域有限,block想訪問它就要知道去哪裏訪問,因此block才須要對其進行捕獲,但與auto
變量不一樣的是,block
只需捕獲static
變量的地址便可。全局變量
,由於其在程序運行過程一直都在,而且其訪問做用域也是全局的,因此block
能夠直接找到它,而不須要對它進行捕獲。因此,block
的變量捕獲原則其實很簡單,若是block
內部能直接訪問到的變量,那就不捕獲(捕獲也是浪費空間
), 若是block內部不能直接訪問到變量,那就須要進行捕獲(不捕獲就沒得用
)。
block有3種類型,能夠經過調用class
方法或者isa指針
查看具體類型,最終都是繼承自NSBlock
類型.
爲了準確分析block
的類型,先把ARC
給關閉,使用MRC
。
int main(int argc, const char * argv[]) {
@autoreleasepool {
auto int age = 10; // 局部變量,默認是auto,通常能夠不寫auto,所處做用域結束後會被銷燬。
static int height = 20; // 靜態變量,程序運行過程當中一直存在內存。
void(^block1)(void) = ^(void) {
NSLog(@"1111111111"); // 沒有捕獲了auto變量
};
void(^block2)(void) = ^(void) {
NSLog(@"age = %d", age); // 捕獲了auto變量
};
void(^block3)(void) = ^(void) {
NSLog(@"height = %d", height); // 捕獲了static變量
};
NSLog(@"block1 class: %@", [block1 class]); // __NSGlobalBlock__
NSLog(@"block2 class: %@", [block2 class]); // __NSStackBlock__
NSLog(@"block2 copy class: %@", [[block2 copy] class]); //__NSMallocBlock__
NSLog(@"block3 class: %@", [block3 class]); //__NSGlobalBlock__
}
return 0;
}
// 輸出結果:
2020-07-28 20:41:43.283331+0800 LearningBlock[40390:637401] block1 class: __NSGlobalBlock__
2020-07-28 20:41:43.283755+0800 LearningBlock[40390:637401] block2 class: __NSStackBlock__
2020-07-28 20:41:43.283877+0800 LearningBlock[40390:637401] block2 copy class: __NSMallocBlock__
2020-07-28 20:41:43.283924+0800 LearningBlock[40390:637401] block3 class: __NSGlobalBlock__
複製代碼
由上可知:
block
類型取值以下:
auto
變量,那麼block
的爲__NSGlobalBlock__
類型。auto
變量,那麼block
爲 __NSStackBlock__
類型。__NSStackBlock__
類型的block
進行copy
操做,則block
就會變成__NSMallocBlock__
類型.block
這幾種類型的主要區別是:在內存中的存放區域不一樣。(即生命的週期不一樣)
__NSGlobalBlock__
存在數據段。__NSStackBlock__
存放在棧空間。__NSMallocBlock__
存放在堆空間。檢驗題:
新建一個Person類, 以下:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)test;
@end
@implementation Person
- (void)test {
void (^block)(void) = ^{
NSLog(@"person name = %@", _name);
};
}
@end
複製代碼
問題: 在Person.m
的test方法
中的block
對self
有沒有進行捕獲呢?
答案是有,block
會捕獲self
. 分析以下:
首先將Person.m
經過xcrun
命令轉換爲C++
, 獲得以下內容:
//test 方法內的block方法
struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//test 方法
static void _I_Person_test(Person * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_Person_14871d_mi_1, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)block, sel_registerName("class")));
}
複製代碼
觀察轉換後的代碼能夠看到:
(Person *self, SEL _cmd)
, 分別是方法的調用者 self
和 方法選擇器 sel
。咱們平常使用的block通常是__NSMallocBlock__
類型的,緣由有以下:
__NSGlobalBlock__
類型的block
, 由於沒有捕獲auto
變量, 因此正常通常都是直接使用函數實現。__NSStackBlock__
類型的block
, 由於其存放在棧上,其內部使用變量容易被系統回收掉,從而致使一些異常的狀況。好比下面:(要先將項目切成MRC,由於ARC下編譯器會根據狀況作copy操做,會影響分析)typedef void (^MJBlock)(void);
MJBlock block;
void test() {
int a = 10; // test方法結束後,a的內存就被回收了。
block = ^(void) {
NSLog(@"a = %d", a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block(); // block裏打印的是被回收了的a
}
return 0;
}
輸出結果:
2020-09-27 10:05:28.616920+0800 Interview01-block的copy[7134:29679] a = -272632776
複製代碼
__NSMallocBlock__
類型的block
, 由於它是存儲在堆上,因此就不存在__NSStackBlock__
類型block
的問題。上面演示的是在MRC
環境下的, 那麼在ARC
環境下又是如何的呢?
在ARC
環境下,編譯器會自動根據狀況將棧上的block
複製到堆上。好比一下狀況:
block
做爲函數返回值時。block
賦值給__strong
指針時。block
做爲Cocoa API
中方法名含有usingBlock
的方法參數時。block
做爲GCD API
方法參數時。typedef void (^MJBlock)(void);
MJBlock myblock()
{
int a = 10;
return ^{
NSLog(@"--------- %d", a); // 1. 做爲方法返回值時。會自動copy
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
MJBlock block = ^{ // 2.賦值給strong指針時,會自動copy
NSLog(@"---------%d", age);
};
NSArray *arr = @[@10, @20];
[arr enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 3. block做爲Cocoa API中方法名含有usingBlock的方法參數時。會自動copy
}];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 4. block做爲GCD API方法參數時。會自動copy
});
}
return 0;
}
複製代碼
根據上面的狀況,在MRC
和ARC
下block屬性
的寫法能夠有差別:
MRC下block屬性的建議寫法
@property (copy, nonatomic) void (^block)(void); // 賦值時會自動copy到堆上
ARC下block屬性的建議寫法
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
複製代碼
基本數據類型
的auto
變量咱們已經分析了,那麼對象類型
的auto
變量是否是和基本數據類型的同樣仍是有什麼特別之處呢?下面咱們一塊兒來分析下:(記得先將工程切回ARC模式)
以下代碼:
@interface LCPerson : NSObject
@property (nonatomic, assign) int age;
@end
@implementation LCPerson
- (void)dealloc {
NSLog(@"%s", __func__); // 銷燬代碼
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"11111111");
{
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
}
NSLog(@"22222222");
}
return 0;
}
// 輸出結果:
2020-09-27 10:36:43.856070+0800 LearningBlock[16016:56873] 11111111
2020-09-27 10:36:43.856442+0800 LearningBlock[16016:56873] -[LCPerson dealloc]
2020-09-27 10:36:43.856474+0800 LearningBlock[16016:56873] 22222222
複製代碼
咱們定義了一個LCPerson
類,在main.m
中作測試,由輸出結果能夠看出,person對象的釋放是在1111111
和 22222222
之間, 這咱們應該均可以理解。(局部做用域)
咱們繼續~
加入Block以後,咱們再觀察一下。
typedef void (^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"11111111");
MyBlock block;
{
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
block = ^(void){
NSLog(@"person age = %d", person.age);
};
}
NSLog(@"22222222");
}
NSLog(@"3333333");
return 0;
}
輸出結果:
2020-09-27 10:52:27.578241+0800 LearningBlock[20478:70040] 11111111
2020-09-27 10:52:27.578627+0800 LearningBlock[20478:70040] 22222222
2020-09-27 10:52:27.578688+0800 LearningBlock[20478:70040] -[LCPerson dealloc]
2020-09-27 10:52:27.578729+0800 LearningBlock[20478:70040] 3333333
複製代碼
根據結果,咱們能夠發現加入了block
以後,person
的銷燬是在222222
以後發生的,即person
所在的做用域結束後,person
對象沒有當即釋放。 那麼block
究竟對person
幹了什麼,致使person對象沒能及時釋放呢? 爲了分析,咱們將上面的代碼先簡化一下。簡化以下
int main(int argc, const char * argv[]) {
@autoreleasepool {
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
void (^block)(void) = ^(void){
NSLog(@"person age = %d", person.age);
};
block();
}
return 0;
}
複製代碼
將上面OC代碼轉換爲C++代碼:(支持ARC、指定運行時系統版本)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
複製代碼
轉換後的C++代碼以下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
LCPerson *__strong person; // strong類型的指針
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, LCPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
LCPerson *__strong person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_5882d6_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LCPerson *person = ((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LCPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 10);
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
經過觀察能夠發現,block
內部對person
進行了捕獲。而且與捕獲基本數據類型的auto
變量不一樣的是,捕獲對象類型時__main_block_desc_0
結構體多了兩個函數,分別是copy
和dispose
,這兩個函數與被捕獲對象的引用計數的處理有關。
block
從棧
上拷貝到堆
上時,copy
函數被調用,接着它會調用_Block_object_assign
函數,處理被捕獲對象的引用計數,若是捕獲變量時是使用__strong
修飾,那麼對象的引用計數就會+1
. 若是捕獲時是__weak
修飾,則引用計數不變。(下面會驗證)block
被回收,即釋放時,dispose
函數被調用,接着它會調用_Block_object_dispose
函數,若是捕獲變量時是使用__strong
修飾,那麼對象的引用計數就會-1
. 若是捕獲變量時是__weak
修飾,則引用計數不變。(下面會驗證)咱們知道,在ARC
環境下,將block
賦值給__strong
指針,block
會自動調用copy
函數。因此 person
對象離開了局部做用域後沒有釋放的緣由就很明確了,是由於block
調用copy
函數時,將person
對象的引用計數增長了1,因此當局部做用域結束時,person對象
的引用計數並不爲0,所以不會釋放。 而當block
的做用域結束,block
調用dispose
函數,將person
的引用計數減爲0,而後person
纔會釋放。
如上面所說,那若是是在MRC
環境下,person
對象離開局部做用域後就會銷燬了, 由於在MRC
環境下,將block
賦值給__strong
指針是不會觸發copy
函數的,因此person
對象應該能夠正常釋放。
驗證一: 將工程切換到MRC
模式下,測試剛纔的代碼,以下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"11111111");
MyBlock block;
{
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
block = ^(void){
NSLog(@"person age = %d", person.age);
};
[person release]; // MRC下須要手動管理內存
}
NSLog(@"22222222");
}
NSLog(@"3333333");
return 0;
}
// 輸出結果:
2020-09-27 11:39:05.493388+0800 LearningBlock[33422:105156] 11111111
2020-09-27 11:39:05.493800+0800 LearningBlock[33422:105156] -[LCPerson dealloc]
2020-09-27 11:39:05.493833+0800 LearningBlock[33422:105156] 22222222
2020-09-27 11:39:05.493857+0800 LearningBlock[33422:105156] 3333333
複製代碼
觀察輸出結果,和預料中的同樣。person
對象離開局部做用域後正常釋放。
驗證二: 用weak
修飾的對象類型的auto
變量. (記得切回ARC環境)
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"11111111");
MyBlock block;
{
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
// 弱指針
__weak LCPerson *weakPerson = person;
block = ^(void){
NSLog(@"person age = %d", weakPerson.age);
};
}
NSLog(@"22222222");
}
NSLog(@"3333333");
return 0;
}
// 輸出結果:
2020-09-27 12:00:20.461929+0800 LearningBlock[39325:122309] 11111111
2020-09-27 12:00:20.462321+0800 LearningBlock[39325:122309] -[LCPerson dealloc]
2020-09-27 12:00:20.462361+0800 LearningBlock[39325:122309] 22222222
2020-09-27 12:00:20.462391+0800 LearningBlock[39325:122309] 3333333
複製代碼
觀察輸出結果,和預料中的同樣。person
對象離開局部做用域後正常釋放。
總結:
當block
內部訪問了對象類型的auto
變量時
block
是在棧上,將不會對auto
變量產生強引用若是block
被拷貝到堆上
block
內部的copy
函數copy
函數內部會調用_Block_object_assign
函數_Block_object_assign
函數會根據auto
變量的修飾符(__strong
、__weak
、__unsafe_unretained
)作出相應的操做,造成強引用(retain
)或者弱引用
若是block
從堆上移除
block
內部的dispose
函數dispose
函數內部會調用_Block_object_dispose
函數_Block_object_dispose
函數會自動釋放引用的auto
變量(release
)__block
能夠用於解決block
內部沒法修改auto變量
值的問題__block
不能修飾全局變量
、靜態變量
(static
)__block
變量包裝成一個對象.下面一塊兒驗證一下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int a = 10;
void (^block)(void) = ^{
a = 20;
NSLog(@"a = %d", a);
};
block();
}
return 0;
}
// 輸出結果:
a = 20
複製代碼
將上面OC代碼轉換爲C++代碼:(支持ARC、指定運行時系統版本)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
複製代碼
獲得轉換後結果:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref 這就捕獲到的a的引用
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a) = 20; // 修改值a的值。
NSLog((NSString *)&__NSConstantStringImpl__var_folders_vt_j2sf07q142992_z55yg_170w0000gp_T_main_ca9eb0_mi_0, (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10}; // 這就是__block 修飾的a變量。
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); // 傳入的是a變量的地址。
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
由上面能夠看到,OC
代碼 __block int a = 10
轉換爲C++
以後變爲了:
__Block_byref_a_0 a = {0, &a, 0, sizeof(__Block_byref_a_0), 10};
複製代碼
__Block_byref_a_0
是一個結構體,結構以下:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
}
複製代碼
因此在OC中用__block
修飾一個變量, 編譯器會自動生成一個全新的OC對象。
__block
在block
中的內存管理和對象類型的auto
變量相似(但也有區別)。
block
在棧上時,並不會對__block
變量產生強引用block
被copy
到堆時
block
內部的copy
函數copy
函數內部會調用_Block_object_assign
函數_Block_object_assign
函數會對__block
變量造成強引用(retain)。(這點就是和對象類型的auto
變量有區別的地方,對於對象類型的auto
變量, _Block_object_assign
函數會根據auto變量的修飾符(__strong
、__weak
、__unsafe_unretained
)作出相應的操做, 而__block
則是直接強引用 )block
從堆中移除時
block
內部的dispose
函數dispose
函數內部會調用_Block_object_dispose
函數_Block_object_dispose
函數會自動釋放引用的__block
變量(release
)經過上面咱們知道了用__block
修飾的基本數據類型
的處理。那用__block
修飾的對象類型的處理是否是同樣的呢? 下面咱們一塊兒看下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
void(^block)(void) = ^(void) {
NSLog(@"person age %d", person.age);
};
block();
}
return 0;
}
複製代碼
經過xcrun
命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
複製代碼
轉換成C++後,獲得結果以下:
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*); // 管理person的內存
void (*__Block_byref_id_object_dispose)(void*); // 管理person的內存
LCPerson *__strong person; //arc環境下, copy 和 dispose函數,會根據person的修飾類型(__strong、__weak)來對person作相應的內存管理。
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__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_person_0 *person = __cself->person; // bound by ref // 這裏就是強引用
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hc_wwwl26516td3w0ds9cx80c280000gp_T_main_213c56_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)(person->__forwarding->person), sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LCPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LCPerson"), sel_registerName("alloc")), sel_registerName("init"))};
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(person.__forwarding->person), sel_registerName("setAge:"), 10);
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
當block
從棧
拷貝到堆
上時,會調用block
的copy
方法,同時還會調用__Block_byref_person_0
結構體裏的__Block_byref_id_object_copy
方法,__Block_byref_id_object_copy
內部會調用_Block_object_assign
方法,處理結構體__Block_byref_person_0
內部的person
指針所指對象的引用計數。
總結以下:
當__block
變量在棧上時,不會對指向的對象產生強引用
當__block
變量被copy
到堆時
__block
變量內部的copy
函數copy
函數內部會調用_Block_object_assign
函數_Block_object_assign
函數會根據所指向對象的修飾符(__strong
、__weak
、__unsafe_unretained
)作出相應的操做,造成強引用(retain
)或者弱引用(注意:這裏僅限於ARC時會retain,MRC時不會retain
)若是__block
變量從堆上移除
__block
變量內部的dispose
函數dispose
函數內部會調用_Block_object_dispose
函數_Block_object_dispose
函數會自動釋放指向的對象(release
)當block在棧上時,對它們都不會產生強引用
當block拷貝到堆上時,都會經過copy函數來處理它們
__block變量(假設變量名叫作a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);
對象類型的auto變量(假設變量名叫作p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
當block從堆上移除時,都會經過dispose函數來釋放它們
__block變量(假設變量名叫作a)
_Block_object_dispose((void*)src->a, 8/BLOCK_FIELD_IS_BYREF/);
對象類型的auto變量(假設變量名叫作p)
_Block_object_dispose((void*)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
在開發過程當中咱們常常會遇到block循環引用的問題, 以下:
typedef void (^MyBlock)(void);
@interface LCPerson : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, copy) MyBlock block;
@end
@implementation LCPerson
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"person age %d", person.age);
};
NSLog(@"211212121122");
}
return 0;
}
// 輸出結果:
2020-09-28 20:01:48.358822+0800 LearningBlock[41115:298402] 211212121122
複製代碼
由打印結果能夠看出,person
並無釋放(沒有調用person的dealloc方法)。那是什麼緣由致使的呢?是循環引用。 下面咱們來分析一下:
@property (nonatomic, copy) MyBlock block;
從這句話能夠看出,person
強引用着block
.block
內部訪問了person
對象的age
屬性,根據上面所學,咱們知道block
會對person
進行捕獲,而且在arc
環境下,block
賦值給__strong
指針時會自動調用copy
方法,將block
從棧拷貝到堆上, 這樣會致使person
的引用計數加1,即block
強引用着person
。因此person
與block
相互強引用着,出現了循環引用,因此person
對象不會釋放。
那麼該如何解決呢? 下面說下在ARC
環境和MRC
環境分別如何處理?
在ARC
環境下,咱們能夠經過使用關鍵字__weak
、__unsafe_unretained
來解決。以下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
__weak LCPerson *weakPerson = person;
// 或者 __unsafe_unretained LCPerson *weakPerson = person;
person.block = ^{
NSLog(@"person age %d", weakPerson.age);
};
NSLog(@"211212121122");
}
return 0;
}
// 打印結果:
2020-09-28 20:30:19.659679+0800 LearningBlock[41212:307877] 211212121122
2020-09-28 20:30:19.660256+0800 LearningBlock[41212:307877] -[LCPerson dealloc]
複製代碼
示意圖以下:
還可使用__block
解決, 以下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"person age %d", person.age);
person = nil;
};
person.block(); // 必須調用
NSLog(@"211212121122");
}
return 0;
}
// 打印結果:
2020-09-28 20:35:32.531704+0800 LearningBlock[41256:310297] person age 10
2020-09-28 20:35:32.532221+0800 LearningBlock[41256:310297] -[LCPerson dealloc]
2020-09-28 20:35:32.532310+0800 LearningBlock[41256:310297] 211212121122
複製代碼
使用__block
解決,必須調用block,否則沒法將循環引用
打破。
疑問: __weak
和__unsafe_unretained
關鍵字有什麼區別呢?
使用__weak
和__unsafe_unretained
關鍵字都能達到弱引用的效果。這二者主要的區別在於,使用__weak
關鍵字修飾的指針,在所指的對象銷燬時,指針存儲的地址會被清空(即置爲nil), 而__unsafe_unretained
則不會。
__weak
關鍵字的,因此可使用__unsafe_unretained
關鍵字解決。(與ARC差很少,這裏就不演示了)__block
關鍵字解決。以下:int main(int argc, const char * argv[]) {
@autoreleasepool {
__block LCPerson *person = [[LCPerson alloc] init];
person.age = 10;
person.block = ^{
NSLog(@"person age %d", person.age);
person = nil;
};
[person release]; // MRC須要手動添加內存管理代碼
NSLog(@"211212121122");
}
return 0;
}
複製代碼
與ARC
不一樣的是,MRC
下使用__block
解決循環引用問題,不要求必定要調用block
。緣由上面__block修飾的對象類型
裏有說到:
_Block_object_assign
函數會根據所指向對象的修飾符(__strong
、__weak
、__unsafe_unretained
)作出相應的操做,造成強引用(retain
)或者弱引用(注意:這裏僅限於ARC時會retain,MRC時不會retain
)
這篇文章有點亂,還有待改進。寫博客真的費時間,不過能加深印象,也不錯。
輕鬆一刻: