對象類型的auto變數 例子一 首先看一個簡單的例子定義一個類 YZPerson,裡面只有一個dealloc方法 @interface YZPerson : NSObject @property (nonatomic ,assign) int age; @end @implementation YZ ...
對象類型的auto變數
例子一
首先看一個簡單的例子
定義一個類 YZPerson
,裡面只有一個dealloc
方法
@interface YZPerson : NSObject
@property (nonatomic ,assign) int age;
@end
@implementation YZPerson
- (void)dealloc
{
NSLog(@"%s",__func__);
}
@end
如下代碼使用
int main(int argc, const char * argv[]) {
@autoreleasepool {
{
YZPerson *person = [[YZPerson alloc]init];
person.age = 10;
}
NSLog(@"-----");
}
return 0;
}
想必大家都能知道會輸出什麼,沒錯,就是person先銷毀,然後列印-----
因為person是在大括弧內,當大括弧執行完之後,person 就銷毀了。
iOS-block[1376:15527] -[YZPerson dealloc]
iOS-block[1376:15527] -----
例子二
上面的例子,是不是挺簡單,那下麵這個呢,
// 定義block
typedef void (^YZBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
YZBlock block;
{
YZPerson *person = [[YZPerson alloc]init];
person.age = 10;
block = ^{
NSLog(@"---------%d", person.age);
};
NSLog(@"block.class = %@",[block class]);
}
NSLog(@"block銷毀");
}
return 0;
}
如下結果,輸出可知當 block為__NSMallocBlock__
類型時候,block可以保住person的命的,因為person離開大括弧之後沒有銷毀,當block銷毀,person才銷毀
iOS-block[3186:35811] block.class = __NSMallocBlock__
iOS-block[3186:35811] block銷毀
iOS-block[3186:35811] -[YZPerson dealloc]
一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流群:1012951431, 分享BAT,阿裡面試題、面試經驗,討論技術, 大家一起交流學習成長!希望幫助開發者少走彎路。
分析
終端執行這行指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
把main.m
生成main.cpp
可以 看到如下代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
YZPerson *person;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *_person, int flags=0) : person(_person) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
很明顯就是這個block裡面包含 YZPerson *person
。
MRC下 block引用實例對象
上面的例子,是不是挺簡單,那如果是MRC下呢
// 定義block
typedef void (^YZBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
YZBlock block;
{
YZPerson *person = [[YZPerson alloc]init];
person.age = 10;
block = ^{
NSLog(@"---------%d", person.age);
};
NSLog(@"block.class = %@",[block class]);
// MRC下,需要手動釋放
[person release];
}
NSLog(@"block銷毀");
// MRC下,需要手動釋放
[block release];
}
return 0;
}
輸出結果為
iOS-block[3114:34894] block.class = __NSStackBlock__
iOS-block[3114:34894] -[YZPerson dealloc]
iOS-block[3114:34894] block銷毀
和上面的對比,區別就是,還沒有執行NSLog(@"block銷毀");
的時候,[YZPerson dealloc]
已經執行了。也就是說,person 離開大括弧,就銷毀了。
輸出可知當 block為__NSStackBlock__
類型時候,block不可以保住person的命的
MRC下 [block copy]引用實例對象
在MRC下,對block執行了copy操作
// 定義block
typedef void (^YZBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
YZBlock block;
{
YZPerson *person = [[YZPerson alloc]init];
person.age = 10;
block = [^{
NSLog(@"---------%d", person.age);
} copy];
NSLog(@"block.class = %@",[block class]);
// MRC下,需要手動釋放
[person release];
}
NSLog(@"block銷毀");
[block release];
}
return 0;
輸出結果為,可知當 block為__NSMallocBlock__
類型時候,block是可以保住person的命的
iOS-block[3056:34126] block.class = __NSMallocBlock__
iOS-block[3056:34126] block銷毀
iOS-block[3056:34126] -[YZPerson dealloc]
__weak
修飾
- 如下代碼
// 定義block
typedef void (^YZBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
YZBlock block;
{
YZPerson *person = [[YZPerson alloc]init];
person.age = 10;
__weak YZPerson *weakPerson = person;
block = ^{
NSLog(@"---------%d", weakPerson.age);
};
NSLog(@"block.class = %@",[block class]);
}
NSLog(@"block銷毀");
}
return 0;
}
- 輸出為
iOS-block[3687:42147] block.class = __NSMallocBlock__
iOS-block[3687:42147] -[YZPerson dealloc]
iOS-block[3687:42147] block銷毀
-
生成cpp文件
-
註意:
-
在使用clang轉換OC為C++代碼時,可能會遇到以下問題
cannot create __weak reference in file using manual reference
-
解決方案:支持ARC、指定運行時系統版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
生成之後,可以看到,如下代碼,MRC情況下,生成的代碼明顯多了,這是因為ARC自動進行了copy操作
//copy 函數
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
//dispose函數
void (*dispose)(struct __main_block_impl_0*);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//weak修飾
YZPerson *__weak weakPerson;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, YZPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
//copy 函數
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
//dispose函數
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
};
//copy函數內部會調用_Block_object_assign函數
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
//asssgin會對對象進行強引用或者弱引用
_Block_object_assign((void*)&dst->person,
(void*)src->person,
3/*BLOCK_FIELD_IS_OBJECT*/);
}
//dispose函數內部會調用_Block_object_dispose函數
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->person,
3/*BLOCK_FIELD_IS_OBJECT*/);
}
小結
無論是MAC還是ARC
- 當block為
__NSStackBlock__
類型時候,是在棧空間,無論對外面使用的是strong 還是weak 都不會對外面的對象進行強引用 - 當block為
__NSMallocBlock__
類型時候,是在堆空間,block是內部的_Block_object_assign
函數會根據strong
或者weak
對外界的對象進行強引用或者弱引用。
其實也很好理解,因為block本身就在棧上,自己都隨時可能消失,怎麼能保住別人的命呢?
-
當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)
函數 | 調用時機 |
---|---|
copy函數 | 棧上的Block複製到堆上 |
dispose函數 | 堆上的block被廢棄時 |
__block
先從一個簡單的例子說起,請看下麵的代碼
// 定義block
typedef void (^YZBlock)(void);
int age = 10;
YZBlock block = ^{
NSLog(@"age = %d", age);
};
block();
代碼很簡單,運行之後,輸出
age = 10
上面的例子在block中訪問外部局部變數,那麼問題來了,如果想在block內修改外部局部的值,怎麼做呢?
修改局部變數的三種方法
寫成全局變數
我們把a定義為全局變數,那麼在哪裡都可以訪問,
// 定義block
typedef void (^YZBlock)(void);
int age = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
YZBlock block = ^{
age = 20;
NSLog(@"block內部修改之後age = %d", age);
};
block();
NSLog(@"block調用完 age = %d", age);
}
return 0;
}
這個很簡單,輸出結果為
block內部修改之後age = 20
block調用完 age = 20
對於輸出就結果也沒什麼問題,因為全局變數,是所有地方都可訪問的,在block內部可以直接操作age的記憶體地址的。調用完block之後,全局變數age指向的地址的值已經被更改為20,所以是上面的列印結果
static修改局部變數
// 定義block
typedef void (^YZBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int age = 10;
YZBlock block = ^{
age = 20;
NSLog(@"block內部修改之後age = %d", age);
};
block();
NSLog(@"block調用完 age = %d", age);
}
return 0;
}
上面的代碼輸出結果為
block內部修改之後age = 20
block調用完 age = 20
終端執行這行指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
把main.m
生成main.cpp
可以 看到如下代碼
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *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 *age = __cself->age; // bound by copy
(*age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_x4_920c4yq936b63mvtj4wmb32m0000gn_T_main_5dbaa1_mi_0, (*age));
}
可以看出,當局部變數用static修飾之後,這個block內部會有個成員是int *age
,也就是說把age的地址捕獲了。這樣的話,當然在block內部可以修改局部變數age了。
- 以上兩種方法,雖然可以達到在block內部修改局部變數的目的,但是,這樣做,會導致記憶體無法釋放。無論是全局變數,還是用static修飾,都無法及時銷毀,會一直存在記憶體中。很多時候,我們只是需要臨時用一下,當不用的時候,能銷毀掉,那麼第三種,也就是今天的主角
__block
隆重登場
__block
來修飾
代碼如下
// 定義block
typedef void (^YZBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
YZBlock block = ^{
age = 20;
NSLog(@"block內部修改之後age = %d",age);
};
block();
NSLog(@"block調用完 age = %d",age);
}
return 0;
}
輸出結果和上面兩種一樣
block內部修改之後age = 20
block調用完 age = 20
__block
分析
- 終端執行這行指令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
把main.m
生成main.cpp
首先能發現 多了__Block_byref_age_0
結構體
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 這裡多了__Block_byref_age_0類型的結構體
__Block_byref_age_0 *age; // by ref
// fp是函數地址 desc是描述信息 __Block_byref_age_0 類型的結構體 *_age flags標記
__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; //fp是函數地址
Desc = desc;
}
};
再仔細看結構體__Block_byref_age_0
,可以發現第一個成員變數是isa指針,第二個是指向自身的指針__forwarding
// 結構體 __Block_byref_age_0
struct __Block_byref_age_0 {
void *__isa; //isa指針
__Block_byref_age_0 *__forwarding; // 指向自身的指針
int __flags;
int __size;
int age; //使用值
};
查看main函數裡面的代碼
// 這是原始的代碼 __Block_byref_age_0
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
// 這是原始的 block代碼
YZBlock block = ((void (*)())&__main_block_impl_0(
(void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
代碼太長,簡化一下,去掉一些強轉的代碼,結果如下
// 這是原始的代碼 __Block_byref_age_0
__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
__Block_byref_age_0 age = {
0, //賦值給 __isa
(__Block_byref_age_0 *)&age,//賦值給 __forwarding,也就是自身的指針
0, // 賦值給__flags
sizeof(__Block_byref_age_0),//賦值給 __size
10 // age 使用值
};
// 這是原始的 block代碼
YZBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
// 這是簡化之後的 block代碼
YZBlock block = (&__main_block_impl_0(
__main_block_func_0,
&__main_block_desc_0_DATA,
&age,
570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
//簡化為
block->FuncPtr(block);
其中__Block_byref_age_0
結構體中的第二個(__Block_byref_age_0 *)&age
賦值給上面代碼結構體__Block_byref_age_0
中的第二個__Block_byref_age_0 *__forwarding
,所以__forwarding
裡面存放的是指向自身的指針
//這是簡化之後的代碼 __Block_byref_age_0
__Block_byref_age_0 age = {
0, //賦值給 __isa
(__Block_byref_age_0 *)&age,//賦值給 __forwarding,也就是自身的指針
0, // 賦值給__flags
sizeof(__Block_byref_age_0),//賦值給 __size
10 // age 使用值
};
結構體__Block_byref_age_0
中代碼如下,第二個__forwarding
存放指向自身的指針,第五個age
裡面存放局部變數
// 結構體 __Block_byref_age_0