前言:对于ios初学者,block通常用于逆向传值,遍历等,会使用,但是可能心虚,会感觉block很神秘,那么下面就一起来揭开它的面纱吧。

ps: 下面重点讲叙了闭包的概念,常用的语法,以及访问变量,循环引用问题,至于底层的运行,堆栈block的区别,还有其他用法这里就不介绍了,目前也处于迷糊中,等到真正理解了再来补充 - -。

一. 概念 

1. 什么是闭包?

闭包就是能够读取其他函数内部变量的函数,可以理解成“定义在一个函数内部的函数“。

在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

闭包在很多语言中都有应用,C,JAVA,OC等

2. OC中的Block

在OC中,Block是在iOS4开始引入,是对C语言的扩展,被用来实现匿名函数的特性

Block是一种特殊的数据类型,可以正常定义变量、作为参数、作为返回值

特殊地,Block还可以声明赋值去保存一段代码,在需要调用的地方去调用

目前Block已经广泛应用于各类回调传值、排序遍历、GCD、动画等

二. 基本语法

1. block做自由变量 - 声明、赋值以及调用 (个人感觉理解语法就好)

 // 声明一个名字为 TestBlockTest 的无返回值含有参数的变量
void (^TestBlockTest)(NSString *);
// 只声明变量,需要赋值
TestBlockTest = ^(NSString *parameter){
NSLog(@"测试");
};
// 调用
TestBlockTest(@""); // 声明TestTwoBlock变量同时赋值
int (^TestTwoBlock)(int) = ^(int num){
return num*;
};
// 已经声明了blcok并赋值了 ,可以直接调用
int num = TestTwoBlock();
NSLog(@"%d",num);

注意:

^ 这个叫做 脱字符,其中,返回值类型,参数列表可以省略简写,这个用多了就知道了,开始推荐写全,基础要扎实

第一个输出值 测试,没有用到参数parameter,强迫症大神请见谅哈!第二个输出值 64

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

2. 使用typedef定义Block类型

可做属性,可做参数、返回值类型等 (这个用法务必掌握)

示例代码背景:A页面点击按钮 跳转 B页面,B页面返回A页面时候,传值@“测试”,用于修改A页面按钮名字

2.1 .h中定义

 #import <UIKit/UIKit.h>

 // typedef 定义无返回值,有一个参数,名字为TestBlock的block类型
typedef void(^TestBlock)(NSString *); @interface ViewController : UIViewController // 做属性
@property (nonatomic, copy) TestBlock testBolck; // 做方法参数
- (void)returnText:(TestBlock)block; @end

2.2 .m中用法,实现(下面整合了做属性,做参数的代码)

 // B页面

 // 做属性
TestBlock blockVar = ^(NSString *parameterTwo){
NSLog(@"hello world %@",parameterTwo);
};
/*
* 我们可能需要重复地声明多个相同返回值相同参数列表的Block变量
* 如果总是重复地编写一长串代码来声明变量会非常繁琐
* 所以我们可以使用typedef来定义Block类型
* 然后像OC中声明变量一样使用Block类型 TestBlock 来声明变量
**/
blockVar(@"UZI");
blockVar(@"");
// 实现定义的方法
- (void)returnText:(TestBlock)block{
self.testBolck = block;
}
// 这里选择返回传参数,用法很多,看个人喜好
- (void)viewWillDisappear:(BOOL)animated{
self.testBolck(@"测试");
}
// A页面 在页面跳转部分
// 做属性回调
__weak __typeof(self) weakSelf = self;
vc.testBolck = ^(NSString *parameter) {
[weakSelf setBtnTitle:parameter];
}; // 做参数回调
[vc returnText:^(NSString *parameter) {
[self setBtnTitle:parameter];
}];
// 做参数另一种写法
__weak __typeof(self) weakSelf = self;
[vc returnText:^(NSString *parameter) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
// 这里用strong保证self不被释放,详见下文 循环引用weak,strong修饰问题
[strongSelf setBtnTitle:parameter];
}];

注意:

上面选择在B页面返回A页面时候传递参数,传递给A,A页面分别用了block做属性,做参数是怎么完成逆向传值的,方式很多,凭自己喜好

三.   block访问变量问题

这里不一一代码举例,个人感觉看总结,主要有以下四点,记住就好

1.   Block拥有捕获外部变量的功能,在Block中访问一个外部的局部变量,Block会持用它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的的状态,可以理解为瞬间性捕获。

2.  在block中,可以访问局部变量(自由变量),但是不能修改局部变量,因为:block捕获的是自动变量的const值,名字一样,不能修改

3.  可以访问静态变量,并修改变量的值,静态变量属于类的,不是某一个变量,因此block不用调用self指针,所以block可以修改值

4.  使用__block修饰符的局部变量,可以修改局部变量的值。包括可变类型的参数,也可以修改,这个可以用clang命令将OC转为C++代码来查看一下Block底层实现

四:  循环引用 __weak __strong 修饰问题

很多初学者对这块都是模糊的,只知道加上 __weak __typeof(self) weakSelf = self 这句,弱引用,可以防止循环引用。

那什么是循环引用?

简单理解为 相互持有强引用,造成block内所持有的对象无法释放,引起内存泄漏

当然,造成循环引用不唯一,好比对象内部有一个Block属性,而在Block内部又访问了该对象,那么也会造成循环引用

下面分三点来谈论:

1. 如果相互持有强引用,即对象内部有一个Block属性,而在Block内部又访问了该对象,那么会造成循环引用。

解决办法是使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作,这样引用计数不加1,避免了Block对对象进行强引用。

通常是这样: __weak __typeof(self) weakSelf = self

注意: 以上是在ARC情况下,如果MRC中,可以在 会引起相互持有的对象 前面,使用 __block 修饰,原理是可以禁止block对对象进行retain操作,引用计数不会加1,从而解决循环引用问题。

这种循环引用示例(部分代码,提供思路):

 // 定义一个block
typedef void(^HYBFeedbackBlock)(id model);
// 声明一个对象
@property (nonatomic, strong) HYBAView *aView;
// block做方法参数
- (instancetype)initWithBlock:(HYBFeedbackBlock)block;
// 构造方法
- (instancetype)initWithBlock:(HYBFeedbackBlock)block {
if (self = [super init]) {
self.block = block;
return self;
}
// 调用
self.aView = [[HYBAView alloc] initWithBlock:^(id model) {
// 假设要更新model
self.currentModel = model;
}];

上面代码很容易看出所形成的环:

vc->aView->block->vc(self)

vc->aView->block->vc.currentModel

2. 对于上面的那种情况,为消除循环引用,而用弱引用

虽说使用__weak,但是此处会有一个隐患,你不知道 block内的 self 什么时候会被释放,

为了保证在block内不会被释放,我们添加__strong,更多的时候需要配合strongSelf使用

    // 上面讲到做方法参数时候的一种写法
__weak __typeof(self) weakSelf = self;
[vc returnText:^(NSString *parameter) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
[strongSelf setBtnTitle:parameter];
}];

可能有人问,用strong,那什么时候才释放呢?

用修饰符strong时,当外部把变量/对象释放掉,但block如果没有执行结束,那么系统就会等待block执行完成后再释放,

对该变量/对象在block中的使用起到了保护作用,当block执行结束后会自动释放掉(ARC)。

不过若无强烈需求,不建议在Block里加strong,容易占用内存,造成内存消耗

3. 是不是所有的block都要用弱引用呢?

  不是,如果没有相互直接引用,可以放心大胆的不用__weak

  并不是block就一定会造成循环引用,如果不是相互持有,可以不用__weak 去弱引用

最经典的示例: Masonry代码布局

  [self.headView mas_makeConstraints:^(MASConstraintMaker *make) {

         make.centerY.equalTo(self.otherView.mas_centerY);
}];

  * block里用到了self,block会保持一个对self的引用,但是self并没有直接或者间接持有block,所以不会造成循环引用

  形成的持有链:

self ->self.headView ··· MASConstraintMaker构造block->self

五. 感兴趣的可以继续了解下

*  block 与内存管理,堆栈block等

*  block 底层实现

*  block 其他用法

总结: 认清一件事物需要长期不断的观察与思考,长路漫漫修远兮,时刻保持学习心,与君共勉

参考文献:

一篇文章看懂iOS代码块Block

block 中使用__weak 和__strong修饰符的问题

iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)

iOS闭包循环引用精讲

深入解构iOS的block闭包实现原理 (想看block实现原理推荐看此文献)

浅谈iOS中的闭包

IOS 浅谈闭包block的使用的更多相关文章

  1. [iOS、Unity、Android] 浅谈闭包的使用方法

    前言 我们经常所编程语言的的进步速度是落后于硬件的发展速度的. 但是最近几年,闭包语法在各个语言中都有自己的体现形式,例如 • C语言中使用函数指针作为回调函数的入口: • Java和C#语言中的La ...

  2. iOS 浅谈MVC设计模式及Controllers之间的传值方式

    1.简述你对MVC的理解? MVC是一种架构设计.它考虑了三种对象:Model(模型对象).View(试图对象).Controller(试图控制器) (1)模型:负责存储.定义.操作数据 (2)视图: ...

  3. [iOS]浅谈NSRunloop工作原理和相关应用

    一. 认识NSRunloop  1.1 NSRunloop与程序运行 那么具体什么是NSRunLoop呢?其实NSRunLoop的本质是一个消息机制的处理模式.让我们首先来看一下程序的入口——main ...

  4. iOS——浅谈iOS中三种生成随机数方法

    ios 有如下三种随机数方法:

  5. 【转】iOS 浅谈:深.浅拷贝与copy.strong

    深.浅拷贝 copy mutableCopy NSString 1 2 3 4 5 6 NSString *string = @"汉斯哈哈哈"; // 没有产生新对象 NSStri ...

  6. Python核心编程 | 浅谈闭包的使用

    1.函数的引用   >>> def test(): print('test:') >>> test <function test at 0x10ffad488 ...

  7. 浅谈闭包(Closure)

    一.闭包 好长时间对于闭包都知道与函数和其环境变量有关系,但是一直没有一个清晰的认识.今天查看了一下维基百科,顺便写下来. 二.闭包的定义 在编程语言中,闭包(语义闭包或函数闭包)是指哪些可以将语义范 ...

  8. iOS 浅谈:深.浅拷贝与copy.strong

    深.浅拷贝 copy mutableCopy NSString NSString *string = @"汉斯哈哈哈"; // 没有产生新对象 NSString *copyStri ...

  9. ios浅谈关于nil和 NIL区别及相关问题(转)

    转自:http://blog.csdn.net/guozh/article/details/8469131 个就是将引用技术减1,所谓的引用计数就是看看有多个指针指向一块内存实体,当release一次 ...

随机推荐

  1. goto语句和标签

    goto 语句用于将执行流更改到标签处,虽然t-sql和pl/sql都提供了该语句,但是作为编程而言,我们不推荐使用此编程技术.要编写一个标签,应当在标识符后面加一个冒号.列如,下面示例使用goto语 ...

  2. c# 快速修改图片颜色

    public static void ChangeColour(this Bitmap bmp, byte inColourR, byte inColourG, byte inColourB, byt ...

  3. 2017年11月4日 vs类和结构的区别&哈希表&队列集合&栈集合&函数

    类和结构的区别 类: 类是引用类型在堆上分配,类的实例进行赋值只是复制了引用,都指向同一段实际对象分配的内存 类有构造和析构函数 类可以继承和被继承 结构: 结构是值类型在栈上分配(虽然栈的访问速度比 ...

  4. vue学习笔记之基础篇

    本文主要记录学习vue的一些基础内容及常用知识点的记录. 1.搭建脚手架 vue init webpack vue-demo 初始化一个使用webpack打包的vue项目 npm install 安装 ...

  5. javascript12个你必须掌握的技能

    网站建设的时候,作为码农,总喜欢写一些高效且省事的代码,这里,dbestech为你提供关于JavaScript的使用技巧点. 1. 空(null, undefined)验证 当我们创建了一个新的变量, ...

  6. Python中变量的本质探索

    Python中变量的本质探索 参考:Vamei博客Python进阶09 动态类型 ''' a = [1,2,3] ''' (1)这条"赋值语句"实际上是将a指向对象"[1 ...

  7. C/C++函数指针,指针函数的用法,用处

     先看函数指针 int func2(int x); /* 声明一个函数 */ int (*q2) (int x); /* 声明一个函数指针 */ q2=func2;    /* 将func函数的首地址 ...

  8. oracle查询时间

    oracle查询和时间有关的命令: 方法一:select * from dual where time between to_date('2012-06-18 00:00:00','yyyy-mm-d ...

  9. SpringBoot热部署插件

    1.配置在 maven工程中的pom.xml文件中 2.SpringBoot框架中提供的一个热部署插件,利用该热部署插件,我们可以在修改代码后不用重启应用,大大提高开发效率:

  10. HCNA调整RIP的运行版本

    1.拓扑图 2.实验配置 R1配置RIPv1 md5加密认证 Please press enter to start cmd line! ############################### ...