iOS Blocks 入门

前言

官方文档 Blocks Programming Topics

1. Block 是什么❓

Block 在其他语言里又称闭包,也可以叫匿名函数,代码块。

Block 是苹果在iOS4开始引入的对C语言的扩展,是用来实现匿名函数的特性。

其实就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。

2. Block 有什么作用❓

Block 的声明和实现一般不在一个类里,在一个类里也就没什么意思了。
Block 是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,特殊的 Block 还可以保存一段代码,在需要的时候调用,目前 Block 已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调。

3. Block有什么优势❓

个人觉得 Block 优势如下:
第一可以使代码看起来更简单明了,
第二可以取代以前的 delegate 使代码的逻辑看起来更清晰。

4. Block 代码块和普通函数都是一段代码,两者有什么区别❓

  • Block代码: 是一个函数对象,是在程序运行过程中产生的;
  • 普通函数: 是一段固定代码,产生于编译期;

Block 语法

Block 表达式语法:

^ 返回值类型 (参数列表) {表达式}

注: 1.^ 该操作符被称作”脱字符”
2.Block 的声明与赋值只是保存了一段代码段,必须调用才能执行内部代码

例如:

 ^ int (int num) {
        return num + 1;
    };

其中,可省略部分有:

  • 可以省略返回值类型,因为编译器可以从存储代码块的变量中确定返回值的类型。例:
 ^ (int num) {
        return num + 1;
    };
  • 参数列表为空,则可省略,例:
 ^ {
        NSLog(@"No Parameter");
    };

即最简模式语法为:

^ {表达式}

巧记 Block 格式

很多人觉得 Block 格式定义很难记,其实我们可以通过与 C 语言中的函数方法对比加强记忆:


int functionName (int a , int b)   // C 的方法声明

int (^MyBlockName) (int a , int b)  // iOS 的 block 声明   

Block 声明

声明 Block 类型变量语法:

返回值类型 (^变量名)(参数列表) = Block 表达式

例如,如下声明了一个变量名为 myBlock 的 Block :

int multiplier = 7;

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

printf("%d", myBlock(3));

// prints "21"

该示例的解析如下图:

《iOS Blocks 入门》 block 示例解析.png

Block 的定义

/*定义属性,block 属性可以用 strong 修饰,也可以用 copy 修饰 */  
 @property (nonatomic, strong) void(^myBlock)();                   //无参无返回值  
 @property (nonatomic, strong) void(^myBlock1)(NSString *);        //带参数无返回值  
 @property (nonatomic, strong) NSString *(^myBlock2)(NSString *);  //带参数与返回值 

 //定义变量  
 void(^myBlock)() = nil;                   //无参无返回值  
 void(^myBlock1)(NSString *) = nil;        //带参数无返回值  
 NSString *(^myBlock2)(NSString *) = nil;  //带参数与返回值  
  
block 被当做方法的参数  
格式:( block 类型)参数名称  
 - (void)test:(void(^)())testBlock                    //无参无返回值  
 - (void)test1:(void(^)(NSString *))testBlock         //带参数无返回值  
 - (void)test2:(NSString *(^)(NSString *))testBlock   //带参数与返回值  
  
  
使用 typedef 定义 block  
 typedef void(^myBlock)();                            //以后就可以使用 myBlock 定义无参无返回值的 block  
 typedef void(^myBlock1)(NSString *);                 //使用 myBlock1 定义参数类型为 NSString 的 block  
 typedef NSString *(^myBlock2)(NSString *);           //使用 myBlock2 定义参数类型为 NSString,返回值也为 NSString 的 block  
 //定义属性  
 @property (nonatomic, strong) myBlock testBlock;  
 //定义变量  
 myBlock testBlock = nil;  
 //当做参数  
 - (void)test:(myBlock)testBlock;

Block 的赋值

格式:block = ^返回值类型 (参数列表) {函数主体}
注4: 通常情况下,可以省略返回值类型,因为编译器可以从存储代码块的变量中确定返回值的类型。

//没有参数没有返回值
myBlock testBlock = ^void(){
     NSLog(@"test");
 };

//没有返回值,void 可以省略
myBlock testBlock1 = ^(){
     NSLog(@"test1");
 };

//没有参数,小括号也可以省略
myBlock testBlock2 = ^{
     NSLog(@"test2");
 };

//有参数没有返回值
myBlock1 testBlock = ^void(NSString *str) {
      NSLog(str);
}

//省略void
myBlock1 testBlock = ^(NSString *str) {
      NSLog(str);
}

//有参数有返回值
myBlock2 testBlock = ^NSString *(NSString *str) {
     NSLog(str)
     return @"hi";
}

//有返回值时也可以省略返回值类型
 myBlock2 testBlock2 = ^(NSString *str) {
     NSLog(str)
     return @"hi";
}

声明 Block 变量的同时进行赋值

int(^myBlock)(int) = ^(int num){  
    return num * 7;  
};  
  
// 如果没有参数列表,在赋值时参数列表可以省略  
void(^aVoidBlock)() = ^{  
    NSLog(@"I am a aVoidBlock");  
};

注:如果没有参数,= 左边用括号表示,= 右边参数可以省略

使用 typedef 定义 Block 类型

在实际使用 Block 的过程中,我们可能需要重复地声明多个相同返回值相同参数列表的 Block 变量,如果总是重复地编写一长串代码来声明变量会非常繁琐,所以我们可以使用 typedef 来定义 Block 类型

// 定义一种无返回值无参数列表的Block类型
typedef void(^MyBlock)();

// 我们可以像OC中声明变量一样使用Block类型SayHello来声明变量
MyBlock myBlock = ^(){
    NSLog(@"MyBlock");
};

// 调用后控制台输出"MyBlock"
myBlock();

截获自动变量值

Block 表达式可截获所使用的自动变量的值。
截获:保存自动变量的瞬间值。
因为是“瞬间值”,所以声明 Block 之后,即便在 Block 外修改自动变量的值,也不会对 Block 内截获的自动变量值产生影响。

    int i = 10;

    void (^blk)(void) = ^{
        NSLog(@"In block, i = %d", i);
    };

    i = 20;//Block 外修改变量i,也不影响 Block 内的自动变量
    blk();//i 修改为20后才执行,打印: In block, i = 10
    NSLog(@"i = %d", i);//打印:i = 20

局部自动变量:

在 Block 中只读。Block 定义时 copy 变量的值,在 Block 中作为常量使用,所以即使变量的值在 Block 外改变,也不影响他在 Block 中的值。

 int i = 10;

    void (^blk)(void) = ^{
        // i++; 编译错误,只读 
        NSLog(@"In block, i = %d", i);
    };
static 变量、全局变量:

如果把上个例子的 base 改成全局的、或 static。Block 就可以对他进行读写了。因为全局变量或静态变量在内存中的地址是固定的,Block 在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时 copy 的常量。

static 修饰变量,效果与_ _block一样

__block 说明符号

自动变量截获的值为 Block 声明时刻的瞬间值,保存后就不能改写该值,如需对自动变量进行重新赋值,需要在变量声明前附加 __block 说明符,这时该变量称为__block 变量。

基本类型的 Block 变量等效于全局变量、或静态变量。

例如:

    __block int i = 10;//i为__block变量,可在block中重新赋值
    void (^blk)(void) = ^{
        NSLog(@"In block, i = %d", i);
    };
    i = 20;
    blk();//打印: In block, i = 20
    NSLog(@"i = %d", i);//打印:i = 20

自动变量值为一个对象情况

当自动变量为一个类的对象,且没有使用 __block 修饰时,虽然不可以在Block 内对该变量进行重新赋值,但可以修改该对象的属性。
如果该对象是个 Mutable 的对象,例如 NSMutableArray ,则还可以在 Block内对 NSMutableArray 进行元素的增删:

    NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@"1", @"2",nil ];
    NSLog(@"Array Count:%ld", array.count);//打印Array Count:2
    void (^blk)(void) = ^{
        [array removeObjectAtIndex:0];//Ok
        //array = [NSNSMutableArray new];//没有__block修饰,编译失败!
    };
    blk();
    NSLog(@"Array Count:%ld", array.count);//打印Array Count:1

Block 在内存中的位置

根据Block在内存中的位置分为三种类型 NSGlobalBlockNSStackBlock, NSMallocBlock

NSGlobalBlock: 类似函数,位于text段;
NSStackBlock: 位于栈内存,函数返回后Block将无效;
NSMallocBlock: 位于堆内存。

示例1:  
  
BlkSum blk1 = ^ long (int a, int b) {  
  return a + b;  
};  
NSLog(@"blk1 = %@", blk1);// 打印结果:blk1 = <__NSGlobalBlock__: 0x47d0>  
  
  
示例2:  
  
int base = 100;  
BlkSum blk2 = ^ long (int a, int b) {  
  return base + a + b;  
};  
NSLog(@"blk2 = %@", blk2); // 打印结果:blk2 = <__NSStackBlock__: 0xbfffddf8>  
  
 
示例3:  
  
BlkSum blk3 = [[blk2 copy] autorelease];  
NSLog(@"blk3 = %@", blk3); // 打印结果:blk3 = <__NSMallocBlock__: 0x902fda0>  

blk1blk2 的区别在于:

blk1没有使用Block以外的任何外部变量,Block不需要建立局部变量值的快照,这使blk1与一般函数没有任何区别。
blk2blk1 唯一不同是的使用了局部变量 base
注意1:在定义(注意是“定义”,不是“运行”)blk2时,局部变量 base 当前值被 copy 到栈上,作为常量供 Block 使用。执行下面代码,结果是 203,而不是204。

int base = 100;  
  base += 100;  
  BlkSum sum = ^ long (int a, int b) {  
    return base + a + b;  
  };  
  base++;  
  printf("%ld",sum(1,2));  

Block的copy、retain、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到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock 类型对象。

NSMallocBlock:支持 retain、release,虽然 retainCount 始终是1,但内存管理器中仍然会增加、减少计数。copy 之后不会生成新的对象,只是增加了一次引用,类似 retain;

尽量不要对 Block 使用 retain 操作。

几个应用实例:

1、代码用在字符串数组排序

NSArray *stringArray = [NSArray arrayWithObjects:@"abc 1", @"abc 21", @"abc 12",@"abc 13",@"abc 05",nil];    
NSComparator sortBlock = ^(id string1, id string2)    
{    
    return [string1 compare:string2];    
};    
NSArray *sortArray = [stringArray sortedArrayUsingComparator:sortBlock];    
NSLog(@"sortArray:%@", sortArray);  

// 运行结果:sortArray:( 
   "abc 05",
   "abc 1",
   "abc 12",
   "abc 13",
   "abc 21"
)

2、代码块的递归调用

代码块想要递归调用,代码块变量必须是全局变量或者是静态变量,这样在程序启动的时候代码块变量就初始化了,可以递归调用

static void (^ const blocks)(int) = ^(int i)  
{  
    if (i > 0) {  
        NSLog(@"num:%d", i);  
        blocks(i - 1);  
    }  
};  
blocks(3); 

参考:

iOS之Block代码块的定义及使用

    原文作者:村雨灬龑
    原文地址: https://www.jianshu.com/p/0108a430b719
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞