一. iOS代码块Block

1.1 概述

代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,Block是一种特殊的数据类型,其可以正常定义变量、作为参数、作为返回值,特殊地,Block还可以保存一段代码,在需要的时候调用,目前Block已经广泛应用于iOS开发中,常用于GCD、动画、排序及各类回调

注: Block的声明与赋值只是保存了一段代码段,必须调用才能执行内部代码

1.2 Block变量的声明、赋值与调用

#######1.2.1 Block变量的声明

Block变量的声明格式为: 返回值类型(^Block名字)(参数列表);

// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y); // 形参变量名称可以省略,只留有变量类型即可
void(^aBlock)(NSString *, NSString *);

#######1.2.2 Block变量的赋值

Block变量的赋值格式为: Block变量 = ^(参数列表){函数体};

aBlock = ^(NSString *x, NSString *y){
NSLog(@"%@ love %@", x, y);
};
注: Block变量的赋值格式可以是: Block变量 = ^返回值类型(参数列表){函数体};,不过通常情况下都将返回值类型省略,
因为编译器可以从存储代码块的变量中确定返回值的类型

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

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

#######1.2.4 Block变量的调用

// 调用后控制台输出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei"); // 调用后控制台输出"result = 63"
NSLog(@"result = %d", myBlock(9)); // 调用后控制台输出"I am a aVoidBlock"
aVoidBlock();

二. 使用typedef定义Block类型

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

// 定义一种无返回值无参数列表的Block类型
typedef void(^SayHello)(); // 我们可以像OC中声明变量一样使用Block类型SayHello来声明变量
SayHello hello = ^(){
NSLog(@"hello");
}; // 调用后控制台输出"hello"
hello();

三. Block作为函数参数

3.1 Block作为C函数参数
// 1.定义一个形参为Block的C函数
void useBlockForC(int(^aBlock)(int, int))
{
NSLog(@"result = %d", aBlock(300,200));
} // 2.声明并赋值定义一个Block变量
int(^addBlock)(int, int) = ^(int x, int y){
return x+y;
}; // 3.以Block作为函数参数,把Block像对象一样传递
useBlockForC(addBlock); // 将第2点和第3点合并一起,以内联定义的Block作为函数参数
useBlockForC(^(int x, int y) {
return x+y;
});
3.2 Block作为OC函数参数
// 1.定义一个形参为Block的OC函数
- (void)useBlockForOC:(int(^)(int, int))aBlock
{
NSLog(@"result = %d", aBlock(300,200));
} // 2.声明并赋值定义一个Block变量
int(^addBlock)(int, int) = ^(int x, int y){
return x+y;
}; // 3.以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock]; // 将第2点和第3点合并一起,以内联定义的Block作为函数参数
[self useBlockForOC:^(int x, int y){
return x+y;
}];
3.3 使用typedef简化Block
// 1.使用typedef定义Block类型
typedef int(^MyBlock)(int, int); // 2.定义一个形参为Block的OC函数
- (void)useBlockForOC:(MyBlock)aBlock
{
NSLog(@"result = %d", aBlock(300,200));
} // 3.声明并赋值定义一个Block变量
MyBlock addBlock = ^(int x, int y){
return x+y;
}; // 4.以Block作为函数参数,把Block像对象一样传递
[self useBlockForOC:addBlock]; // 将第3点和第4点合并一起,以内联定义的Block作为函数参数
[self useBlockForOC:^(int x, int y){
return x+y;
}];

四 Block内访问局部变量

4.1 在Block中可以访问局部变量
// 声明局部变量global
int global = 100; void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
// 调用后控制台输出"global = 100"
myBlock();
4.2 在声明Block之后、调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之前的旧值
// 声明局部变量global
int global = 100; void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
global = 101;
// 调用后控制台输出"global = 100"
myBlock();
4.3 在Block中不可以直接修改局部变量
// 声明局部变量global
int global = 100; void(^myBlock)() = ^{
global ++; // 这句报错
NSLog(@"global = %d", global);
};
// 调用后控制台输出"global = 100"
myBlock();

五. Block内访问__block修饰的局部变量

5.1 在局部变量前使用下划线下划线block修饰,在声明Block之后、调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之后的新值
// 声明局部变量global
__block int global = 100; void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
global = 101;
// 调用后控制台输出"global = 101"
myBlock();
5.2 在局部变量前使用下划线下划线block修饰,在Block中可以直接修改局部变量
// 声明局部变量global
__block int global = 100; void(^myBlock)() = ^{
global ++; // 这句正确
NSLog(@"global = %d", global);
};
// 调用后控制台输出"global = 101"
myBlock();

六. Block内访问全局变量

6.1 在Block中可以访问全局变量
// 声明全局变量global
int global = 100; void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
// 调用后控制台输出"global = 100"
myBlock();
6.2 在声明Block之后、调用Block之前对全局变量进行修改,在调用Block时全局变量值是修改之后的新值
// 声明全局变量global
int global = 100; void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
global = 101;
// 调用后控制台输出"global = 101"
myBlock();
6.3 在Block中可以直接修改全局变量
// 声明全局变量global
int global = 100; void(^myBlock)() = ^{
global ++;
NSLog(@"global = %d", global);
};
// 调用后控制台输出"global = 101"
myBlock();

七. Block内访问静态变量

7.1 在Block中可以访问静态变量
// 声明静态变量global
static int global = 100; void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
// 调用后控制台输出"global = 100"
myBlock();
7.2 在声明Block之后、调用Block之前对静态变量进行修改,在调用Block时静态变量值是修改之后的新值
// 声明静态变量global
static int global = 100; void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
global = 101;
// 调用后控制台输出"global = 101"
myBlock();
7.3 在Block中可以直接修改静态变量
// 声明静态变量global
static int global = 100; void(^myBlock)() = ^{
global ++;
NSLog(@"global = %d", global);
};
// 调用后控制台输出"global = 101"
myBlock();

8. Block在MRC及ARC下的内存管理

8.1 Block在MRC下的内存管理
默认情况下,Block的内存存储在栈中,不需要开发人员对其进行内存管理
// 放Block变量出了作用域,Block的内存会被自动释放
void(^myBlock)() = ^{
NSLog(@"------");
};
myBlock();

在Block的内存存储在栈中时,如果在Block中引用了外面的对象,不会对所引用的对象进行任何操作

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock(); [p release]; // Person对象在这里可以正常被释放

如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,这时需要开发人员对其进行release操作来管理内存

void(^myBlock)() = ^{
NSLog(@"------");
};
myBlock(); Block_copy(myBlock); // do something ... Block_release(myBlock);

如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,即使在Block自身调用了release操作之后,Block也不会对所引用的对象进行一次release操作,这时会造成内存泄漏

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock(); Block_copy(myBlock); // do something ... Block_release(myBlock); [p release]; // Person对象在这里无法正常被释放,因为其在Block中被进行了一次retain操作

如果对Block进行一次copy操作,那么Block的内存会被移动到堆中,在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行一次retain操作,为了不对所引用的对象进行一次retain操作,可以在对象的前面使用下划线下划线block来修饰

__block Person *p = [[Person alloc] init];

void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock(); Block_copy(myBlock); // do something ... Block_release(myBlock); [p release]; // Person对象在这里可以正常被释放

如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用

  • 情况一
@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end

@implementation Person

- (void)dealloc
{
NSLog(@"Person dealloc"); Block_release(_myBlock);
[super dealloc];
} @end Person *p = [[Person alloc] init]; p.myBlock = ^{
NSLog(@"------%@", p);
};
p.myBlock(); [p release]; // 因为myBlock作为Person的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Person对象进行一次retain操作,导致循环引用无法释放
  • 情况二
@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end

@implementation Person

- (void)resetBlock
{
self.myBlock = ^{
NSLog(@"------%@", self);
};
} - (void)dealloc
{
NSLog(@"Person dealloc"); Block_release(_myBlock); [super dealloc];
} @end Person *p = [[Person alloc] init];
[p resetBlock];
[p release]; // Person对象在这里无法正常释放,虽然表面看起来一个alloc对应一个release符合内存管理规则,但是实际在resetBlock方法实现中,Block内部对self进行了一次retain操作,导致循环引用无法释放

如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是在对象的前面使用下划线下划线block来修饰,以避免Block对对象进行retain操作

情况一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end

@implementation Person

- (void)dealloc
{
NSLog(@"Person dealloc"); Block_release(_myBlock);
[super dealloc];
} @end __block Person *p = [[Person alloc] init]; p.myBlock = ^{
NSLog(@"------%@", p);
};
p.myBlock(); [p release]; // Person对象在这里可以正常被释放

情况二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end

@implementation Person

- (void)resetBlock
{
// 这里为了通用一点,可以使用__block typeof(self) p = self;
__block Person *p = self;
self.myBlock = ^{
NSLog(@"------%@", p);
};
} - (void)dealloc
{
NSLog(@"Person dealloc"); Block_release(_myBlock); [super dealloc];
} @end Person *p = [[Person alloc] init];
[p resetBlock];
[p release]; // Person对象在这里可以正常被释放

九. Block在ARC下的内存管理

9.1 在ARC默认情况下,Block的内存存储在堆中,ARC会自动进行内存管理,程序员只需要避免循环引用即可
// 放Block变量出了作用域,Block的内存会被自动释放
void(^myBlock)() = ^{
NSLog(@"------");
};
myBlock();

在Block的内存存储在堆中时,如果在Block中引用了外面的对象,会对所引用的对象进行强引用,但是在Block被释放时会自动去掉对该对象的强引用,所以不会造成内存泄漏

Person *p = [[Person alloc] init];

void(^myBlock)() = ^{
NSLog(@"------%@", p);
};
myBlock(); // Person对象在这里可以正常被释放
9.2
如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用

情况一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end

@implementation Person

- (void)dealloc
{
NSLog(@"Person dealloc");
} @end Person *p = [[Person alloc] init]; p.myBlock = ^{
NSLog(@"------%@", p);
};
p.myBlock(); // 因为myBlock作为Person的属性,采用copy修饰符修饰(这样才能保证Block在堆里面,以免Block在栈中被系统释放),所以Block会对Person对象进行一次强引用,导致循环引用无法释放 情况二 @interface Person : NSObject @property (nonatomic, copy) void(^myBlock)(); - (void)resetBlock; @end @implementation Person - (void)resetBlock
{
self.myBlock = ^{
NSLog(@"------%@", self);
};
} - (void)dealloc
{
NSLog(@"Person dealloc");
} @end Person *p = [[Person alloc] init];
[p resetBlock]; // Person对象在这里无法正常释放,在resetBlock方法实现中,Block内部对self进行了一次强引用,导致循环引用

如果对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用,解决循环引用的办法是使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作,这样避免了Block对对象进行强引用

情况一

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

@end

@implementation Person

- (void)dealloc
{
NSLog(@"Person dealloc");
} @end Person *p = [[Person alloc] init];
__weak typeof(p) weakP = p; p.myBlock = ^{
NSLog(@"------%@", weakP);
};
p.myBlock(); // Person对象在这里可以正常被释放

情况二

@interface Person : NSObject

@property (nonatomic, copy) void(^myBlock)();

- (void)resetBlock;

@end

@implementation Person

- (void)resetBlock
{
// 这里为了通用一点,可以使用__weak typeof(self) weakP = self;
__weak Person *weakP = self;
self.myBlock = ^{
NSLog(@"------%@", weakP);
};
} - (void)dealloc
{
NSLog(@"Person dealloc");
} @end Person *p = [[Person alloc] init];
[p resetBlock]; // Person对象在这里可以正常被释放

十. Block在ARC下的内存管理的官方案例

在MRC中,我们从当前控制器采用模态视图方式present进入MyViewController控制器,在Block中会对myViewController进行一次retain操作,造成循环引用

MyViewController *myController = [[MyViewController alloc] init];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
[myController release];
}];

在MRC中解决循环引用的办法即在变量前使用下划线下划线block修饰,禁止Block对所引用的对象进行retain操作

__block MyViewController *myController = [[MyViewController alloc] init];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{
[myController release];
}];

但是上述方法在ARC下行不通,因为下划线下划线block在ARC中并不能禁止Block对所引用的对象进行强引用,解决办法可以是在Block中将myController置空(为了可以修改myController,还是需要使用下划线下划线block对变量进行修饰)

__block MyViewController *myController = [[MyViewController alloc] init];
// ...
myController.completionHandler = ^(NSInteger result) {
[myController dismissViewControllerAnimated:YES completion:nil];
myController = nil;
};
[self presentViewController:myController animated:YES completion:^{}];

上述方法确实可以解决循环引用,但是在ARC中还有更优雅的解决办法,新创建一个弱指针来指向该对象,并将该弱指针放在Block中使用,这样Block便不会造成循环引用

MyViewController *myController = [[MyViewController alloc] init];
// ...
__weak MyViewController *weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
[weakMyController dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:myController animated:YES completion:^{}];

虽然解决了循环引用,但是也容易涉及到另一个问题,因为Block是通过弱引用指向了myController对象,那么有可能在调用Block之前myController对象便已经被释放了,所以我们需要在Block内部再定义一个强指针来指向myController对象

MyViewController *myController = [[MyViewController alloc] init];
// ...
__weak MyViewController *weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController)
{
[strongMyController dismissViewControllerAnimated:YES completion:nil];
}
else
{
// Probably nothing...
}
};
[self presentViewController:myController animated:YES completion:^{}];

这里需要补充一下,在Block内部定义的变量,会在作用域结束时自动释放,Block对其并没有强引用关系,且在ARC中只需要避免循环引用即可,如果只是Block单方面地对外部变量进行强引用,并不会造成内存泄漏

** 注: 关于下划线下划线block关键字在MRC和ARC下的不同 **

__block在MRC下有两个作用
1. 允许在Block中访问和修改局部变量
2. 禁止Block对所引用的对象进行隐式retain操作 __block在ARC下只有一个作用
1. 允许在Block中访问和修改局部变量

ios Block详解的更多相关文章

  1. iOS开发——Block详解

    iOS开发--Block详解 1. Block是什么 代码块 匿名函数 闭包--能够读取其他函数内部变量的函数 函数变量 实现基于指针和函数指针 实现回调的机制 Block是一个非常有特色的语法,它可 ...

  2. 【转】IOS AutoLayout详解(三)用代码实现(附Demo下载)

    转载自:blog.csdn.net/hello_hwc IOS SDK详解 前言: 在开发的过程中,有时候创建View没办法通过Storyboard来进行,又需要AutoLayout,这时候用代码创建 ...

  3. IOS SDK详解

    来源:http://blog.csdn.net/column/details/huangwenchen-ios-sdk.html?page=1#42803301 博客专栏>移动开发专栏>I ...

  4. iOS路由详解

    本文如题,路由详解,注定是一篇详细解释iOS路由原理及使用的文章,由于此时正在外地出差,无法详细一一写出,只能不定时的补充. 一.什么是iOS路由 路由一词来源于路由器,可以实现层级之间消息转发的功能 ...

  5. Block详解一(底层分析)

    本篇博客不再讲述Block的基本定义使用,最近而是看了很多的block博客讲述的太乱太杂,所以抽出时间整理下block的相关底层知识,在讲述之前,提出几个问题,如果都可以回答出来以及知道原理,大神绕过 ...

  6. Block详解二(底层分析)

    Block专辑: Block讲解一 MRC-block与ARC-block Block详解一(底层分析) 今天讲述Block的最后一篇,后两篇仅仅是加深1,2篇的理解,废话少说,开始讲解! __blo ...

  7. iOS模式详解—「runtime面试、工作」看我就 🐒 了 ^_^.

    Write in the first[写在最前] 对于从事 iOS 开发人员来说,当提到 ** runtime时,我想都可以说出来 「runtime 运行时」和基本使用的方法.相信很多开发者跟我当初一 ...

  8. iOS 模式详解—「runtime面试、工作」看我就 🐒 了 ^_^.

    引导 Copyright © PBwaterln Unauthorized shall not be *copy reprinted* . 对于从事 iOS 开发人员来说,所有的人都会答出「runti ...

  9. IOS 手势详解

    在IOS中手势可以让用户有很好的体验,因此我们有必要去了解一下手势. (在设置手势是有很多值得注意的地方) *是需要设置为Yes的点击无法响应* *要把手势添加到所需点击的View,否则无法响应* 手 ...

随机推荐

  1. mysql-查询存在主表但是不存在副标的数据

    A.B两表,找出ID字段中,存在A表,但是不存在B表的数据.A表总共13w数据,去重后大约3W条数据,B表有2W条数据,且B表的ID字段有索引. 方法一 使用 not in ,容易理解,效率低  ~执 ...

  2. 云存储的未来:Scale Up还是Scale Out?

      云存储的几十年发展历程,其计算架构模型,也从Scale Up走向Scale Out.但是展望未来数字世界的海量需求,目前流行的模型还能够持续满足吗?本文通过对云存储历史的回顾,及对Scale Up ...

  3. 一个简单的通讯服务框架(大家发表意见一起研究)JAVA版本

    最近研究下java语言,根据一般使用的情况,写了个连接通讯服务的框架: 框架结构 C-Manager-S; 把所有通讯内容抽取成三个方法接口:GetData,SetData,带返还的Get; 所有数据 ...

  4. centos7在vmware上安装后不能连接网络的问题。注意虚拟机向导时选择的操作系统...........

    在虚拟机上安装时发现网络不可用 在网上查找解决方案一整理天,各种命令代码偿试   无效. 在贴上发现说 Centos7镜像是64位 ,虚拟机操作系统一定要选择centos64位也是必须的. 最后选择重 ...

  5. Python的随机数模块

    random模块中几个随机函数用法. 引入random模块: import random 1.random.random() 此函数用于生成一个0到1的随机浮点数:0 <= n < 1.0 ...

  6. 格式化hdfs后,hadoop集群启动hdfs,namenode启动成功,datanode未启动

    集群格式化hdfs后,在主节点运行启动hdfs后,发现namenode启动了,而datanode没有启动,在其他节点上jps后没有datanode进程!原因: 当我们使用hdfs namenode - ...

  7. python基础之从认识python到python的使用

    python的历史: python的创始人是吉多·范罗苏姆(Guido van Rossum),人称“龟叔”,1989年圣诞节期间,Guido开始写Python语言的编译器.他希望这个叫做Python ...

  8. Error importing tensorflow. Unless you are using bazel version `CXXABI_1.3.8' not found

    I have re-installed Anaconda2. And I got the following error when 'python -c 'import tensorflow'' &g ...

  9. 使用wireshark以及filddler配合抓去手机端的TCP以及HTTP请求

    在测试手机客户端时,有时候需要查看网络请求状况.使用在IDE中查看log的方式,能够解决问题,但是会比较复杂.wireshark不能够做代理,而fiddler主要是抓HTTP请求,没有wireshar ...

  10. EOS 权限

    [EOS权限] 1.查看权限 cleos get account $(Account_Name) 2.使用 cleos set account permission 命令来修改权限 可以看到,owne ...