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"

该示例的解析如下图:

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代码块的定义及使用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容

  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,806评论 10 69
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,135评论 30 470
  • 1.项目经验 2.基础问题 3.指南认识 4.解决思路 ios开发三大块: 1.Oc基础 2.CocoaTouch...
    阳光的大男孩儿阅读 4,977评论 0 13
  • block.png iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实...
    全栈农民工阅读 588评论 0 1
  • iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,B...
    smile刺客阅读 2,338评论 2 26