上篇Category和關聯對象講解了+load
和initialize
區別和練習,沒看過的朋友能夠去溫習一下,本章講解block的用法和底層數據結構,以及使用過程當中須要注意的點。ios
前幾篇文章講過了,class
是對象,元類也是對象,本質是結構體,那麼block是否也是如此呢?block
具備這幾個特色:c++
先簡單來看一下block
編譯以後的樣子git
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^block)(void) = ^(void){
NSLog(@"hello word");
};
block();
}
return 0;
}
複製代碼
命令行執行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.mm -o main.cpp
,來到main.cpp
內部,已經去除多餘的轉化函數,剩餘骨架,能夠看得更清晰。程序員
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//構造函數 相似OC init函數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;//block類型
impl.Flags = flags;
impl.FuncPtr = fp;// 執行函數的地址
Desc = desc;//desc 存儲 __main_block_desc_0(0,sizeof(__main_block_impl_0))的值
}
};
//block 內部代碼封裝成函數
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_b7cca8_mii_0);
}
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;
//定義block
void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
//執行block
block->FuncPtr(block);
}
return 0;
}
複製代碼
最終block
轉化成__main_block_impl_0
結構體,賦值給變量block
,傳入參數是__main_block_func_0
和__main_block_desc_0_DATA
來執行__main_block_impl_0
的構造函數,__main_block_desc_0_DATA
函數賦值給__main_block_impl_0->FuncPtr
,執行函數是block->FuncPtr(block)
,刪除冗餘代碼以前是((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
,那麼爲何block
能夠直接強制轉化成__block_impl
呢?由於__main_block_impl_0
結構體的第一行變量是__block_impl
,至關於__main_block_impl_0
的內存地址和__block_impl
的內存地址同樣,強制轉化也不會有問題。github
變量捕獲分爲3種:chrome
變量類型 | 是否會捕獲到block內部 | 訪問方式 | 內部變量假定是a |
---|---|---|---|
局部變量 auto | 會 | 值傳遞 | a |
局部變量 static | 會 | 指針傳遞 | *a |
全局變量 | 不會 | 直接訪問 | 空 |
auto
變量,通常auto
是省略不寫的,訪問方式是值傳遞,關於值傳遞不懂的話能夠看這篇博客, 看下這個例子express
int age = 10;
void (^block)(void) = ^(void){
NSLog(@"age is %d",age);
};
age = 20;
block();
//實際輸出是 age is 10
複製代碼
有沒有疑問呢?在block
執行以前age =20
,爲何輸出是10呢? 將這段代碼轉化成c/c++
,以下所示:bash
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;//多了一個變量age,存儲值是10
__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 age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_baf352_mii_0,age);
}
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;
int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
結構體__main_block_impl_0
多了一個變量age
,在block
轉化成c
函數的時候__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age)
直接將age的值存儲在__main_block_impl_0.age
中,此時__main_block_impl_0.age
是存儲在堆上的,以前的age
是存儲在數據段的,執行block
訪問的變量是堆上的``__main_block_impl_0.age,因此
最終輸出來age is 10
。數據結構
咱們經過一個例子來說解static和auto區別:app
void(^block)(void);
void test(){
int age = 10;
static int level = 12;
block = ^(void){
NSLog(@"age is %d,level is %d",age,level);
};
age = 20;
level = 13;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test();
block();
}
return 0;
}
//輸出:age is 10,level is 13
複製代碼
轉化成源碼:
void(*block)(void);
struct __test_block_impl_0 {
struct __block_impl impl;
struct __test_block_desc_0* Desc;
int age;
int *level;
__test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _age, int *_level, int flags=0) : age(_age), level(_level) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *level = __cself->level; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_b26797_mii_0,age,(*level));
}
static struct __test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0)};
void test(){
int age = 10;
static int level = 12;
block = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, age, &level));
age = 20;
level = 13;
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
test();
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
當執行完test()
函數,age
變量已經被收回,可是age
的值存儲在block
結構體中,level
的地址存儲在__test_block_impl_0.level
,能夠看到level
類型是指針類型,讀取值的時候也是*level
,則無論什麼時間改動level
的值,讀level
的值都是最新的,由於它是從地址直接讀的。因此結果是age is 10,level is 13
。
全局不用捕獲的,訪問的時候直接訪問。咱們來測試下
int age = 10;
static int level = 12;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^(void){
NSLog(@"age is %d,level is %d",age,level);
};
age = 20;
level = 13;
block();
}
return 0;
}
複製代碼
轉化成c/c++
int age = 10;
static int level = 12;
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_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_45cab9_mii_0,age,level);
}
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));
age = 20;
level = 13;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
複製代碼
能夠看出來編譯以後僅僅是多了兩行int age = 10; static int level = 12;
,結構體__main_block_impl_0
內部和構造函數並無專門來存儲值或者指針,緣由是當執行__main_block_func_0
,能夠直接訪問變量age
和 level
,由於全局變量有效區域是全局,不會出了main
函數就消失。 基本歸納來說就是超出執行區域與可能消失的會捕獲,必定不會消失的不會捕獲。
咱們再看下更復雜的狀況,對象類型的引用是如何處理的?
@interface FYPerson : NSObject
@property (nonatomic,copy) NSString * name;
@end
@implementation FYPerson
- (void)test{
void (^block)(void) = ^{
NSLog(@"person is %@",self);
};
void (^block2)(void) = ^{
NSLog(@"name is %@",_name);
};
}
@end
struct __FYPerson__test_block_impl_0 {
struct __block_impl impl;
struct __FYPerson__test_block_desc_0* Desc;
FYPerson *self;
__FYPerson__test_block_impl_0(void *fp, struct __FYPerson__test_block_desc_0 *desc, FYPerson *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __FYPerson__test_block_func_0(struct __FYPerson__test_block_impl_0 *__cself) {
FYPerson *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_FYPerson_c624e0_mi_0,self);
}
static void __FYPerson__test_block_copy_0(struct __FYPerson__test_block_impl_0*dst, struct __FYPerson__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __FYPerson__test_block_dispose_0(struct __FYPerson__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __FYPerson__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __FYPerson__test_block_impl_0*, struct __FYPerson__test_block_impl_0*);
void (*dispose)(struct __FYPerson__test_block_impl_0*);
} __FYPerson__test_block_desc_0_DATA = { 0, sizeof(struct __FYPerson__test_block_impl_0), __FYPerson__test_block_copy_0, __FYPerson__test_block_dispose_0};
struct __FYPerson__test_block_impl_1 {
struct __block_impl impl;
struct __FYPerson__test_block_desc_1* Desc;
FYPerson *self;
__FYPerson__test_block_impl_1(void *fp, struct __FYPerson__test_block_desc_1 *desc, FYPerson *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __FYPerson__test_block_func_1(struct __FYPerson__test_block_impl_1 *__cself) {
FYPerson *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_FYPerson_c624e0_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_FYPerson$_name)));
}
static void __FYPerson__test_block_copy_1(struct __FYPerson__test_block_impl_1*dst, struct __FYPerson__test_block_impl_1*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __FYPerson__test_block_dispose_1(struct __FYPerson__test_block_impl_1*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __FYPerson__test_block_desc_1 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __FYPerson__test_block_impl_1*, struct __FYPerson__test_block_impl_1*);
void (*dispose)(struct __FYPerson__test_block_impl_1*);
} __FYPerson__test_block_desc_1_DATA = { 0, sizeof(struct __FYPerson__test_block_impl_1), __FYPerson__test_block_copy_1, __FYPerson__test_block_dispose_1};
static void _I_FYPerson_test(FYPerson * self, SEL _cmd) {
void (*block)(void) = ((void (*)())&__FYPerson__test_block_impl_0((void *)__FYPerson__test_block_func_0, &__FYPerson__test_block_desc_0_DATA, self, 570425344));
void (*block2)(void) = ((void (*)())&__FYPerson__test_block_impl_1((void *)__FYPerson__test_block_func_1, &__FYPerson__test_block_desc_1_DATA, self, 570425344));
}
複製代碼
block
和block2
都是結構體__FYPerson__test_block_impl_1
內部引用了一個FYPerson
對象指針,FYPerson
對象屬於局部變量,須要捕獲。第2個block
訪問_name
捕捉的也是FYPerson
對象,訪問_name
,須要先訪問FYPerson
對象,而後再訪問_name
,本質上是訪問person.name
,因此捕捉的是FYPerson
對象。
//ARC環境下
void(^block)(void)=^{
NSLog(@"Hello, World!");
};
NSLog(@"本身class:%@ 它爹class:%@ 它爺爺class:%@ 它老爺爺的tclass:%@",[block class],[[block class] superclass],[[[block class] superclass]superclass],[[[[block class] superclass]superclass] superclass]);
//輸出是:本身class:__NSGlobalBlock__ 它爹class:__NSGlobalBlock 它爺爺class:NSBlock 它老爺爺的tclass:NSObject
複製代碼
能夠了解到block
是繼承與基類的,因此block
也是OC對象。
block
有3種類型,以下所示,能夠經過調用class
方法或者isa
指針查看具體類型,最終都是繼承來自NSBlock
類型。
在應用程序中內存分配是這樣子的:
---------------
程序區域 .text區
---------------
數據區域 .data區 <--------- _NSConcreteGlobalBlock(存儲全局變量)
---------------
堆 <--------- _NSConcreteMallocBlock(動態申請釋放內存區域)
---------------
棧 <--------- _NSConcreteStackBlock(存儲存局部變量)
---------------
複製代碼
block類型 | 環境 |
---|---|
NSGlobalBLock | 沒有訪問auto變量 |
NSStackBlock | 訪問auto變量 |
NSMallocBLock | NSStackBlock 調用copy |
驗證須要設置成MRC,找到工程文件,設置project->Object-C Automatic Reference Counting=
爲NO
int age = 10;
void(^block1)(void)=^{
NSLog(@"block1");
};
void(^block2)(void)=^{
NSLog(@"block2 %d",age);
};
void(^block3)(void)=[block2 copy];
NSLog(@"block1:%@ block2:%@ block3:%@ ",[block1 class],[block2 class],[block3 class]);
//輸出
block1:__NSGlobalBlock__
block2:__NSStackBlock__
block3:__NSMallocBlock__
複製代碼
沒有訪問auto
變量的block
屬於__NSGlobalBlock__
,訪問了auto變量的是__NSStackBlock__
,手動調用了copy
的block
屬於__NSMallocBlock__
。__NSMallocBlock__
是在堆上,須要程序員手動釋放[block3 release];
,不釋放會形成內存泄露。
每一種類型的block
調用copy
後的結果以下
block類型 | 副本源的配置存儲域 | 複製效果 |
---|---|---|
NSGlobalBLock | 堆 | 從棧複製到堆 |
NSStackBlock | 程序的數據區域 | 什麼也不作 |
NSMallocBLock | 堆 | 引用計數+1 |
在ARC環境下測試:
typedef void (^FYBlock)(void);
typedef void (^FYBlockInt)(int);
FYBlock myBlock(){
return ^{
NSLog(@"哈哈");
};
};
FYBlock myBlock2(){
int age = 10;
return ^{
NSLog(@"哈哈 %d",age);
};
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
FYBlock block = myBlock();
FYBlock block2 = myBlock2();
int age = 10;
FYBlock block3= ^{
NSLog(@"強指針block %d",age);
};
NSLog(@"沒訪問變量:%@ 訪問佈局變量:%@ 強指針:%@",[block class],[block2 class],[block3 class]);
}
return 0;
}
//輸出
沒訪問變量:__NSGlobalBlock__
訪問局部變量:__NSMallocBlock__
強指針:__NSMallocBlock__
複製代碼
arc
環境下,沒訪問變量的block
是__NSGlobalBlock__
,訪問了局部變量是__NSMallocBlock__
,有強指針引用的是__NSMallocBlock__
,強指針系統自動執行了copy操做,由棧區複製到堆區,由系統管理改成開發者手動管理。
因此有如下建議:
MRC下block屬性的建議寫法
ARC下block屬性的建議寫法
平時咱們使用block
,對象類型來傳遞數據的比較多,對象類型讀取到block
中用__block
修飾符,會把對象地址直接讀取到block
結構體內,__weak
修飾的對象是弱引用,默認是強引用,咱們看下這段代碼
//FYPerson.h
@interface FYPerson : NSObject
@property (nonatomic,assign) int age;
@end
//FYPerson.m
@implementation FYPerson
@end
//main.m
typedef void (^FYBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
FYBlock block ;
FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak typeof(person) __weakPerson = person;
block = ^{
NSLog(@" %d",__weakPerson.age);
};
block();
}
return 0;
}
複製代碼
使用下面該命令轉化成cpp
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main.cpp
複製代碼
摘取關鍵結構體代碼:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
FYPerson *__weak __weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, FYPerson *__weak ___weakPerson, int flags=0) : __weakPerson(___weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
FYPerson *__weak __weakPerson = __cself->__weakPerson; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_c0_7nm4_r7s4xd0mbs67ljb_b8m0000gn_T_main_7f0272_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)__weakPerson, sel_registerName("age")));
}
複製代碼
FYPerson *__weak __weakPerson
是__weak
修飾的對象 當block內部換成block = ^{ NSLog(@" %d",person.age); };
,轉換源碼以後是
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
FYPerson *__strong person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, FYPerson *__strong _person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
person
默認是使用__storng
來修飾的,arc
中,block
引用外界變量,系統執行了copy
操做,將block
copy
到堆上,由開發者本身管理,轉c/c++
中結構體描述爲
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)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->__weakPerson, (void*)src->__weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->__weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
複製代碼
有對象的使用,則有內存管理,既然是arc,則是系統幫開發者管理內存,函數void (*copy)
和void (*dispose)
就是對block的引用計數的+1
和-1
。
若是block被拷貝到堆上
若是block從堆上移除
函數 | 調用時機 |
---|---|
copy函數 | 棧上的Block複製到堆時 |
dispose函數 | 堆上的Block被廢棄時 |
person什麼時間釋放?
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%d",person.age);
});
}
複製代碼
3s後釋放,dispatch
對block
強引用,block
強引用person
,在block
釋放的時候,person
沒其餘的引用,就釋放掉了。
變換1:person
什麼時間釋放
FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak FYPerson *__weakPerosn = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%d",__weakPerosn.age);
});
複製代碼
__weak
沒有對perosn
進行強引用,咋執行完dispatch_block則立馬釋放,答案是當即釋放。 變換2:person
什麼時間釋放
FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak typeof(person) __weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%d",__weakPerson.age);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%d",person.age);
});
});
複製代碼
person
被內部block
強引用,則block
銷燬以前person
不會釋放,__weakPerson
執行完person
不會銷燬,NSLog(@"---%d",person.age)
執行完畢以後,person
銷燬。答案是4秒以後NSLog(@"---%d",person.age)
執行完畢以後,person
銷燬。
變換3:person
什麼時間釋放
FYPerson *person = [[FYPerson alloc]init];
person.age = 10;
__weak typeof(person) __weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%d",person.age);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%d",__weakPerson.age);
});
});
複製代碼
person
被強引用於第一層block
,第二層弱引用person
,僅僅當第一層block執行完畢的時候,person
釋放。
想要修改變量,首先要變量的有效區域,或者block持有變量的地址。 例子1:
int age = 10;
FYBlock block = ^{
age = 20;//會報錯
};
複製代碼
報錯的緣由是age
是值傳遞,想要不報錯只須要將int age = 10
改爲static int age = 10
,就由值傳遞變成地址傳遞,有了age
的地址,在block
的內部就能夠更改age
的值了。或者將int age = 10
改爲全局變量,全局變量在block
中不用捕獲,block
本質會編譯成c
函數,c
函數訪問全局變量在任意地方均可以直接訪問。
__block
本質上是修飾的對象或基本類型,編譯以後會生成一個結構體__Block_byref_age_0
,結構體中*__forwarding
指向結構體本身,經過 (age->__forwarding->age) = 20
來修改變量的值。
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;//10
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5p_cwsr3ytd5md9r_kgb2fv_2c40000gn_T_main_043d00_mi_0,(age->__forwarding->age));
}
複製代碼
age
在block
外部有一個,在block
內部有一個,他們是同一個嗎?咱們來探究一下:
typedef void (^FYBlock)(void);
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;//10
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
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;
struct __Block_byref_age_0 *age; // by ref
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
__block int age = 10;
NSLog(@" age1:%p",&age);
FYBlock block = ^{
age = 20;
NSLog(@"age is %d",age);
};
struct __main_block_impl_0 *main= (__bridge struct __main_block_impl_0 *)block;
NSLog(@" age1:%p age2:%p",&age,&(main->age->__forwarding->age));
}
return 0;
}
輸出:
age1:0x7ffeefbff548
age1:0x100605358 age2:0x100605358
複製代碼
通過__block
修飾以後,以後訪問的age
和結構體__Block_byref_age_0
中的age
地址是同樣的,能夠斷定age
被系統copy
了一份。
例子:
__block int age = 10;
NSLog(@" age1:%p",&age);
NSObject *obj=[[NSObject alloc]init];
FYBlock block = ^{
NSLog(@"age is %d,obj is %p",age,&obj);
};
複製代碼
使用命令編譯
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
複製代碼
摘錄主要函數:
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__strong obj;
__Block_byref_age_0 *age; // by ref
};
複製代碼
__main_block_impl_0
結構體對age
進行了一個強引用並持有該結構體的地址,將age
複製到了堆上,age
轉化成__Block_byref_age_0
對象,__main_block_impl_0
能夠對__Block_byref_age_0->__forwarding->age
進行賦值。__Block_byref_age_0
既然是對象,就須要內存管理,__main_block_copy_0
出現了_Block_object_assign
和_Block_object_dispose
對__Block_byref_age_0
進行內存管理的代碼。
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 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->age, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
複製代碼
age
和obj
是一個對象結構體,obj
只是一個強引用而沒有地址變換緣由是obj
自己就在堆上,block
也在堆上,故無需複製出新的obj
來進行管理。
看一下循環引用是反面教材
typedef void (^FYBlock)(void);
@interface FYPerson : NSObject
@property (nonatomic,copy) FYBlock blcok;
@end
@implementation FYPerson
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@" age1:%p",&age);
FYPerson *obj=[[FYPerson alloc]init];
[obj setBlcok:^{
NSLog(@"%p",&obj);
}];
NSLog(@"--------------");
}
return 0;
}
複製代碼
輸出是:
age1:0x7ffeefbff4e8
block 執行完畢--------------
複製代碼
obj
經過copy
操做強引用block
,block
經過默認__strong
強制引用obj
,這就是A<---->B
,相互引用致使執行結束應該釋放的時候沒法釋放。 將main
改爲
FYPerson *obj=[[FYPerson alloc]init];
__weak typeof(obj) weakObj = obj;
[obj setBlcok:^{
NSLog(@"%p",&weakObj);
}];
複製代碼
結果是
age1:0x7ffeefbff4e8
block 執行完畢--------------
-[FYPerson dealloc]
複製代碼
使用__weak
或__unsafe__unretain
弱引用obj
,在block
執行完畢的時候,obj
釋放,block
釋放,無相互強引用,正常釋放。
__weak
和__unsafe__unretain
__weak
和__unsafe__unretain
都是弱引用obj
,都是不影響obj
正常釋放,區別是__weak
在釋放以後會將值爲nil,__unsafe__unretain
不對該內存處理。 下面咱們來具體驗證一下該結論:
typedef void (^FYBlock)(void);
@interface FYPerson : NSObject
@property (nonatomic,assign) int age ;
@end
@implementation FYPerson
-(void)dealloc{
NSLog(@"%s",__func__);
}
@end
struct __Block_byref_age_0 {
void *__isa;
struct __Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(void);
void (*dispose)(void);
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
FYPerson *__unsafe_unretained __unsafe_obj;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
FYBlock block;
{
FYPerson *obj=[[FYPerson alloc]init];
obj.age = 5;
__weak typeof(obj) __unsafe_obj = obj;
block = ^{
NSLog(@"obj->age is %d obj:%p",__unsafe_obj.age,&__unsafe_obj);
};
struct __main_block_impl_0 *suct = (__bridge struct __main_block_desc_0 *)block;
NSLog(@"inside struct->obj:%p",suct->__unsafe_obj);//斷點1
}
struct __main_block_impl_0 *suct = (__bridge struct __main_block_desc_0 *)block;
NSLog(@"outside struct->obj:%p",suct->__unsafe_obj);//斷點2
block();
NSLog(@"----end------");
}
return 0;
}
複製代碼
根據文中提示斷點1處使用lldb
打印obj
命令
(lldb) p suct->__unsafe_obj->_age
(int) $0 = 5 //年齡5仍是存儲在這裏的
inside struct->obj:0x102929d80
複製代碼
在斷點2處再次查看obj
的值,報錯不可讀取該內存
-[FYPerson dealloc]
outside struct->obj:0x0
p suct->__unsafe_obj->_age
error: Couldn't apply expression side effects : Couldn't dematerialize a result variable: couldn't read its memory 複製代碼
已經超出了obj
的有效範圍,obj
已經重置爲nil,也就是0x0000000000000000
。 上文代碼__weak
改成__unsafe_unretained
再次在obj
斷點1查看地址:
(lldb) p suct->__unsafe_obj->_age
(int) $0 = 5
inside struct->obj:0x10078c0c0
複製代碼
在斷點2出再次查看地址並查看age
的值
-[FYPerson dealloc]
outside struct->obj:0x10078c0c0
(lldb) p suct->__unsafe_obj->_age
(int) $1 = 5
複製代碼
__unsafe_unretained
在obj
銷燬以後內存並無及時重置爲空。
當咱們離開某個頁面須要再執行的操做,那麼咱們改怎麼辦? 實際應用A:
-(void)test{
__weak typeof(self) __weakself = self;
[self setBlcok:^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"perosn :%p",__weakself);
});
}];
self.blcok();
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
FYPerson *obj=[[FYPerson alloc]init];
[obj test];
NSLog(@"block 執行完畢--------------");
}
NSLog(@"person 死了");
}
return 0;
}
輸出:
block 執行完畢--------------
-[FYPerson dealloc]
person 死了
複製代碼
猛的一看,哪裏都對!使用__weak
對self
進行弱引用,不會致使死循環,在self
死的時候,block
也會死,就會致使一個問題,self
和block
共存亡,可是這個須要3秒後再執行,3秒後,self
已經死了,block
也死了,顯然不符合咱們的業務需求。 那麼咱們剝離block
和self
的關係,讓block
強引用self
,self
不持有block
就能知足業務了。以下所示:
__block typeof(self) __weakSelf = self;//__block或者沒有修飾符
dispatch_async(dispatch_get_main_queue(), ^{
sleep(2);
NSLog(@"obj:%@",__weakSelf->_obj);
});
//perosn :0x0
複製代碼
當self
不持用block
的時候,block
能夠強引用self
,block
執行完畢本身釋放,也會釋放self
,當self
持有block
,block
必須弱引用self
,則釋放self
,block
也會釋放,不然會循環引用。
block
本質是一個封裝了函數調用以及調用環境的結構體
對象__block
修飾的變量會被封裝成結構體
對象,以前在數據段的會被複制到堆上,以前在堆上的則不受影響,解決auto
對象在block
內部沒法修改的問題,在MRC
環境下,__block
不會對變量產生強引用.block
不使用copy
則不會從全局或者棧區域移動到堆上,使用copy
以後有由發者管理block
要注意不能產生循環引用,引用不能變成一個環,主動使其中一個引用成弱引用,則不會產生循環引用。__weak
修飾的對象,block
不會對對象強引用,在執行block
的時候有可能會值已經被系統置爲nil
,__unsafe_unretained
修飾的銷燬以後內存不會及時重置爲空。咱們看的cpp
是編譯以後的代碼,runtime
是否和咱們看到的一致呢?請聽下回分解。
本文章之因此圖片比較少,我以爲仍是跟着代碼敲一遍,印象比較深入。
最怕一輩子碌碌無爲,還安慰本身平凡難得。
廣告時間