本篇文章主要講block的基本使用和底層實現,以下將block的講解分成三小節:
一. 什么是block
block 表面語義指“塊”,在Objective-C中block就是能執行某些任務的代碼塊,是對C語言的擴充,從它的語法上看(請看 block的語法),block相當于帶有局部變量的匿名函數。在Objective-C編程中使用block可以簡化代碼結構,使代碼的業務邏輯更清晰。
二. block語法
1.block代碼體定義
^返回類型(參數0,參數1,……){……} 其中返回類型,參數列表可以省略
示例代碼:
帶參有返回值:^void(int i){ NSLog(@"%d",i);}
無參無返回值:^{NSLog(@"I'am block");}
2.block 類型變量
從上面的block代碼體的定義中,知道了怎樣寫一個代碼塊。但問題是如何來引用該代碼塊? 拋磚引玉,又如我們怎樣引用字符串@"hello world"
,一般會先定義字符串類型變量再把字符串賦值給該變量:NSString *str=@"hello world"
,之后就可以通過str
變量來引用@"hello world"
。同理,要引用代碼塊,我們可以先定義一個block 類型變量,block類型變量定義:
返回類型 (^變量名)(參數0,參數1,...)
示例:
void (^myprint)(int i)=^void(int i){NSLog(@"%d",i);};
void (^myprint)(int i)=^(int i){NSLog(@"%d",i);};
void (^myprint)(void)=^{NSLog(@"hello world");};
注意:block類型變量定義,即使它的返回類型為void和參數列表為空都不能省略
3.用typedef 定義block 數據類型
如果block類型變量在代碼中多處出現或作為函數的參數和返回值時,它的書寫將變得很繁雜。通常,我們會用宏定義typedef 來重新定義block數據類型
typedef 返回類型(^代碼塊類型名)(參數列表);
示例:
typedef void(^Myprint)(int i);
#import <Foundation/Foundation.h>
int main(int argc,const char *argv[]){
@autoreleasepool{
Myprint myprint=^void(int i){NSLog(@"%d",i);};
myprint(1);//使用block
}
return 0;
}
三. block的底層實現
想窺探block的底層實現,我們要把OC反編譯成C++語言,看看block整體定義包含了哪些內容。貼出要做反編譯的OC代碼文件 main.m:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^myprint)(void) = ^{
NSLog(@"Hello World!");
};
myprint();
}
return 0;
}
進入到工程目錄,在終端執行編譯指令:clang -rewrite-objc main.m
,
編譯后可以在main.cpp文件的尾部可以看到block 的整體定義有四部分:
//1.此結構體記錄block的描述信息,它在定義時順便初始化了個實例__main_block_desc_0_DATA
static struct __main_block_desc_0 {
//size_t可以理解成 unsigned long(但不嚴謹)
size_t reserved; //指明block在內存中要保留一塊內存空間的大小,這塊內存區暫沒用途。
size_t Block_size;//指明block的大小 == sizeof(struct __main_block_impl_0)
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
//2.存儲與block實現相關的信息
static struct __block_impl {
void *isa;//isa是一個void *類型(空指針類型可以指向任何類型,相當于id),在這里isa是指向了block實例,也就是指向了自己在內存中的起始地址
int Flags; //系統默認值為0
int Reserved; //構造方法里沒看到賦值,應該是用來存儲block保留內存空間大小
void *FuncPtr; //指向block代碼體實現的函數指針,block的調用關鍵就它了
}
//3.
//__main_block_impl_0就是block(myprint)的定義,它就是一個結構體里面包含了另外兩個struct和一個結構體的構造方法(用來初始化一個結構體實例)
struct __main_block_impl_0 {
struct __block_impl impl; //存儲與block實現(代碼體)相關的信息,請看//2.
struct __main_block_desc_0* Desc; //存儲block的描述信息
//構造方法,這個方法是重點
__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;
}
};
//4.block代碼體的實現函數,就是一個C函數,可以通過函數指針來調用
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello World!");
}
//這里是主函數,調用了block
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//下面做了各種轉換,拆解一下
//1.void (*myprint)(void):聲明一個返回類型為空,參數為空,名稱叫myprint的函數指針,它指向了block的構造方法,實質它是指向block結構體實例的指針
//2.(void (*)(__block_impl *))這是一個返回值為void,參數類型為__block_impl *的指針類型,用來修飾FuncPtr
void (*myprint)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *)) ((__block_impl *)myprint)->FuncPtr)((__block_impl *)myprint);
}
return 0;
}
//轉換簡化一下如下:
//struct __main_block_impl_0 tmp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
//struct __main_block_impl_0 *myprint = &tmp;
//(*myprint->impl.FuncPtr)(myprint);
由block的底層定義可以知道,block類型變量其實它是一個結構體實例與Objective-C對象極為相似,它的底層定義也解釋了block為什么既可以像變量一樣定義和作為參數被傳遞,也可以像函數一樣被調用。
Block還有其它重要知識點:
- 基本類型變量與對象的截取
- __block修飾符
- block存儲域
- copy的使用
- 相互引用問題