iOS block探究(一): 基础详解

你要知道的block都在这里

转载请注明出处 //www.greatytc.com/p/184b04c1f454

本文大纲

  • block基础语法
  • block基础使用
  • block常见问题
  • block进阶: 深入代码理解

block基础语法

block作为C语言的扩展,正在OC中发挥着举足轻重的作用,我们经常使用block块作为回调函数,这样做可以大大简化编程方式,多线程的核心也是block,因此,学会使用block并深入理解block有助于我们写出更好的代码。

block基础知识

先来看一下定义block的基础语法

^ returnType (parameter1, parameter2, parameter3, ...) 
{
    //block code
}
  • block的标志就是^,所有的block必须以^开头
  • returnType表示返回值类型,如果没有返回值一般使用void表示

再来看一下定义block变量的基础语法

returnType (^blockName) (parameter1, parameter2, ...);
  • 必须包含blockName并且以^开头,是block的标志
  • 参数列表可以和声明函数一样,只写出形参类型不需写出形参名称

接下来看一个具体的栗子:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^printBlock)(void) = ^ void(void) {
            NSLog(@"Hello World");
        };
        //调用block,与C语言调用函数一致
        printBlock();
    }
    return 0;
}

上述代码展示了一个无参数、无返回值的block,定义block变量的时候不能省略返回值类型、block名称以及形参列表,如果没有参数则用void占位或者不写,这样就能够定义一个block变量
定义block的时候如果返回值为void可以省略,如果没有形参可以使用void占位或者整个形参列表都省略不写,因此上述代码可以简化为如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^printBlock)() = ^ {
            NSLog(@"Hello World");
        };
        //调用block,与C语言调用函数一致
        printBlock();
    }
    return 0;
}

再来看看有参数列表有返回值的情况

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //定义一个有参数无返回值的block
        void (^printBlock)(NSString *) = ^(NSString *content) {
            NSLog(@"Block Say: %@", content);
        };
        printBlock(@"Jiaming Chen is a good guy.");
        
        //定义一个有多个参数且有返回值的block
        NSInteger (^addBlock)(NSInteger, NSInteger) = ^ NSInteger (NSInteger a1, NSInteger a2) {
            return a1 + a2;
        };
        NSInteger a = addBlock(12, 13);
        NSLog(@"%ld", a);
    }
    return 0;
}

上述代码定义了两个block,一个block有一个参数但是无返回值,因此可以省略返回值类型不写,一个block有多个参数和返回值,不能省略。

block捕获变量

直接看代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSUInteger age = 22;
        void (^printBlock)() = ^ {
            NSLog(@"My Age is %ld", age);
            //age = 33;
        };
        //输出 Age is 22
        NSLog(@"Age is %ld", age);
        age = 100;
        //输出 Age is 100
        NSLog(@"Age is %ld", age);
        //输出 My Age is 22
        printBlock();        
    }
    return 0;
}

从输出结果可以看出,执行block之前进行的变量值修改并没有影响到block块内的代码,这是由于在定义block块的时候编译器已经在编译期将外部变量值赋给了block内部变量(称为“值捕获”),在这时候进行了一次值拷贝,而不是在运行时赋值,因此外部变量的修改不会影响到内部block的输出。
如果捕获是一个指针类型的变量则外部的修改会影响到内部,就和函数传递形参是一样的道理,这个时候block内部或持有这个对象,并增加引用计数,在block结束释放后,也会释放持有的对象,减少引用计数,这里需要注意循环引用的问题,在后文中会讲解。
上述block块内注释了一段给age重新赋值的代码,因为在block内部不允许修改捕获的变量。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //定义一个NSMutableString类型的变量
        NSMutableString *content = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
        void (^printBlock)() = ^ {
            NSLog(@"%@", content);
        };
        //输出 Jiaming Chen
        NSLog(@"%@", content);
        [content appendString:@" is a good guy"];
        //输出 Jiaming Chen is a good guy
        printBlock();
    }
    return 0;
}

__block的使用

如果希望block捕获的变量在外部修改后也可以影响block内部,或是想在block内部修改捕获的变量,可以使用__block关键字定义变量。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
         //使用__block关键字定义age
        __block NSUInteger age = 22;
        void (^printBlock)() = ^ {
            NSLog(@"My Age is %ld", age);
            age = 200;
        };
        //输出 Age is 22
        NSLog(@"Age is %ld", age);
        age = 100;
        //输出 Age is 100
        NSLog(@"Age is %ld", age);
        //输出 My Age is 100
        printBlock();
        //输出 Age is 200
        NSLog(@"Age is %ld", age);        
    }
    return 0;
}

上述代码使用__block定义变量age,这样定义以后编译器会在block定义的时候捕获变量的引用而不是拷贝一个值(具体实现细节在iOS block探究(二): 深入理解中有详细介绍)这样,外部变量的修改就会影响到block内部。

block作为参数传递

在实际应用中很少有上述那样的用法,更多的是定义一个block块然后作为参数传递。

//使用typedef定义一个无返回值、有一个NSInteger类型的形参的block类型,该block名字为 CJMNumberOperationBlock
typedef void (^CJMNumberOperationBlock)(NSInteger);

//numberOperator函数,参数为一个numberArray数组和一个CJMNumberOperationBlock块类型
void numberOperator(NSArray *numberArray, CJMNumberOperationBlock operationBlock) {
    NSUInteger count = [numberArray count];
    for (NSUInteger i = 0; i < count; i++) {
        operationBlock([numberArray[i] integerValue]);
    }
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
         //定义一个数组
        NSArray *numberArray = @[@1, @2, @3];
        //直接调用numberOperator函数,实现一个匿名的block传入成为“内联块”(inline block),在swift中成为“尾随闭包”(Trailing closure)
        //输出 pow(number, 2) = 1
        //   pow(number, 2) = 4
        //    pow(number, 2) = 9
        numberOperator(numberArray, ^(NSInteger number) {
            NSLog(@"pow(number, 2) = %ld", number * number);
        });
        
        //定义一个CJMNumberOperationBlock
        CJMNumberOperationBlock operationBlock = ^(NSInteger number) {
            NSLog(@"add(number, number) = %ld", number + number);
        };
        //将上述定义的CJMNumberOperationBlock参数传入
        //输出 add(number, number) = 2
        //   add(number, number) = 3
        //    add(number, number) = 6                
        numberOperator(numberArray, operationBlock);
    }
    return 0;
}

使用typedef定义块变量类型的语法为

typdef returnType (^blockTypeName)(parameter1, parameter2,..);

定义了块变量类型就能够重复定义块变量,上述代码也是一种常见的使用block的方式。也可以作为回调函数,在代码逻辑处理完成之后执行block块,这也是常用做法。

在实际代码中,经常将一些处理封装在block中,或使用delegate方式进行处理,这样有利于代码解耦,逻辑更清晰,具体的栗子本文不再赘述,可以多看开源代码来学习。

block常见问题

使用block最常见的问题就是循环引用问题,循环引用也可能发生在delegateNSTimer中,具体可以自行查阅。
前文讲过如果block内部访问了外部变量会进行值捕获,block同样是一个对象,也有引用计数,如过一个对象持有了一个blockblock内部也捕获了这个对象,那么就会产生循环引用。
举个栗子:

typedef void (^CJMInfoOperationBlock)();

@interface Person : NSObject

@property (nonatomic, copy) NSString* cjmName;
@property (nonatomic, assign) NSUInteger cjmAge;
@property (nonatomic, copy) CJMInfoOperationBlock infoOperationBlock;

- (void)setFixBlock;

@end

@implementation Person

@synthesize cjmName = _cjmName;
@synthesize cjmAge = _cjmAge;
@synthesize infoOperationBlock = _infoOperationBlock;

- (void)setFixBlock {
    self.infoOperationBlock = ^ {
        NSLog(@"My name is %@ and my age is %ld", self.cjmName, self.cjmAge);
    };
}

@end

首先声明,上述代码应该不会在实际中应用,太懒了不想用Xcode写代码,就没有用UIKit,只用了Foundation框架,setFixBlock函数就可以想象成在一个UIViewController类中手动传递block变量。
上述代码首先自定义了一个block变量类型,然后Person类持有了一个block变量,在函数setFixBlock中使用了self变量,这样就会捕获selfself也是一个对象,因此block也会持有一个self如果调用了setFixBlock函数就会造成循环引用。编译器也会检查出来并给出警告。

上述代码的修改方法如下:

- (void)setFixBlock {
    __weak typeof(self) weakSelf = self;
    self.infoOperationBlock = ^ {
        __strong typeof(self) strongSelf = weakSelf;
        NSLog(@"My name is %@ and my age is %ld", strongSelf.cjmName, strongSelf.cjmAge);
    };
}

首先定义一个__weak修饰的weakSelf变量(在iOS @property探究(一): 基础详解一文中有详细介绍weak修饰符与此处含义一致),使用__weak修饰表示弱引用, 定义weakSelf不会增加self的引用计数,因此在block内部使用这个weakSelf就不会产生循环引用,因此在所有可能产生循环引用的地方都这么做,就能有效避免循环引用的产生。
那么为什么在block内部又使用__strong修饰符去定义了一个strongSelf变量呢?其实这个strongSelf定义或者不定义都无所谓,不会产生问题,因为在ARC环境下,block会被自动拷贝到堆,不存在栈堆了,这样block被传递到其他地方的时候也不会释放self对象。

block进阶: 深入代码理解

  • 三种block类型
  • 深入代码理解block

由于篇幅有限,如果对进阶内容有兴趣可以查看另一篇文章iOS block探究(二): 深入代码

备注

由于作者水平有限,难免出现纰漏,如有问题还请不吝赐教。

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

推荐阅读更多精彩内容