前言:对于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. WCF寄宿在C#控制台,并用命令行启动

    公司运用wcf比较多,所以自己研究一下寄宿做个笔记,wcf寄宿在控制台有两种方式 第一种,直接在控制台程序内添加wcf服务. 第二种分别添加控制台程序和wcf服务应用程序,前者引用后者,并在app.c ...

  2. Javaweb三大组件之一 servlet

    1.servlet基本使用 注意:单例:init只会执行一次    线程不安全 1.1 创建方式 (1)实现servlet接口[不使用] package cn.getword.servlet; imp ...

  3. NIOSocket Server Client

    最近在看Netty框架,顺便写了一下NIO SocketChannel服务端和客户端 Server.java import java.io.IOException; import java.net.I ...

  4. 关于消息推送的补充,主要介绍服务端的实现,包含object c 版本 c 版本 java 版本 php 版本 (转)

    要实现消息推送功能,我们可以采用第三方(腾讯:信鸽:百度:云推送:极光推送:友盟):当然,因为各种原因,我们不能使用第三方的推送服务,那我们就需要自己编写服务端.在网上寻觅了很久,找到一篇很不错的讲解 ...

  5. ASP.NET MVC 音乐商店 - 5 通过支架创建编辑表单 续

    查看 StoreManager 控制器的代码 现在,Store Manager 控制器中已经包含了一定数量的代码,我们从头到尾重新过一下. 首先,在控制器中包含了标准的 MVC 控制器的代码,为了使用 ...

  6. Ruby系列文章之1---开发者应该熟悉的10个工具

    1. Git Git是进入Ruby这个生态圈首先最应该学会的工具.几乎所有以Ruby开发出来的套件都放在Github上.也就是不管你要下载或修改协作都需要透过Git. 2. RVM Ruby有很多种i ...

  7. 深入浅出 关于SQL Server中的死锁问题

    深入浅出 关于SQL Server中的死锁问题 博客2013-02-12 13:44   分享到:我要吐槽 死锁的本质是一种僵持状态,是多个主体对于资源的争用而导致的.理解死锁首先需要对死锁所涉及的相 ...

  8. php调用含有命名空间的类

    现有a.php 和 b.php在同一个目录下 a.php中 namespace myspace; class A{ __construct(){} .... } b.php中调用类A require_ ...

  9. day2 数据结构和一些基础知识

    请查看我的云笔记链接: http://note.youdao.com/noteshare?id=4171342601326695ec87866e1cc3e410&sub=20CFB149543 ...

  10. TP5.0:新建控制器

    例如,我们在admin模块下创建一个名为OneMenu.php的控制器 1.在该控制器文件中内容为: 2.访问的URL为:http://localhost/tp5/public/index.php/a ...