在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
可以用一句话来表示Block:带有自动变量(局部变量)的匿名函数。 在iOS中使用“^”来声明一个Block。Block的内容是包含在“{}”中的,并且和C语言一样用“;”来表示语句的结束,标准语法如下所示: //完整语法 ^ 返回值类型 参数列表 表达式 //省略返回值 ^ 参数列表 表达式 //省略参数列表 ^ 返回值类型 表达式 //省略返回值和参数列表 ^ 表达式 从上面可以看到,Block和函数很相似,具体体现在这些方面:
我们通常使用如下形式将Block赋值给Block类型变量,示例代码如下: int multiplier = 7; int (^myBlock)(int) = ^(int num){ return multiplier * num; }; NSLog(@"%d",myBlock(3)); 采用这种方式在函数参数或返回值中使用Block类型变量时,记述方式极为复杂。这时,我们可以使用typedef来解决该问题。 示例1:没有使用typedef - (void)loadDataFromUrl:(void(^)(NSString *))retData { } 示例2:使用typedef typedef void(^RetDataHandler)(NSString *); - (void)loadDataFromUrl:(RetDataHandler)retData { } 从上面的代码可以看到,使用typedef声明之后,在方法中传递block参数时,更容易理解。 Block的强大之处是:在声明它的范围里,所有变量都可以为其所捕获。下面我们来看看自动变量。 2.自动变量从上面Block语法的介绍中,我们可以理解“带有自动变量(局部变量)的匿名函数”中的匿名函数。那么“带有自动变量(局部变量)”是什么呢?这个在Block中表现为“截获自动变量值”。示例如下: int iCode = 10; NSString *strName = @"Tom"; void (^myBlock)(void) = ^{ // 结果:My name is Tom,my code is 10 NSLog(@"My name is %@,my code is %d", strName, iCode); }; iCode = 20; strName = @"Jim"; myBlock(); // 结果:My name is Jim,my code is 20 NSLog(@"My name is %@,my code is %d", strName, iCode); 从代码中可以看到,Block表达式截获所使用的自动变量iCode和strName的值,即保存该自动变量的瞬间值。因为Block表达式保存了自动变量的值,所以在执行Block语法后,即使改写Block中所用的自动变量的值也不会影响Block执行时自动变量的值,这就是自动变量值的截获。 如果我们想在Block中修改截获的自动变量值,会有什么结果?咱们做个尝试:
从上面可以看到,该源代码会产生编译错误。若想在Block语法的表达式中将值赋给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符,示例如下: __block NSString *strName = @"Tom"; void (^myBlock)(void) = ^{ strName = @"Sky"; }; strName = @"Jim"; myBlock(); // 结果:My name is Sky NSLog(@"My name is %@",strName); 需要说明的是,对于截获的自动变量,调用变更该对象的方法是没有问题的:即赋值给截获的自动变量会产生编译错误,但使用截获的自动变量的值却不会有任何问题。 3.如何在代码中创建Block?3.1不带参数和返回值的block- (void)testBlockOne { void (^myBlock)(void) = ^{ NSLog(@"Hello Block One"); }; NSLog(@"%@",myBlock); myBlock(); } 3.2带参数的block- (void)testBlockTwo{ void (^myBlock)(NSString *) = ^(NSString *str){ NSLog(@"Hello Block %@",str); }; NSLog(@"%@",myBlock); myBlock(@"ligf"); } 3.3带参数和返回值的block- (void)testBlockThree{ int (^myBlock)(NSString *,int) = ^(NSString *str,int code){ NSLog(@"Hello Block %@,code is %d", str, code); return 1; }; NSLog(@"%@",myBlock); int iRet = myBlock(@"ligf",3); NSLog(@"%d",iRet); } 4.block实现页面传值4.1先用传统的Delegate来进行示例WebServicesHelper类: @class WebServicesHelper; @protocol WebServicesHelperDelegate <NSObject> - (void)networkFecherSuccess:(WebServicesHelper *)networkFetcher didFinishWithData:(NSString *)data; - (void)networkFecherFailed:(WebServicesHelper *)networkFetcher error:(NSError *)error; @end @interface WebServicesHelper : NSObject @property (nonatomic, retain) NSURL *url; @property (nonatomic, assign) id<WebServicesHelperDelegate> delegate; - (id)initWithUrl:(NSURL *)url; - (void)startDownload; @end - (id)initWithUrl:(NSURL *)url { self = [super init]; if (self) { self.url = url; } return self; } - (void)startDownload { NSError *error = nil; NSString *str = [NSString stringWithContentsOfURL:self.url encoding:NSUTF8StringEncoding error:&error]; if (error) { [_delegate networkFecherFailed:self error:error]; } else { [_delegate networkFecherSuccess:self didFinishWithData:str]; } } DownloadByDelegate类: #import "WebServicesHelper.h" @interface DownloadByDelegate : NSObject<WebServicesHelperDelegate> - (void)fetchUrlData; @end @implementation DownloadByDelegate - (void)fetchUrlData { NSURL *url = [[NSURL alloc] initWithString:@"http://www.baidu.com"]; WebServicesHelper *webServicesHelper = [[WebServicesHelper alloc] initWithUrl:url]; webServicesHelper.delegate = self; [webServicesHelper startDownload]; } - (void)networkFecherSuccess:(WebServicesHelper *)networkFetcher didFinishWithData:(NSString *)data { NSLog(@"%@",data); } - (void)networkFecherFailed:(WebServicesHelper *)networkFetcher error:(NSError *)error; { NSLog(@"%@",error); } @end 调用: DownloadByDelegate *downloadByDelegate = [[DownloadByDelegate alloc] init];
[downloadByDelegate fetchUrlData];
4.2再看看用Block的实现DownloadByBlock类: typedef void(^NetworkFetcherCompletionHandler) (NSString *data, NSError *error); @interface DownloadByBlock : NSObject - (id)initWithUrl:(NSURL *)url; - (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)completion; @end @implementation DownloadByBlock { NSURL *_url; } - (id)initWithUrl:(NSURL *)url { self = [super self]; if (self) { _url = url; } return self; } - (void)startWithCompletionHandler:(NetworkFetcherCompletionHandler)completion { NSError *error; NSString *str = [NSString stringWithContentsOfURL:_url encoding:NSUTF8StringEncoding error:&error]; completion(str,error); } @end 调用: DownloadByBlock *downloadByBlock = [[DownloadByBlock alloc] initWithUrl:[NSURL URLWithString:@"http://www.baidu.com"]]; [downloadByBlock startWithCompletionHandler:^ (NSString *data, NSError *error){ NSLog(@"%@",data); }]; 从上面的代码可以明显看到,使用Block方式,代码的可读性更高,使用也更加的方便。 5.Block存储域先看一个示例: int (^myBlockOne)(int,int) = ^ (int a, int b) { return a + b; }; //myBlockOne = <__NSGlobalBlock__: 0x101f1d230> NSLog(@"myBlockOne = %@", myBlockOne); int base = 100; int (^myBlockTwo)(int,int) = ^ (int a, int b) { return base + a + b; }; //MRC:myBlockTwo = <__NSStackBlock__: 0x7fff5dce6520> //ARC:myBlockTwo = <__NSMallocBlock__: 0x6000002441d0> NSLog(@"myBlockTwo = %@", myBlockTwo); int (^myBlockThree)(int,int) = [[myBlockTwo copy] autorelease]; //myBlockThree = <__NSMallocBlock__: 0x6080000499c0> NSLog(@"myBlockThree = %@", myBlockThree); 从上面的代码可以看到,Block在内存中的位置可以分为三种类型:__NSGlobalBlock__,__NSStackBlock__, __NSMallocBlock__。
其中在全局区域和堆里面存储的对象是相对安全的,但是在栈区里面的变量是危险的,有可能造成程序的崩溃,因此在iOS中如果使用block的成员变量或者属性时,需要将其copy到堆内存中。 上面的例子中,myBlockOne和myBlockTwo的区别在于:myBlockOne没有使用Block以外的任何外部变量,Block不需要建立局部变量值的快照,这使myBlockOne与函数没有任何区别。myBlockTwo与myBlockOne唯一不同是的使用了局部变量base,在定义(注意是定义,不是运行)myBlockTwo时,局部变量base当前值被截获到栈上,作为常量供Block使用。执行下面代码,结果是103,而不是203。 int base = 100; int (^myBlockTwo)(int,int) = ^ (int a, int b) { return base + a + b; }; base = 200; NSLog(@“%d", myBlockTwo(1, 2)); 我们再看一段代码,大家思考一下这段代码有没有问题? int base = 100; void(^myBlock)(); if (YES) { myBlock = ^{ NSLog(@"This is ture,%d", base); }; } else { myBlock = ^{ NSLog(@"This is false,%d", base); }; } myBlock(); 表面上看,和我们以前给变量赋值的语句没什么太大的差异,那么是不是没有问题呢?答案是NO。在定义这个块的时候,其所占的内存区域是分配在栈中的,块只在定义它的那个范围内有效,也就是说这个块只在对应的if或else语句范围内有效。当离开了这个范围之后,编译器有可能把分配给块的内存覆写掉。这样运行的时候,若编译器未覆写待执行的块,则程序照常运行;若覆写,则程序崩溃。 5.1__NSGlobalBlock__的实现我们先写一段生成__NSGlobalBlock__的代码: #include <stdio.h> void (^gofBlock)(void) = ^{ printf("Hello, Gof"); }; int main(int argc, char * argv[]) { gofBlock(); return 0; } 对上面的代码使用“clang -rewrite-objc main.m”进行编译,生成后的代码整理之后如下: struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; struct __gofBlock_block_impl_0 { struct __block_impl impl; struct __gofBlock_block_desc_0* Desc; __gofBlock_block_impl_0(void *fp, struct __gofBlock_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteGlobalBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __gofBlock_block_func_0(struct __gofBlock_block_impl_0 *__cself) { printf("Hello, Gof"); } static struct __gofBlock_block_desc_0 { size_t reserved; size_t Block_size; } __gofBlock_block_desc_0_DATA = { 0, sizeof(struct __gofBlock_block_impl_0)}; static __gofBlock_block_impl_0 __global_gofBlock_block_impl_0((void *)__gofBlock_block_func_0, &__gofBlock_block_desc_0_DATA); void (*gofBlock)(void) = ((void (*)())&__global_gofBlock_block_impl_0); int main(int argc, char * argv[]) { ((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock); return 0; } 我们将源代码分成几个部分来逐步理解。 第一部分:源码中的Block语法 ^{ printf("Hello, Gof"); }; 可以看到,变换后的代码中也含有相同的表达式: static void __gofBlock_block_func_0(struct __gofBlock_block_impl_0 *__cself) { printf("Hello, Gof"); } 从代码可以看到,通过Blocks使用的匿名函数,实际上被作为简单的C语言函数来处理。该函数名根据Block语法所属的函数名和该Block语法在函数出现的顺序值(这里为0)来命名。函数的参数__cself为指向__gofBlock_block_impl_0结构体的指针。 第二部分:__gofBlock_block_impl_0结构体 struct __gofBlock_block_impl_0 { struct __block_impl impl; struct __gofBlock_block_desc_0* Desc; __gofBlock_block_impl_0(void *fp, struct __gofBlock_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteGlobalBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 我们先不看构造函数,__gofBlock_block_impl_0包含两个成员变量:impl和Desc。 struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; static struct __gofBlock_block_desc_0 { size_t reserved; size_t Block_size; } 从这两个结构体的声明,可以知道:impl包含某些标志、所需区域、函数指针等信息;Desc包含所需区域、Block大小信息。 接下来我们看看__gofBlock_block_impl_0构造函数的调用: static __gofBlock_block_impl_0 __global_gofBlock_block_impl_0((void *)__gofBlock_block_func_0, &__gofBlock_block_desc_0_DATA); void (*gofBlock)(void) = ((void (*)())&__global_gofBlock_block_impl_0); 继续看__gofBlock_block_impl_0构造函数的两个参数:
通过参数的配置,我们来看看__gofBlock_block_impl_0结构体的初始化: isa = &_NSConcreteGlobalBlock; 关于_NSConcreteGlobalBlock,我们可以看看RunTime之类与对象。实际上,__gofBlock_block_impl_0结构体相当于基于objc_object结构体的OC类对象的结构体。对其中的isa成员变量初始化,_NSConcreteGlobalBlock相当于objc_class结构体实例。在将Block作为OC的对象处理时,关于该类的信息放置于_NSConcreteGlobalBlock中。 第三部分:Block的调用。 gofBlock(); 变换后的代码: ((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock);
去掉转换部分: (*gofBlock->impl.FuncPtr)(gofBlock); 这实际上就是使用函数指针调用函数__gofBlock_block_func_0。另外,我们也可以看到,__gofBlock_block_func_0函数的参数__cself指向Block值。 总结一下:
5.2__NSStackBlock__的实现先看代码: #include <stdio.h> int main(int argc, char * argv[]) { int a = 18; void (^gofBlock)(void) = ^{ printf("I have %d ages", a); }; gofBlock(); return 0; } 对上面的代码使用“clang -rewrite-objc main.m”进行编译,生成后的代码整理之后如下: 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; int a; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy printf("I have %d ages", a); } 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, char * argv[]) { int a = 18; void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); ((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock); return 0; } 这和上面转换的源代码有一点点差异。 首先,Block语法表达式中的自动变量被作为成员变量追加到了__main_block_impl_0结构体中。 其次,在调用__main_block_impl_0构造函数初始化的时候,对由自动变量追加的成员变量进行了初始化。 void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); 通过参数的配置,__main_block_impl_0结构体构造函数的初始化: isa = &_NSConcreteStackBlock; Flags = 0; Reserved = 0; FuncPtr = __main_block_func_0; Desc = &__main_block_desc_0_DATA; a = 18; 再次,Block匿名函数的实现: static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy printf("I have %d ages", a); } 截获到__main_block_impl_0结构体实例的成员变量上的自动变量a,该变量在Block语法表达式之前被声明定义。 总结一下:
现在我们修改一下上面的代码,在变量前面增加 __block 关键字: #include <stdio.h> int main(int argc, char * argv[]) { __block int a = 18; void (^gofBlock)(void) = ^{ a = 20; printf("I have %d ages", a); }; gofBlock(); printf("a variable is %d", a); return 0; } 使用“clang -rewrite-objc main.m”进行编译: struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; 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 __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; printf("I have %d ages", (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, char * argv[]) { __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18}; void (*gofBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344)); ((void (*)(__block_impl *))((__block_impl *)gofBlock)->FuncPtr)((__block_impl *)gofBlock); printf("a variable is %d", (a.__forwarding->a)); return 0; } 从编译之后的源代码可以看到,只加了一个__block关键字,源码数量大大增加。 首先,我们看看这句: __block int a = 18; 编译之后: __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18}; 去掉类型转换: __Block_byref_a_0 a = { 0, &a, 0, sizeof(__Block_byref_a_0), 18 }; 从源码可以看到,这个__block变量变成了__Block_byref_a_0结构体类型的自动变量。 接下来,我们看看Block匿名函数的实现: 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; printf("I have %d ages", (a->__forwarding->a)); } __Block_byref_a_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量a。 总结一下:
5.3__NSMallocBlock__的实现__NSMallocBlock__类型的 block 通常不会在源码中直接出现,因为默认它是当一个 block 被 copy 的时候,才会将这个 block 复制到堆中。以下是一个 block 被 copy 时的示例代码 (来自 这里),可以看到,在第 8 步,目标的 block 类型被修改为 _NSConcreteMallocBlock。 static void *_Block_copy_internal(const void *arg, const int flags) { struct Block_layout *aBlock; const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE; // 1 if (!arg) return NULL; // 2 aBlock = (struct Block_layout *)arg; // 3 if (aBlock->flags & BLOCK_NEEDS_FREE) { // latches on high latching_incr_int(&aBlock->flags); return aBlock; } // 4 else if (aBlock->flags & BLOCK_IS_GLOBAL) { return aBlock; } // 5 struct Block_layout *result = malloc(aBlock->descriptor->size); if (!result) return (void *)0; // 6 memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first // 7 result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed result->flags |= BLOCK_NEEDS_FREE | 1; // 8 result->isa = _NSConcreteMallocBlock; // 9 if (result->flags & BLOCK_HAS_COPY_DISPOSE) { (*aBlock->descriptor->copy)(result, aBlock); // do fixup } return result; } 5.4Block的copy、retain、release
|
请发表评论