美文网首页iOS Developer
Working with Blocks

Working with Blocks

作者: 好_快 | 来源:发表于2016-10-20 14:55 被阅读28次

简介

Blocks是C语言层级语法和运行时特性。 它们类似于标准C函数,但是除了可执行代码之外,它们还可以保存堆栈变量。 因此,块可以保存数据,在代码执行时使用。
1、Block可以作为函数数调用、作为函数参数、作为方法参数。
2、因为独立完整可以在多线程中使用;
3、因为拥有回调时需要执行的代码和执行代码时需要的数据,常常被用来实现回调Callback。
由于Objective-C和C++都是从C派生,因此三种语言均可以使用(Objective-C使用更多)。iOS从4.0版本开始支持。在其他语言环境中有时被称为“闭包”。

特点

Block是一个匿名的内联代码集合,有以下特点:
1、像函数一样用参数列表
2、有可推断或者声明的返回值类型
3、可以从定义它作用域捕获数据
4、可选的可以修改捕获到的数据
5、同一作用域中的其它block共享捕获的数据
6、作用域的堆栈被销毁后,仍然可以继续共享定义其范围定义的数据
由于compiler 和 runtime 保证block和其相关的数据的生命周期,因此可以copy一份传递到其他地方使用。

声明和创建

声明

首先说明下函数指针的声明格式

返回值类型 ( * 指针变量名) ([形参列表]);

Block variables保存着block的引用。可以像声明函数指针一样声明block变量,但是要把*换成^,例如以下均为有效的声明

void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);

Blocks支持可变的参数列表,当没有参数时必须写void。
通过为编译器提供block使用的数据,传递参数,返回值,block设计完全类型安全。可以把block引用强制转换成任意类型的指针,反之亦然。但是不能使用操作符*来访问值,因为block的值在编译期无法计算。
理解:假如每次方法调用当做一次消息发送(一般底层会有很多次),把block中的所以消息发送按照顺序放到一种能够先进先出的数据结构--比方说实现栈特性的结构体;那么block变量^{ ... }作用一样都是指向结构体的首地址;传递首地址跟其他对象指针、函数指针用法很像。

类型定义简化用法

如果经常重复使用同一个类型的block,你可以定义自己的block类型,例如

//返回值类型:float 两个参数类型:float 变量名称:MyBlockType
typedef float (^MyBlockType)(float, float);
 
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;

创建

^表示block的开始,接一个返回值类型(可选,默认不写)再接一个(参数列表),后边接一个{代码块};例如

int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};

解读如下图所示



如果没有明确声明返回值类型,则根据block代码内容推断具体类型,例如

int multiplier = 7;

//未指明返回值类型
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};

//指明返回值类型
int (^myBlock)(int) =  ^ int (int num) {
   return multiplier;
};

Blocks和变量

首先可以像函数一样引用三种标准类型的变量:
1、全局变量,包括静态局部变量
2、全局函数
3、作用域内的局部变量和参数
其次Blocks还支持另外两种类型:
4、__block修饰的变量
5、const导入的
所以Blocks内部处理变量的五种情况:
1、全局变量(包括作用域内的静态局部变量)可以直接访问
2、Blocks的参数可以直接访问
3、作用域内非静态局部变量,作为const变量引用(只读),强行修改编译器报错
4、被__block修饰的作用域内非静态局部变量,可以直接访问
5、Blocks内部声明的局部变量,可以直接访问
示例代码如下

int global_var = 1;

- (void)viewDidLoad {
[super viewDidLoad];
    
   
static int static_var = 2;
    
__block int loacal_var = 3;

__block int const_var = 3;

void (^myBlock)(int) = ^(int number) {

   global_var = global_var * 10;
   static_var = static_var * 10;
   loacal_var = loacal_var * 10;
   number     = number * 10;
   NSLog(@"局部变量:%d ,说明局部变量可以读取",const_var);
   NSLog(@"参数变量原:4 修改后%d,说明可以正常访问",number);

};
myBlock(4);

NSLog(@"全局变量原:1 修改后:%d,说明可以正常访问 ",global_var);
NSLog(@"静态变量原:2 修改后:%d,说明可以正常访问 ",static_var);
NSLog(@"__block修饰的局部变量原:3 修改后:%d,说明可以正常访问 ",loacal_var);
}

//输出结果
局部变量:3 ,说明局部变量可以读取
参数变量原:4 修改后40,说明可以正常访问
全局变量原:1 修改后:10,说明可以正常访问 
静态变量原:2 修改后:20,说明可以正常访问 
__block修饰的局部变量原:3 修改后:30,说明可以正常访问 

Blocks和对象

对象通过Properties来引用Blocks。语法和定义Blocks语法类似;例如

@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end

self.blockProperty = ^{
   ...
};
self.blockProperty();

注意:此处应该用copy,因为block需要被copy来保存引用的外界的变量,无需关心引用计数问题,编译器自动进行管理。

还可以通过自定义类型简化

typedef void (^XYZSimpleBlock)(void);
 
@interface XYZObject : NSObject
@property (copy) XYZSimpleBlock blockProperty;
@end

避免循环引用

当变量变成实例变量的时候,需要避免循环引用的问题。由于Blocks会持有使用的变量,比如在Viewcontroller中,block作为一个property被self持有,block中使用self调用方法,这样便造成循环引用的问题使得二者在不使用时均得不到释放。解决方法是在block中使用self的弱引用,用__weak修饰。

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@end

- (void)configureBlock {
    XYZBlockKeeper * __weak weakSelf = self;
    self.block = ^{
        [weakSelf doSomething];   // capture the weak reference
                                  // to avoid the reference cycle
    }
}

用法

作为变量

如果声明Block作为一个变量,可以像函数一样调用。例如

int (^oneFrom)(int) = ^(int anInt) {
    return anInt - 1;
};
 
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
 
float (^distanceTraveled)(float, float, float) =
                         ^(float startingSpeed, float acceleration, float time) {
 
    float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);
    return distance;
};
 
float howFar = distanceTraveled(0.0, 9.8, 1.0);
// howFar = 4.9

作为函数参数

可以像传递其它参数一样传递block。大多数的情况不需要声明,只需要作为参数以内联的方式实现。例如

char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };

//根据首字符进行一次排序 
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
    char *left = *(char **)l;
    char *right = *(char **)r;
    //此函数表示:以参数3的长度计算前2个参数的差值
    return strncmp(left, right, 1);
});

// Block implementation ends at "}"
 
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }

作为方法参数

Cocoa Touch 提供了很多方法使用Blocks。可以传递block作为参数使用。例如

//是否包含指定字符串
__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";
 
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
    if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
        *stop = YES;
        found = YES;
    }
}];
 
// At this point, found == YES

作用

很大程度上起到了简化的作用。

简化回调

Blocks可以用处替换传统回调的原因如下:
1、方法调用和回调代码集中在一起;还常作为framework methods的参数。
2、可以直接访问局部变量。

简化枚举

对一个数组进行枚举,常用的方法有3种
1、for循环
2、for的泛型遍历for (type *object in collection)
3、使用带有block的简化方法。例如- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block;

NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];

//for循环
for (int i = 0; i < array.count; i ++)
{
   
   NSString *item = array[i];
   NSLog(@"index:%d value:%@",i,item);
}
 
 //泛型遍历   
for (NSString *item in array)
{
   NSLog(@"value:%@",item);
}
 
 //枚举方法   
[array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
   NSLog(@"index:%ld value:%@",idx,obj);
   
}];

简化并发任务

每个block都是独立的工作单元,包含可执行代码和保存使用的数据,这使得它能够在多线程开发中被异步调用。
系统提供了一系列的多线程编程技术,其中包括两种任务调度机制:Operation queues 和 Grand Central Dispatch(GCD)。不像Thread关注怎么样管理运行,把需要执行的任务打包到blocks中,然添加到队列中,然后交给系统根据当时的资源自动执行。

简化Operation Queues

//定义任务
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    ...
}];

// 主线程执行
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperation:operation];
 
// 后台执行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];

更多知识请阅读Operation Queues

简化Grand Central Dispatch

//获取并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//定义任务添加到队列并执行
dispatch_async(queue, ^{
    NSLog(@"Block for asynchronous execution");
});

更多知识请阅读 Dispatch Queues

参考文献:Blocks Programming TopicsWorking with Blocks

相关文章

网友评论

    本文标题:Working with Blocks

    本文链接:https://www.haomeiwen.com/subject/lgbfuttx.html