一. Block的概念
1.1 帶有自動(dòng)變量的匿名函數(shù)纯出。因?yàn)楹瘮?shù)是對(duì)象,所以block本身也是一個(gè)對(duì)象
將Objective-C編譯為C++代碼
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 'class.m' -o 'class.cpp'
如果帶有weak需要運(yùn)行時(shí)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios8.0.0 'class.m' -o 'class.cpp'
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;
}
};
1.2 Block語法
^``返回值類型``參數(shù)列表``表達(dá)式
^ int (int count) { return count + 1; }
可以省略為:
^``參數(shù)列表``表達(dá)式
注:省略返回值類型時(shí)敷燎,如果表達(dá)式中有retrun語句就使用該返回值的類型暂筝,如果表達(dá)式中沒有return語句就使用void類型。表達(dá)式中含有多個(gè)return語句時(shí)硬贯,所有return的返回值類型必須相同焕襟。
^ (int count){ return count + 1; };
如果沒有參數(shù)還可以省略為以下形式
^``表達(dá)式
^{printf("Blocks\n")}
1.3 完整形式的Block語法與一般的C語言函數(shù)定義相比,僅有兩點(diǎn)不同
- 沒有函數(shù)名
- 帶有
^
// c
void func(int a) {
}
// block
^(int a) {
};
二. Block類型變量
2.1 C語言中將函數(shù)地址賦值給函數(shù)指針類型變量:
Block除了沒有名稱以及帶有^
以外鸵赖,其他都與C語言函數(shù)定義相同它褪。在定義C語言函數(shù)時(shí)翘悉,就可以將所定義函數(shù)的地址賦值給函數(shù)指針類型變量中。
int func(int count) {
return count + 1;
}
int (* funcptr) (int count) = &func;
2.2 聲明Block類型變量包吝,在Block語法中源葫,也可將Block語法賦值給聲明為Block類型的變量中
同樣,在Block語法中息堂,可將Block語法賦值給聲明為block類型的變量中块促。即源代碼中一旦使用Block語法就相當(dāng)于生成了可賦值給Block類型變量的“值”竭翠。Blocks中由Block語法生成的值也被稱為“Block”薇搁。在有Block的文檔中观蜗,“Block”既指源代碼中的Block語法,也指由Block語法所生成的值宏娄。
// Block類型變量結(jié)構(gòu)
return_type (^blockName)(parameters)
// Block類型變量結(jié)構(gòu)舉例
int (^ blockName)(int count);
// Block語法
^ (int count){
return count + 1;
};
// 使用Block語法將Block賦值為Block類型變量
int (^blockName)(int count) = ^ (int count){
return count + 1;
};
賦值過程可以描述為:
由^
開始的Block語法生成的Block被賦值給變量blackName中。
2.3與前面的使用函數(shù)指針的源代碼相比而知粮宛,聲明Block類型變量僅僅是將聲明函數(shù)指針類型變量的^
變?yōu)?code>^卖宠。該Block類型變量與一般的C語言變量完全相同,可作為以下用途使用:
-
自動(dòng)變量(局部變量)
因?yàn)榕c通常的變量相同筷畦,所以當(dāng)然也可以由Block類型變量向Block類型變量賦值蜒秤。如下:
int (^blackName1)(int count) = blockName;
-
函數(shù)參數(shù)
作為函數(shù)參數(shù)void func(int (^blockName)(int)) { }
將Block作為函數(shù)的返回值返回
int (^func())(int) { return ^(int count){return count + 1;}; }
在使用Block的時(shí)候我們一般 使用typedef 來聲明blockName類型的變量
void func(int (^blockName)(int)) { } 可改寫為: typedef int (^blockName)(int); void func(blockName) { }
Block類型變量可完全像通常的C語言變量一樣使用作媚,因此也可以使用指向Block類型變量的指針纸泡,即Block的指針類型變量
typedef int (^blk_t)(int); blk_t blk = ^(int count) {return count + 1;}; __strong blk_t *blkptr = &blk; // C語言中的代碼因?yàn)闆]有引用計(jì)數(shù)內(nèi)存管理赖瞒,在Xcode中需要指定修飾符 (*blkptr)(10);
靜態(tài)變量
靜態(tài)全局變量
全局變量
三. Block的分類 NSGlobalBlock、NSMallocBlock吧兔、NSStackBlock
NSGlobalBlock 調(diào)用copy 還是 NSGlobalBlock
NSStackBlock 調(diào)用copy 變成 NSMallocBlocl
NSMallocBlock 調(diào)用copy 還是 NSMallocBlock 只不過是引用計(jì)數(shù)加一
-
NSGlobalBlock
void (^globalBlock)(void) = ^{ }; NSLog(@"%@",globalBlock); <__NSGlobalBlock__: 0x107bf6060>
存儲(chǔ)在靜態(tài)區(qū)
-
NSMallocBlock
__block int a = 10; void (^mallocBlock)(void) = ^ { a++; }; mallocBlock(); NSLog(@"%@",mallocBlock);
存在堆區(qū)
-
NSStackBlock
int a = 10; NSLog(@"%@",^{ NSLog(@"%d",a); });
存儲(chǔ)在棧區(qū)
四. 解決循環(huán)引用的三種方式
4.1 __weak 并使用 strong引用
self.string = @"helloWorld";
__weak typeof(self)weakSelf = self;
self.blockName = ^{
__strong typeof(weakSelf)strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.string);
});
};
self.blockName();
此種方式會(huì)延遲delloc的執(zhí)行時(shí)間境蔼,但是能夠保證strongSelf.string正常輸出,輸出順序?yàn)?br>
helloWorld
delloc
如果不用__strong強(qiáng)引用weakSelf的話逢享,weakSelf.string的輸出會(huì)為nill,輸出順序?yàn)閐elloc
null
4.2 __block
self.string = @"helloWorld";
__block XSYViewController *vc = self;
self.blockName = ^{
NSLog(@"%@",vc.string);
vc = nil;
};
self.blockName();
因?yàn)開_block修飾符會(huì)截獲vc的指針吴藻,self->block->vc->self。形成了死循環(huán)侧但,但是vc置為nil,環(huán)被打開弦叶,所以此方式可以防止死循環(huán)
4.3 傳參數(shù)
self.blockName = ^(XSYViewController *vc) {
NSLog(@"%@",vc.string);
};
self.blockName(self);
五. block底層源碼分析
5.1 沒有截獲自動(dòng)變量時(shí)的Block
block的內(nèi)存布局
void (^blockName)(void) = ^{
printf("hello world");
}
blockName();
NSGlobalBlock源碼分析:
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;
__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語法轉(zhuǎn)換的C語言函數(shù)
^ {printf("hello word");};的源碼
*/
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello word");
}
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)};
// block的執(zhí)行過程
int main() {
/*
struct __main_block_impl_0 *tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blockName = &tmp;
描述:該源代碼將__main_block_impl_0結(jié)構(gòu)體類型的自動(dòng)變量燕侠,即棧上生成的__main_block_impl_0結(jié)構(gòu)體實(shí)例的指針,賦值給__main_block_impl_0結(jié)構(gòu)體指針類型的變量blackName立莉。一下為這部分代碼對(duì)應(yīng)的最初源代碼:
void (^blockName)(void) = ^{
printf("hello world");
};
這句話也可描述為:將Block語法生成的Block賦值給Block類型的變量blockName。它等同于將__main_blocl_impl_0結(jié)構(gòu)體實(shí)例的指針賦值給變量__main_blocl_impl_0結(jié)構(gòu)體類型的變量blockName茫舶。
*/
void(*blockName)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/*
blockName() 的源碼刹淌, 去掉轉(zhuǎn)換部分(*blockName->impl.FuncPtr)(blockName);
*/
((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
return 0;
}
5.2 截獲自動(dòng)變量時(shí)的Block
int a = 10;
int (^block)(void) = ^{
NSLog(@"%d",a);
};
block();
六. “id”這一變量類型用于存儲(chǔ)Objective-C對(duì)象。
id 為objc_object結(jié)構(gòu)體的指針類型疹启。
typedef struct objc_object {
Class isa;
} id;
Class為objc_class結(jié)構(gòu)體的指針類型
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
objc_object 結(jié)構(gòu)體和objc_class結(jié)構(gòu)體歸根到底是在各個(gè)對(duì)象和類的實(shí)現(xiàn)中使用的最基本的結(jié)構(gòu)體喊崖。下面我們通過編寫簡單的Objective-C類聲明來確認(rèn)下:
@interface MyObject : NSObject
{
int val0,
int val1,
}
基于objc_object結(jié)構(gòu)體雇逞,該類的對(duì)象的結(jié)構(gòu)如下:
struct MyObject {
Class isa;
int val0;
int val1;
}
對(duì)象都是結(jié)構(gòu)體實(shí)例:
MyObject類的實(shí)例變量val0和val1被直接聲明為對(duì)象的結(jié)構(gòu)體成員〗诜拢“Objective-C中由類生成對(duì)象”意味著掉蔬,像該結(jié)構(gòu)體這樣“生成由該類生成的對(duì)象的結(jié)構(gòu)體實(shí)例”查近。生成的各個(gè)對(duì)象霜威,即由該類生成的對(duì)象的各個(gè)結(jié)構(gòu)體實(shí)例,通過成員變量isa保持該類的結(jié)構(gòu)體實(shí)例指針戈泼。如圖:
5. 如何區(qū)分三種NSGlobalBlock大猛、NSMallocBlock淀零、NSStackBlock
6. 執(zhí)行delloc方法會(huì)讓自動(dòng)釋放池工作嗎?
7. 自動(dòng)釋放池是每個(gè)對(duì)象都有的嗎驾中?
8. __strong 修飾 帶有__weak修飾符的變量原理
9. 在block里改變基本類型的變量也需要添加__block
int a = 10
void(^block)(void) = ^ {
a++; // 會(huì)報(bào)錯(cuò)
};
block()
原因:因?yàn)閍在棧區(qū)肩民,需要拷貝到堆區(qū)
有__block底層進(jìn)行了地址的傳遞(指針傳遞)
沒有__block底層進(jìn)行了值的傳遞(值傳遞)
10.查看block的源碼步驟
- cd XcodeDemo/
- mkdir testBlock
- cd testBlock/
- vim block.c
#include "stdio.h" int main() { void(^block)(void) = ^{ printf("hello word"); }; block(); return 0; }
- gcc block.c // 編譯
- ./a.out // 執(zhí)行
- clang -rewrite-objc block.c -o blockCPP.cpp // 用于指定輸出(out)文件名
- 完成
block的copy操作,在堆上
- block作為返回值灶搜,在arc中會(huì)自動(dòng)copy
- 當(dāng)block被_strong指向是工窍,在arc中自動(dòng)copy
- enumerateObjectsUsingBlock:帶有UsingBlock參數(shù)名的也會(huì)自動(dòng)copy
- 作為gcd API的參數(shù)自動(dòng)copy
被__block修飾的對(duì)象類型
- 當(dāng)__block變量在棧上時(shí)不會(huì)對(duì)指向的類型產(chǎn)生強(qiáng)引用
- 當(dāng)block變量被拷貝到堆上時(shí)
會(huì)調(diào)用block內(nèi)部的copy函數(shù),
copy內(nèi)部會(huì)調(diào)用_block_objct_assing函數(shù)鹏溯,
_block_objct_assign 函數(shù)會(huì)根據(jù)指向?qū)ο蟮男揎椃?_strong, __weak, __unsafe_unretain)做出響應(yīng)的操作纵苛,形成強(qiáng)引用(retain)或弱引用(注意:這里僅限于ARC中時(shí)會(huì)retain言津,MRC時(shí)不會(huì)retain) - 如果__block變量在堆中移除,
會(huì)調(diào)用block內(nèi)部的dispose函數(shù)悬槽,
dispose函數(shù)內(nèi)部會(huì)調(diào)用__block_object_dispose函數(shù)
__block_object_dispose函數(shù)會(huì)自動(dòng)釋放指向的對(duì)象(release)
block的原理是什么?
帶有自動(dòng)變量的匿名函數(shù)
封裝了函數(shù)調(diào)用的oc對(duì)象
__block 的作用是什么蓬坡?有什么注意點(diǎn)
一旦使用了__block,會(huì)將變量包裝成帶有此變量的結(jié)構(gòu)體屑咳。不管是int還是對(duì)象,都會(huì)產(chǎn)生內(nèi)存管理杖爽。
為了修改變量的值
block的屬性修飾詞為什么是copy紫皇?調(diào)用block有哪些使用注意
block不經(jīng)過copy,會(huì)存在于棧上或全局區(qū)聪铺,為了將block拷貝到堆上
避免循環(huán)引用
block在修改NSMutableArray時(shí),需不需要添加__block?
不需要撒桨,只有在重新賦值的時(shí)候才需要番宁。能不加盡量不加,因?yàn)闀?huì)生成新的對(duì)象踱蠢,耗費(fèi)內(nèi)存
參考文獻(xiàn)
- 《Objective-C高級(jí)編程》
- 大廠核心block技術(shù)分享