白话block

本文内容

  • 什么是block?
  • block的用途
  • block的用法
  • block在使用中遇到的问题
  • 如何使用xcode检测循环引用引发的内存问题?

什么是block?

什么是闭包(closure)?
闭包是可以包含自由(未绑定到特定对象)变量的代码块。
什么是函数式编程?
函数可以随时创建、作为参数传递、作为返回值返回。
在函数式编程中,把函数当参数来回传递,而这个,说成术语,我们把他叫做高阶函数。
Objective-C在没有block之前,没有类似的机制,有了block,Objective-C也就具备了函数式编程的能力,block是对象,有自己的ISA指针,可以随时创建,作为参数传递,作为返回值返回
block是带有局部变量的匿名函数(即没有名称的函数),就是OC中的闭包(closure),又名匿名函数,块函数,块

block的用途

block都是一些简短代码片段的封装,适用作工作单元,通常用来做并发任务、遍历、以及回调。

block的用法

  1. block 语法,用插入符号"^"定义一个block字面量,无参,无返回值的block 如下

^{
NSLog(@"This is A Block");
}();

控制台输出:

2015-11-03 12:04:26.266 Whisper[63849:545630] This is a block

  1. block的类型(可以用isa 指针指向的地址查看)
  • NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
  • NSConcreteStackBlock 保存在栈中的 block,当函数返回时会被销毁。
  • NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。
  • 在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block
  1. block Pointer,主要用来简化block的写法,block Pointer定义如下:

回传值 (^名字)(参数列表);

block Pointer 具体使用

int (^myBlock) (int a); // 声明一个名字为myBlock的block 指针,该指针指向的Block有一个int输入和一个int 输出
myBlock = ^(int a){ return a*a;}; //将Block的实体指定给myBlock指针
int result = myBlock(4); //调用blcok实体

  1. 使用typedef给复杂变量block定义类型别名,定义规则:
    在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头,示例如下:

typedef int (^myBlock) (int a); //使用定义的新类型myBlock来声明对象,等价于int (^myBlock) (int a);

一般借助block的类型别名,为特定的类添加block属性变量,做传值或者事件使用。声明block属性变量的时候,property中需设置成copy

  1. 用block来存取变量
    一个Block的内部是可以引用自身作用域外的变量的,包括static变量,extern变量或自由变量,对于自由变量,在Block中是只读的。在引入block的同时,还引入了一种特殊的__block关键字,变量存储修饰符,将变量的存储范围扩展为该函数以及该函数内定义的block的行为主体内(苹果官方文档)。
    存取静态变量

static int outA = 8;

int (^myPtr)(int) = ^(int a){return outA + a;};  
outA = 5;  
int result = myPtr(3);        //result的值是8,因为outA是static类型的变量

存取自由变量

{
__block int num = 5;
int (^myPtr)(int) = ^(int a){return num++;};
int (^myPtr2)(int) = ^(int a){return num++;};
int result = myPtr(0); //result的值为5,num的值为6
result = myPtr2(0); //result的值为6,num的值为7
NSLog(@"result=%d", result);
}

  1. block作为类的属性

typedef int (^MyBlock) (int a);
@property(nonatomic,copy) int (^block)(int a); //使用c的方式, 不能使用OC函数形参的写法.
@property (nonatomic,copy) MyBlock blockName;

  1. block作为函数参数,苹果官方文档建议一个方法最好只有一个block参数,并且block参数一般作为最后一个参数

- (void)beginTaskWithCallbackBlock:(void (^)(void))callbackBlock {
...
callbackBlock();
}
- (void)beginFetchWithCallbackBlock:(MyBlock)callbackBlock {
...
callbackBlock(3);
}

  1. 以block作为函数返回值

-(int (^)(int))blockBack{
return ^(int cout){ return cout;};
}

block在使用中遇到的问题

  1. 修改局部变量需要在局部变量前面加__block修饰符,将变量的存储范围扩展为该函数以及该函数内定义的block的行为主体内。
  2. 在属性定义一个block的时候需要使用copy,因为块是在栈上分配的,一旦离开作用域, 就会释放, 因此如果你要把块用在别的地方, 必须要复制一份
  3. 在ARC下, 以下几种情况, Block会自动被从栈复制到堆
  • 被执行copy方法
  • 作为方法返回值
  • 将Block赋值给附有__strong修饰符的id类型的类或者Blcok类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或者GCD的API中传递的时候.
  1. 循环引用的问题
  • A和B两个对象,A持有B,B同时也持有A,A只有B释放之后才有可能释放,同样B只有A释放后才可能释放,当双方都在等待对方释放的时候, retain cycle就形成了,结果是,两个对象都永远不会被释放,最终内存泄露。
  • 循环引用(retain cycle)的解决
  • 尽量保持子对象引用父对象的时候使用弱引用,也就是assign,比如

@property (nonatomic,assign) NSObject *parent;

  • 及时地将造成retain cycle中的一个变量设置为nil,将环break掉
  • block中的retain cycle

@interface XYZBlockKeeper : NSObject
@property (copy) void (^block)(void);
@implementation XYZBlockKeeper
- (void)configureBlock {
self.block = ^{
[self doSomething]; // capturing a strong reference to self
// creates a reference cycle
};
}
...
@end

  • block中retain cycle 的解决

    • 方法一 将引用的一方变成weak,从而避免循环引用

- (void)configureBlock {
XYZBlockKeeper * __weak weakSelf = self;
self.block = ^{
[weakSelf doSomething]; // capture the weak reference
// to avoid the reference cycle
}
}
或者
- (void)configureBlock {
__weak typeof(self) weakSelf = self;
self.block = ^{
//如果想防止 weakSelf 被释放,可以再次强引用
typeof(weakSelf) strongSelf = weakSelf;

  [weakSelf doSomething];   // capture the weak reference cycle

}
}

  • 方法二.使用完某对象没有必要在保留该对象的时候,在block里面将对象释放即可打破保留环

- (void)downloadData {
NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
_networkFetcher = [[EOCNetworkFetcher alloc] initWithURL:url];
[_networkFetcher startWithCompletionHandler:^(NSData *data) {
_fetchedData = data;
_networkFetcher = nil; //加上此行,此处是为了打破循环引用
}];
}

  • 方法三. 在调用完block之后,将该block设置为nil(block为某类的属性的时候,这么使用)

- (void)p_requestCompleted {
if(_completionHandler) {
_completionHandler(_downloadData);
}
self.completionHandler = nil;//加上此行,此处是为了打破循环引用
}

如何使用xcode检测循环引用(引用自《iOS开发进阶》)?

  1. Xcode 的Instruments工具集可以很方便地检测循环引用,但是检测不出block产生的循环引用,示例如下

- (void)viewDidLoad {
[super viewDidLoad];
//firstArray 持有secondArray, secondArray 持有 firstArray,形成retain cycle
NSMutableArray *firstArray = [NSMutableArray array];
NSMutableArray *secondArray = [NSMutableArray array];
[firstArray addObject:secondArray];
[secondArray addObject:firstArray];
}

  1. 在Xcode 的菜单栏,选择“Product”--->“Profile”,在调出的界面中选择"Leaks"--->"choose",调出Instruments界面。
    Instruments会用红色的X表示一次内存泄露的产生,Instruments中可以通过切换到“Leaks”,单击“Cycles&Roots”,就可以看到以图形方式显示出来的循环引用,这样,我们就可以很方便的看到产生循环引用的对象了。
  • 具体使用步骤如下:


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

推荐阅读更多精彩内容

  • Block使用场景,可以在两个界面的传值,也可以对代码封装作为参数的传递等。用过GCD就知道Block的精妙之处。...
    Coder_JMicheal阅读 722评论 2 1
  • iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,B...
    smile刺客阅读 2,339评论 2 26
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,762评论 0 23
  • Block简介(copy一段) Block作为C语言的扩展,并不是高新技术,和其他语言的闭包或lambda表达式是...
    qui丶MyLove阅读 418评论 0 0
  • 不愿提及的你: 嗨!老同学,如果有一天你看到这封信会不会感到惊讶呢?其实,当我脑海里冒出这个想法的时候我被...
    会跳舞的华尔兹阅读 356评论 0 3