概述

在iOS 4.0之后,block横空出世,它本身封装了一段代码并将这段代码当做变量,通过block()的方式进行回调。这不免让我们想到在C函数中,我们可以定义一个指向函数的指针并且调用。

#import <Foundation/Foundation.h>
void function(){
NSLog(@"function执行了");
}
int main(int argc, const char * argv[]) { void(*funcP)(void) = function;
// 函数指针调用函数
funcP(); return 0;
}

Block的本质就是函数指针。只要通过函数指针可以在任何时候执行函数

Block基本使用

Block的类型

block也是一种数据类型,Block的类型是什么呢。

返回值类型(^)(参数类型列表)就是Block的类型。开发中可以利用typedef定义同一种类型的Block。

// 定义一个没有返回值没有形参的Block类型MyBlock
typedef void(^MyBlock)(void);

Block的声明定义

声明一个block类型的变量

 返回值类型(^blockName)(参数类型列表)

定义一个block

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

    };

Block的定义不管有没有返回值,在定义时返回值类型可以省略。当一个block没有参数时Block定义^后面的括号也可以省略。

// 标准的声明与定义一个block
void (^block)() = ^void(){ }; // 定义省略返回值
void (^block)() = ^(){ }; // block没有返回值省略^后面的括号
void (^block)() = ^{ };

Block的使用场景

监听逆向传值

开发中我们通常使用代理来做监听并且逆向传值,其实使用Block也可以做到。给被监听着添加一个Block属性,在外界给被监听着赋值block。当被监听着内部发生了事件想通知给外界可以执行属性block。通过Block的参数将值传递出来。

其实本质就是Block就是一个函数指针,block定义其实就是一个定义函数的实现。当执行block,就通过block指针地址找到方法实现执行函数。这个Block定义在监听者内部,当被监听者内部执行了block就等于执行了监听者内部定义的函数。

Block的内存管理

MRC下Block的内存管理

在MRC中Block在内存中的位置是有多种情况,总体分为三种.

* NSGlobalBlock

* NSStackBlock

* NSMallocBlock

在MRC定义一个Block,对Block进行控制台输出发现Block默认是在全局区的。

void (^block)(void) = ^void(){
NSLog(@"----------------");
};
NSLog(@"%@", block);

当Block内部访问了局部变量,Block是在栈区。如果访问外部的变量静态变量或者全局变量,Block还是保存在全区区。

int a = 12;
void (^block)(void) = ^void(){
NSLog(@"----------------%d", a);
};

当一个栈区的Block通过copy后会生成新的Block,此时的Block存储在堆区。全局区的Block被copy后没有生成新的Block。

int a = 10;
void (^block)(void) = ^void(){
// 访问了外界的局部变量a block就保存在栈区
NSLog(@"----------------%d", a);
}; NSLog(@"%@", block);
NSLog(@"%@", [block copy]);

这就是为什么在MRC下,Block属性Propery会使用copy。如果使用的是retain那么block在栈区过了作用域就会释放,当调用者属性block时发生坏访问。

如果是MRC声明了一个copy修饰的属性,建议在对象的dealloc的方法对所拥有的Block进行release。

- (void)dealloc
{
[self.block release]; [super dealloc];
}

ARC下Block的内存管理

在ARC下默认定义Block同样存储在全局区,不同的是在ARC下,Block内部引用了局部变量是存储在堆区的。

static int a = 10;
void (^block)(void) = ^void(){
NSLog(@"----------------%d", a); // <__NSMallocBlock__: 0x604000241860>
};

在ARC下如果声明一个Block属性property修饰符建议使用strong。如果使用的copy跟strong的作用一样用一个强指针引用着Block。但是copy内部需要做一些逻辑处理,为了性能建议使用strong。

Block循环引用

在FMModelVCViewController控制器中声明一个block属性。在viewDidLoad中创建一个block为属性赋值。block内部输出self

@interface FMModelVCViewController ()
/** block属性 */
@property (nonatomic, strong) void(^block)(void);
@end @implementation FMModelVCViewController - (void)viewDidLoad {
[super viewDidLoad]; void(^block)(void) = ^{
NSLog(@"----------------%@", self);
}; self.block = block;
}

Block会对里面的所有外部强指针变量进行强引用。上面的代码就造成了循环引用,在内存中控制器不会销毁。

解决方案

在Block内部访问控制器__weak修饰指针的。

- (void)viewDidLoad {
[super viewDidLoad]; __weak typeof(self) weakSelf = self;
void(^block)(void) = ^{
NSLog(@"----------------%@", weakSelf);
}; self.block = block;
}

这样Block对控制器强产生的是弱引用。

Block中延时任务问题

当Block中有延时操作,延时操作block中想访问外界的对象,但是通常Block为了防止循环引用使用是_weak修饰的对象指针。当Block内部的延时Block访问的weak修饰的对象也是弱引用。有可能造成当执行延时的Block时,其内部引用的外部对象已经销毁。

#import "FMModelVCViewController.h"

@interface FMModelVCViewController ()
/** block属性 */
@property (nonatomic, strong) void(^block)(void);
@end @implementation FMModelVCViewController - (void)viewDidLoad {
[super viewDidLoad]; __weak typeof(self) weakSelf = self;
void(^block)(void) = ^{ // afterBlock由系统管理
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"----------------%@", weakSelf);
});
}; self.block = block; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 执行block
self.block();
[self dismissViewControllerAnimated:YES completion:nil];
}
@end

当点击屏幕时控制器执行了dismissViewControllerAnimated控制器方法控制器销毁,2s过后执行延时Block是输出null;

此时的内存如下:

解决方案

对Block内部代码调整如下



这样2s之后依然可以访问到控制器,当延时Block执行完毕。控制器才销毁。至于为什么通过内存图你就明白了。

iOS开发系列-Block的更多相关文章

  1. iOS开发系列-Block本质篇

    概述 在iOS开发中Block使用比较广泛,对于使用以及一些常规的技术点这里不再赘述,主要利用C++角度分析Block内部数据底层实现,解开开发中为什么这样编写代码解决问题. Block底层结构窥探 ...

  2. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  3. iOS开发系列--通知与消息机制

    概述 在多数移动应用中任何时候都只能有一个应用程序处于活跃状态,如果其他应用此刻发生了一些用户感兴趣的那么通过通知机制就可以告诉用户此时发生的事情.iOS中通知机制又叫消息机制,其包括两类:一类是本地 ...

  4. iOS开发系列--数据存取

    概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库.例如前面IOS开发系列-Objective-C之Foundation框架的文章中提到归档.plist文件存储, ...

  5. iOS开发系列--让你的应用“动”起来

    --iOS核心动画 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建 ...

  6. iOS开发系列--并行开发其实很容易

    --多线程开发 概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的, ...

  7. iOS开发系列--让你的应用“动”起来

    --iOS核心动画 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建 ...

  8. 【转】iOS开发系列--数据存取

    原文: http://www.cnblogs.com/kenshincui/p/4077833.html#SQLite 概览 在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储 ...

  9. IOS开发系列 --- 核心动画

    原始地址:http://www.cnblogs.com/kenshincui/p/3972100.html 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥i ...

随机推荐

  1. windows修改docker的默认存放位置

    docker默认存储到c盘,我需要移动到其他盘. 参考了网上很多资料,结果要么移动不了,要么重启docker就回到c盘了. 最后参考docker的官方论坛,找到了解决方案.https://forums ...

  2. 【学习总结】Python-3- 类型判断之 isinstance 和 type 的区别

    菜鸟教程-Python3-基本数据类型 关于类型查询: type() 函数:可以用来查询变量所指的对象类型 用 isinstance()函数:判断是否是某个类型 两者的区别: type()不会认为子类 ...

  3. 容器改变/窗口改变重新渲染echarts

    是否遇见使用侧边栏菜单收缩/展开,echarts容器大小变化,但是echarts不重新自适应容器.或者,window窗口改变但是echarts不随着改变,针对这两种echarts不自适应的情况,分享下 ...

  4. elasticsearch 嵌套对象使用Multi Match Query、query_string全文检索设置

    参考: https://www.elastic.co/guide/en/elasticsearch/reference/1.7/mapping-nested-type.html https://sta ...

  5. setmetamode - define the keyboard meta key handling

    总览 setmetamode [ meta|bit|metabit | esc|prefix|escprefix ] 描述 没有参数时, setmetamode 将打印当前 Meta 键模式; 有参数 ...

  6. 第6篇如何访问pod

        一.通过 Service 访问 Pod: 我们不应该期望 Kubernetes Pod 是健壮的,而是要假设 Pod 中的容器很可能因为各种原因发生故障而死掉.Deployment 等 con ...

  7. WebDriverAgent安装

    这次安装WebDriverAgent的过程可谓坎坷呀,最后还是大牛远程解决问题,自己的确差太远,记录一下过程吧 尽量升级Xcode到最新版,保持iPhone的版本大于9.3 终端进入目标文件夹WebD ...

  8. 每天一个linux命令:nl(12)

    nl nl命令读取 file 参数(缺省情况下标准输入),计算输入中的行号,将计算过的行号写入标准输出.在输出中,nl命令根据您在命令行中指定的标志来计算左边的行.输入文本必须写在逻辑页中.每个逻辑页 ...

  9. tcp和udp得区别

    TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的.可靠的.基于字节流的传输层通信协议 UDP 是User Datagram Protocol,即 用户 ...

  10. VS2014:"64位调试操作花费的时间比预期要长",无法运行调试解决办法

    解决步骤: 右键管理员运行命令提示符,输入IISRESERT,重启IIS即可