Objective-C中的Block
1.Block定义
可以用一句话来表示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 10NSLog(@"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 *)networkFetcherdidFinishWithData:(NSString *)data; - (void)networkFecherFailed:(WebServicesHelper *)networkFetchererror:(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 *)networkFetcherdidFinishWithData:(NSString *)data {NSLog(@"%@",data); }- (void)networkFecherFailed:(WebServicesHelper *)networkFetchererror:(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__。
- __NSGlobalBlock__:与全局变量一样,该类对象存储在程序的数据区域(.data区)中;
- __NSStackBlock__:从名称中可以看到含有“Stack”,即该类对象存储在栈上;位于栈上的Block对象,函数返回后Block将无效,变成野指针;
- __NSMallocBlock__:该类对象由malloc函数分配的内存块(堆)中。
其中在全局区域和堆里面存储的对象是相对安全的,但是在栈区里面的变量是危险的,有可能造成程序的崩溃,因此在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_func_0是由Block语法转换的C语言函数指针;
- __gofBlock_block_desc_0_DATA是作为静态全局变量初始化的__gofBlock_block_desc_0结构体实例指针。
通过参数的配置,我们来看看__gofBlock_block_impl_0结构体的初始化:
isa = &_NSConcreteGlobalBlock;Flags = 0; Reserved = 0; FuncPtr = __gofBlock_block_func_0; Desc = &__gofBlock_block_desc_0_DATA;
关于_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值。
总结一下:
- block 实际是一个对象,它主要由 一个 impl 和 一个 Desc 组成。
- impl.FuncPtr是实际的函数指针,在这里它指向 __gofBlock_block_func_0。
- Desc 用于描述当前这个 block 的附加信息的,包括结构体的大小,需要 capture 和 dispose 的变量列表等。结构体大小需要保存是因为,每个 block 因为会 capture 一些变量,这些变量会加到 __gofBlock_block_impl_0 这个结构体中,使其体积变大。
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语法表达式之前被声明定义。
总结一下:
- isa 指向 _NSConcreteStackBlock,说明这是一个分配在栈上的实例。
- main_block_impl_0 中增加了一个变量 a,在 block 中引用的变量 a 实际是在申明 block 时,被复制到 main_block_impl_0 结构体中的那个变量 a。因为这样,我们就能理解,在 block 内部修改变量 a 的内容,不会影响外部的实际变量 a。
- main_block_impl_0 中由于增加了一个变量 a,所以结构体的大小变大了,该结构体大小被写在了 main_block_desc_0 中。
现在我们修改一下上面的代码,在变量前面增加 __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。
总结一下:
- 源码中增加一个名为 __Block_byref_a_0 的结构体,用来保存我们要 capture 并且修改的变量 a。
- main_block_impl_0 中引用的是 __Block_byref_a_0 的结构体指针,这样就可以达到修改外部变量的作用。
- __Block_byref_a_0 结构体中带有 isa,说明它也是一个对象。
- 我们需要负责 __Block_byref_a_0 结构体相关的内存管理,所以 main_block_desc_0 中增加了 copy 和 dispose 函数指针,对于在调用前后修改相应变量的引用计数。
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;// 1if (!arg) return NULL;// 2aBlock = (struct Block_layout *)arg;// 3if (aBlock->flags & BLOCK_NEEDS_FREE) {// latches on highlatching_incr_int(&aBlock->flags);return aBlock;}// 4else if (aBlock->flags & BLOCK_IS_GLOBAL) {return aBlock;}// 5struct Block_layout *result = malloc(aBlock->descriptor->size);if (!result) return (void *)0;// 6memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first// 7result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not neededresult->flags |= BLOCK_NEEDS_FREE | 1;// 8result->isa = _NSConcreteMallocBlock;// 9if (result->flags & BLOCK_HAS_COPY_DISPOSE) {(*aBlock->descriptor->copy)(result, aBlock); // do fixup }return result; }
5.4Block的copy、retain、release
和OC中的对象的copy、retain、release不同,Block对象:
- Block_copy与copy等效,Block_release与release等效;
- 对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
- NSGlobalBlock:retain、copy、release操作都无效,因为全局块绝不可能为系统所回收。这种块实际上相当于单例;
- NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,copy之后生成新的NSMallocBlock类型对象,然后返回: [[myBlock copy] autorelease]。
typedef NSString * (^retBlock)(void);- (void)test {NSMutableArray *arr = [NSMutableArray array];retBlock block = [[self stackBlock] copy];for (int i = 0; i < 5; i++){[arr addObject:block];} }- (retBlock)stackBlock {int ret = 10;NSString * (^myBlock)(void) = ^{return [NSString stringWithFormat:@"Hello,%d",ret];};return [[myBlock copy] autorelease]; }
- NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
- 尽量不要对Block使用retain操作,原因是并没有Block_retain()这样的函数,而且objc里面的retain消息发送给block对象后,其内部实现是什么都不做。
5.5Block对外部变量的存取管理
5.5.1基本数据类型
- 1、局部变量。局部变量,在Block中只读。Block定义时截获变量的值,在Block中作为常量使用,所以即使变量的值在Block外改变,也不影响它在Block中的值。
int base = 100;int (^myBlock)(int, int) = ^ (int a, int b) {return base + a + b; };base = 200;// base:200 myBlock:103 NSLog(@"base:%d myBlock:%d",base,myBlock(1,2));
- 2、全局变量或静态变量。在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时截获的值。
static int base = 100;int (^myBlock)(int, int) = ^ (int a, int b) {return base + a + b; };base = 200;// base:200 myBlock:203 NSLog(@"base:%d myBlock:%d",base,myBlock(1,2));
- 3、__block修饰的变量。被__block修饰的变量称作Block变量。 基本类型的Block变量等效于全局变量、或静态变量。
注意:Block被另一个Block使用时,另一个Block被copy到堆上时,被使用的Block也会被copy。但作为参数的Block是不会发生copy的。
具体看示例:
- (void)p_copyBlock {int base = 100;GofBlock blockOne = ^ (int a, int b) {return base + a + b;};// <__NSStackBlock__: 0x7fff52191248>NSLog(@"blockOne:%@", blockOne);[self p_copyBlockWithParam:blockOne]; }- (void)p_copyBlockWithParam:(GofBlock)blockParam {// <__NSStackBlock__: 0x7fff52191248>// 和上面一样,说明作为参数传递时,并不会发生copyNSLog(@"blockParam:%@", blockParam);void (^blockTwo)(GofBlock) = ^ (GofBlock gofBlock) {// 第一次:gofBlock:<__NSStackBlock__: 0x7fff52191248>// 第二次:gofBlock:<__NSStackBlock__: 0x7fff52191248>// 无论blockTwo在堆上还是栈上,作为参数的Block不会发生copy。NSLog(@"gofBlock:%@", gofBlock);// 第一次:blockParam:<__NSStackBlock__: 0x7fff52191248>// 第二次:blockParam:<__NSMallocBlock__: 0x618000241500>// 当blockTwo copy到堆上时,blockParam也被copy了一分到堆上。NSLog(@"blockParam:%@", blockParam);};blockTwo(blockParam); // blockTwo在栈上// blockTwo:<__NSStackBlock__: 0x7fff521911e8>NSLog(@"blockTwo:%@", blockTwo);blockTwo = [[blockTwo copy] autorelease];blockTwo(blockParam); // blk在堆上// blockTwo after copy:<__NSMallocBlock__: 0x61000005cef0>NSLog(@"blockTwo after copy:%@",blockTwo); }
5.5.2Objective-C对象
不同于基本类型,Block会引起OC对象的引用计数变化。这里对static、global、instance、block变量非arc下的情况进行分析。还是先看示例:
.h文件:
@interface TestBlock : NSObject {NSObject *_instanceObj; }
.m文件:
@implementation TestBlockNSObject *_globalObj = nil;- (id) init {self = [super init];if (self){_instanceObj = [[NSObject alloc] init];}return self; }- (void)mrcMemoryManage {static NSObject *_staticObj = nil;_globalObj = [[NSObject alloc] init];_staticObj = [[NSObject alloc] init];NSObject *localObj = [[NSObject alloc] init];__block NSObject *blockObj = [[NSObject alloc] init];typedef void (^MyBlock)(void) ;MyBlock aBlock = ^{NSLog(@"%@", _globalObj);NSLog(@"%@", _staticObj);NSLog(@"%@", _instanceObj);NSLog(@"%@", localObj);NSLog(@"%@", blockObj);};aBlock = [[aBlock copy] autorelease];aBlock();// 全局变量:1; 静态变量:1; 实例变量:1; 临时变量:2; block变量:1NSLog(@"全局变量:%lu; 静态变量:%lu; 实例变量:%lu; 临时变量:%lu; block变量:%lu", (unsigned long)[_globalObj retainCount], (unsigned long)[_staticObj retainCount], (unsigned long)[_instanceObj retainCount], (unsigned long)[localObj retainCount], (unsigned long)[blockObj retainCount]); }
根据上面的结果,我们来分析一下:
- 全局变量和静态变量在内存中的位置是确定的,所以Block copy时不会retain对象。
- 实例变量在Block copy时也没有直接retain实例变量对象本身,但会retain self。所以在Block中可以直接读写_instanceObj变量。
- 临时变量在Block copy时,系统自动retain对象,增加其引用计数。
- block变量在Block copy时也不会retain。
6.参考资料
- Objective-C Blocks Quiz
- 谈Objective-C block的实现
- A look inside blocks: Episode 3 (Block_copy)
- GCC,LLVM,Clang编译器对比
转载于:https://www.cnblogs.com/LeeGof/p/6755907.html
Objective-C中的Block相关推荐
- 如何在Objective C中声明Block?
本文翻译自http://fuckingblocksyntax.com/ 作为局部变量: returnType(^blockName)(parameterTypes) = ^returnType(par ...
- Spark 中 File,Block,Split,Task,Partition,RDD概念的理解
1,在 HDFS 中会以 Block 为单位进行存储很多的 File,也就是说每个 File 可能会被拆分成多个 Block 存储在 HDFS 上: 2,当 Spark 读取 HDFS 上的文件作为输 ...
- OC中的Block属性
Block是苹果极力推荐使用的一个知识点,它的简单实用使其在最近几年变的特别流行,尤其是在不同类之间传值的时候,比通过代理实现要方便得多.OC中的block可以借助C中的函数指针来理解,原理基本类似, ...
- 如何在 iOS 5 中使用 Block
How To Use Blocks in iOS 5 Tutorial – Part 1 How To Use Blocks in iOS 5 Tutorial – Part 2 本人将示范项目放在了 ...
- CSS中display:block和display:flax使用记录
CSS中display:block和display:flax使用记录 代码: (css) .navigation ul li a{position:relative;width: 100%;text- ...
- vue中是否有像小程序中的block标签?就是template
小程序中的block 1.<block/>并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性. 2.因为wx:if是一个控制属性,需要将它添加到一个标签上.如果要 ...
- Objective-C中的block块语法
#import <Foundation/Foundation.h>/*OC不同于Java C++ 它没有字符串对象 只有NS对象NS就是乔布斯第一个公司NeXt的类集合 被收购之后才有了C ...
- css规则中区块block,css常用属性总结:背景background下篇
前言:这段时间天天加班到10:30之后,简直是x了. 在上一篇文章中,分别解析了background各个属性的用法和注意细节.如果我们在项目上使用背景效果,如果使用下面的写法,你可能抓狂. body{ ...
- iOS中得block代码块的定义及使用
现在的无论是框架还是项目中,越来越多的使用block代码块. 个人觉得:第一可以使代码看起来更简单明了,第二可以取代以前的delegate使代码的逻辑看起来更清晰. 借一张图表达基本定义: (1)最基 ...
最新文章
- 微信开源「派大星」:4000元游戏电脑能带动7亿参数GPT!
- 我怎么在AD里面找到已经改名的Administrator账户?
- [Unity UGUI]点击和长按组件
- 利用IE8开发人员工具调试JavaScript脚本
- 软件测试自学钢琴考级,钢琴考级被音基难倒?不要慌,跟着这款钢琴陪练APP一起练...
- android studio 发布版本,Android Studio 4.1 Canary 版本发布
- 动态规划 —— 线性 DP —— 最大和问题
- 解决IDEA Maven项目无法下载依赖
- php中读取文件内容的几种方法。(file_get_contents:将文件内容读入一个字符串)...
- python model如何获取分类错误的数据_如何用python获取美股数据?
- Angular2开发拙见——组件规划篇
- 运动会比赛计分系统c语言课程设计,c语言课程设计运动会比赛计分系统(含任务书).doc...
- 广州帕克西为化妆品、眼镜、发型提供一站式AR虚拟试戴解决方案
- 计算机前沿科学与发展,《数据与计算发展前沿》正式创刊
- Doris export任务概率性cancelled第二种情况
- 计算机硬件实验报告怎么写,计算机硬件的认识与组装实验报告怎么写?
- 【软件工程师之路一】咸鱼翻身之自学软件开发
- RAM、SRAM、DRAM、SDRAM、DDRSDRAM等之间的区别
- 程序员眼中看到的网页是如何制作出来的?
- Android 12 adb push更新系统应用
热门文章
- MyBatisPlus条件构造器Condition的用法
- SSM中实现分页与JUnit单元测试
- Tkinter的Text组件
- java强引用、软引用、弱引用、虚引用-Java的引用类型总共有四种,你都知道吗
- centos 对已有卷扩容_CentOS LVM 新加硬盘,扩容逻辑卷步骤
- 计算机录入速度标准,怎么提高电脑录入速度?
- html语言词典,编程字典-HTML5语法
- java win10 32,Win10 同时安装64位和32位的JDK
- 神策数据与 IPIP.NET 强强联合,精准 IP 让用户行为分析更精确
- 接口自动化实现图片上传(selenium/RF)