1.Block定义

可以用一句话来表示Block:带有自动变量(局部变量)匿名函数

在iOS中使用“^”来声明一个Block。Block的内容是包含在“{}”中的,并且和C语言一样用“;”来表示语句的结束,标准语法如下所示:

//完整语法
^ 返回值类型 参数列表 表达式//省略返回值
^ 参数列表 表达式//省略参数列表
^ 返回值类型 表达式//省略返回值和参数列表
^ 表达式

从上面可以看到,Block和函数很相似,具体体现在这些方面:

  1. 可以保存代码(对应表达式);
  2. 有返回值(对应返回值类型);
  3. 有形参(对应参数列表);
  4. 调用方式一样。

我们通常使用如下形式将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构造函数的两个参数:

  1. __gofBlock_block_func_0是由Block语法转换的C语言函数指针;
  2. __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相关推荐

  1. 如何在Objective C中声明Block?

    本文翻译自http://fuckingblocksyntax.com/ 作为局部变量: returnType(^blockName)(parameterTypes) = ^returnType(par ...

  2. Spark 中 File,Block,Split,Task,Partition,RDD概念的理解

    1,在 HDFS 中会以 Block 为单位进行存储很多的 File,也就是说每个 File 可能会被拆分成多个 Block 存储在 HDFS 上: 2,当 Spark 读取 HDFS 上的文件作为输 ...

  3. OC中的Block属性

    Block是苹果极力推荐使用的一个知识点,它的简单实用使其在最近几年变的特别流行,尤其是在不同类之间传值的时候,比通过代理实现要方便得多.OC中的block可以借助C中的函数指针来理解,原理基本类似, ...

  4. 如何在 iOS 5 中使用 Block

    How To Use Blocks in iOS 5 Tutorial – Part 1 How To Use Blocks in iOS 5 Tutorial – Part 2 本人将示范项目放在了 ...

  5. CSS中display:block和display:flax使用记录

    CSS中display:block和display:flax使用记录 代码: (css) .navigation ul li a{position:relative;width: 100%;text- ...

  6. vue中是否有像小程序中的block标签?就是template

    小程序中的block 1.<block/>并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性. 2.因为wx:if是一个控制属性,需要将它添加到一个标签上.如果要 ...

  7. Objective-C中的block块语法

    #import <Foundation/Foundation.h>/*OC不同于Java C++ 它没有字符串对象 只有NS对象NS就是乔布斯第一个公司NeXt的类集合 被收购之后才有了C ...

  8. css规则中区块block,css常用属性总结:背景background下篇

    前言:这段时间天天加班到10:30之后,简直是x了. 在上一篇文章中,分别解析了background各个属性的用法和注意细节.如果我们在项目上使用背景效果,如果使用下面的写法,你可能抓狂. body{ ...

  9. iOS中得block代码块的定义及使用

    现在的无论是框架还是项目中,越来越多的使用block代码块. 个人觉得:第一可以使代码看起来更简单明了,第二可以取代以前的delegate使代码的逻辑看起来更清晰. 借一张图表达基本定义: (1)最基 ...

最新文章

  1. 微信开源「派大星」:4000元游戏电脑能带动7亿参数GPT!
  2. 我怎么在AD里面找到已经改名的Administrator账户?
  3. [Unity UGUI]点击和长按组件
  4. 利用IE8开发人员工具调试JavaScript脚本
  5. 软件测试自学钢琴考级,钢琴考级被音基难倒?不要慌,跟着这款钢琴陪练APP一起练...
  6. android studio 发布版本,Android Studio 4.1 Canary 版本发布
  7. 动态规划 —— 线性 DP —— 最大和问题
  8. 解决IDEA Maven项目无法下载依赖
  9. php中读取文件内容的几种方法。(file_get_contents:将文件内容读入一个字符串)...
  10. python model如何获取分类错误的数据_如何用python获取美股数据?
  11. Angular2开发拙见——组件规划篇
  12. 运动会比赛计分系统c语言课程设计,c语言课程设计运动会比赛计分系统(含任务书).doc...
  13. 广州帕克西为化妆品、眼镜、发型提供一站式AR虚拟试戴解决方案
  14. 计算机前沿科学与发展,《数据与计算发展前沿》正式创刊
  15. Doris export任务概率性cancelled第二种情况
  16. 计算机硬件实验报告怎么写,计算机硬件的认识与组装实验报告怎么写?
  17. 【软件工程师之路一】咸鱼翻身之自学软件开发
  18. RAM、SRAM、DRAM、SDRAM、DDRSDRAM等之间的区别
  19. 程序员眼中看到的网页是如何制作出来的?
  20. Android 12 adb push更新系统应用

热门文章

  1. MyBatisPlus条件构造器Condition的用法
  2. SSM中实现分页与JUnit单元测试
  3. Tkinter的Text组件
  4. java强引用、软引用、弱引用、虚引用-Java的引用类型总共有四种,你都知道吗
  5. centos 对已有卷扩容_CentOS LVM 新加硬盘,扩容逻辑卷步骤
  6. 计算机录入速度标准,怎么提高电脑录入速度?
  7. html语言词典,编程字典-HTML5语法
  8. java win10 32,Win10 同时安装64位和32位的JDK
  9. 神策数据与 IPIP.NET 强强联合,精准 IP 让用户行为分析更精确
  10. 接口自动化实现图片上传(selenium/RF)