iOS 简单易懂的 Block 回调使用和解析

老实说在早前我已经学会了如何使用 Block 来做一些方法回调,传递参数的功能,并且用 Block 简单封装了第三方的网络库(AFNetworking)。虽说对 Block 的应用说不上得心应手,但是却是极其地喜欢使用这种设计模式,并且在项目中也大量地使用了。

但是,最近一位即将参加面试的学弟问我,什么是 Block 呢?我蒙圈了,但是毕竟是学长,我假装淡定地反问道:你所理解的 Block 是什么呢?学弟说:是一段封装的代码块,并可以放在任意位置使用,还可以传递数据。我心里暗喜,这孩子还是图样了,于是语重心长地说:Block 的本质是可以截取自动变量的匿名函数。但是说出这句话我就后悔了,这句话他喵的到底是个什么意思?看着学弟满意地走了之后,我就疯狂地上网查资料,万一下次这个熊孩子深究起来可不就破坏了我英明神武的形象了,但是并没有很满意的答案,大多是照文档描述了 Block 的定义以及基本用法,不然就是高深地去探讨 Block 底层的实现机制,显然这些都不适合让一个初学者既能学会使用又能够没有疑惑地使用。

本文主要讲的是 Block 回调的使用,以及 Block 是如何实现这种神奇的回调两部分来讲的。

Block 回调实现

不着急,先跟着我实现最简单的 Block 回调传参的使用,如果你能举一反三,基本上可以满足了 OC 中的开发需求。已经实现的同学可以跳到下一节。

首先解释一下我们例子要实现什么功能(其实是烂大街又最形象的例子):
有两个视图控制器 A 和 B,现在点击 A 上的按钮跳转到视图 B ,并在 B 中的textfield 输入字符串,点击 B 中的跳转按钮跳转回 A ,并将之前输入的字符串
显示在 A 中的 label 上。也就是说 A 视图中需要回调 B 视图中的数据。

想不明白的同学可以看一看最终实现的效果图:

block example

这里不再对 Block 的语法做说明了,不了解的同学可以点传送门

首先,我们需要定义两个试图控制器 AViewController 和 BViewController,现在我们需要思考一下,Block 应该在哪里定义呢?

我们可以简单地这样思考,需要回调数据的是 A 视图,那么 Block 就应该在 B 中定义,用于获取传入回调数据。

因此我们在 BViewController.h 中定义如下:

//BViewController.h
#import <UIKit/UIKit.h>

typedef void(^CallBackBlcok) (NSString *text);//1

@interface BViewController : UIViewController

@property (nonatomic,copy)CallBackBlcok callBackBlock;//2
@end

在这里,代码 1 用 typedef 定义了 void(^) (NSString *text)的别名为 CallBackBlcok 。这样我们就可以在代码 2 中,使用这个别名定义一个 Block 类型的变量 callBackBlock

在定义了 callBackBlock 之后,我们可以在 B 中的点击事件中添加 callBackBlock 的传参操作:

//BViewController.m

- (IBAction)click:(id)sender {
    self.callBackBlock(_textField.text); //1
    [self.navigationController popToRootViewControllerAnimated:YES];
}

这样我们就可以在想要获取数据回调的地方,也就 A 的视图中调用 block:

// AViewController.m
- (IBAction)push:(id)sender {
    BViewController *bVC = [self.storyboard instantiateViewControllerWithIdentifier:@"BViewController"];

    bVC.callBackBlock = ^(NSString *text){   // 1

        NSLog(@"text is %@",text);

        self.label.text = text;

    };
    [self.navigationController pushViewController:bVC animated:YES];
}

代码 1 中,通过对回调将 B 中的数据传递到代码块中,并赋值给 A
中的 label,实现了整个回调过程。

上例是通过将 block 直接赋值给 block 属性,也可以通过方法参数的方式传递 block 块。

由于考虑有的小伙伴FQ比较困难,完整的示例代码放在 git.oschina.net 上,代码地址:BlockMagic 。

关于 Block 的疑惑

到目前为止,一切看起来都很美好(如果你照着上面的例子做的话),功能正常, A 视图中也获取到数据了。但是某些人可能就要说了,你的代码有问题,你的思路有问题,你这是误人子弟。

是的,代码的确还有问题,第一个问题就是循环引用的问题,在 A 视图的block 代码块中:

bVC.callBackBlock = ^(NSString *text){
       NSLog(@"text is %@",text);
       self.label.text = text;
    };

代码 self.label.text = text; ,在 Block 中引用 self ,也就是 A ,而 A 创建并引用了 B ,而 B 引用 callBackBlock,此时就形成了一个循环引用,而编译器也不会报任何错误,我们需要非常小心这个问题(面试百分百问到我会乱说?)。此时我们通常的解决方法是使用弱引用来解除这个循环:

    __weak AViewController *weakSelf = self;
    bVC.callBackBlock = ^(NSString *text){
        NSLog(@"text is %@",text);
//        self.label.text = text;
        weakSelf.label.text = text;
    };

第二个问题是我自己对 Block 的理解不到位,我们都知道 Block 能截取自动变量,并且是不能在 Block 块中进行修改的(除非用__block修饰符),但是很明显 weakSelf.label.text的值被修改了,并且没有用__block修饰符, 这是为什么呢?因为 label 是个全局变量,而如果像如下的局部变量 a 是不能修改的,编译器也会报错:

局部变量

通过这个小例子发现的两个问题,也算是值得了。

Block 为什么能实现神奇的回调

在这里我不会说什么实现原理,仅仅是个人对 Block 能实现神奇回调的理解,有错误的地方请大家指出。

在先前使用 Block 的过程中,虽然会使用,但是总是有一个疑惑,简单说来就是:

为什么在 A 中的 block 块能调用到 B 中的数据?

回顾一下我们在 B 中所实现的代码,不外乎定义了一个 Block 变量,并在适当的时候传入参数,那么为什么在调用了  self.callBackBlock(_textField.text) 之后,值就神奇传到了 A 中的 Block 块了呢?

通过整理使用的过程,我发现是我们的思维陷入了误区(可能是我个人),我们认为在 B 中传入 _textField.text 参数之后, A 中的 Block 块就可以获取到值。虽然思路是对的,但其实是不完整,导致我们形成了回调的数据是通过某种底层实现传递过去的错觉,这就使得我们认为这不需要深究。

事实是,通过简单的整理我们可以发现完整的回调流程应该是这样的:

回调流程

  1. block 代码块赋值给 bVC.callBackBlock,此时 callBackBlock 的指针就指向这个代码块。
  2. 调用 callBackBlock(NSString *text)
  3. 由于 callBackBlock 的指针是指向 A 中的 block 代码块,因此执行代码块的代码,实现回调。

    很显然之前我忽略了代码块赋值给 callBackBlock 的这个操作(羞愧)。

    现在再通过一段代码可以更清晰地理解这个原理:

 bVC.callBackBlock = ^(NSString *text){ //1
        NSLog(@"text is %@",text);
    };
  bVC.callBackBlock = ^(NSString *text){ //2
        NSLog(@"text b is %@",text);
    };

上述代码中,我们对 callBackBlock进行了两次赋值,结果会怎么样呢?

two block

可以看出来,Block 的回调只对代码 2 生效,因为callBackBlock的指针最后指向了代码 2 的代码块。所以并没有什么神奇的魔法,也没什么隐藏的底层机制(这里指的是方便理解的底层)让你可以带着疑惑去使用它。

总结

我这个人学习方法,总结起来就是看到新技术,先在自己的代码里跑一遍,能跑通,并且使用起来没有什么难度,就基本不会深究了(如果遇到某个熊孩子就坑了)。但是自我反思过,这样的学习方法是很不对的,写代码不能不求甚解,如果想要有所突破,不想局限于码农,一定要深入探究一下实现的机制,最起码要保证不带着疑惑去使用。

时间: 2024-08-05 15:40:03

iOS 简单易懂的 Block 回调使用和解析的相关文章

iOS开发-Block回调

关于Block之前有一篇文章已经写过一篇文章Object-C-代码块Block回顾,不过写的比较浅显,不能体现出Block在实际开发中的重要性,关于Block的基础知识,可以参考之前的博客.在实际开发中Block在回调过程中的是非常适合开发使用,不管是苹果的官方的接口还是一些第三方库的接口中都用到了Block回调.很多情况下Block和GCD一起使用,最常见的场景的就是App去后台取数据的过程中是需要时间,数据取成功之后我们才能更新UI页面,这就是最常见的回调的方式,也可以通过Notificat

iOS:使用block代码块实现事件处理过程中的回调

block是什么,这里就不多加强调了,它的优点: 第一:执行效率高,速度快 第二:使用起来比代理简单,省却不少代码,增强代码美感 有一些小的知识点要强调一下: 第一点:它类似于一个匿名函数,也跟java中的匿名内部类相似,但是,记住,它是一种基本数据类型: 第二点:在类中声明block为属性时,如果使用assgin修饰,那么它被放到了栈中,方法已过就会被销毁,所以,尽量使用copy作为修饰词,这样一来block就被存放到了堆中,生命周期就会延长,保证block不会被立即销毁: 第三点:要防止循环

iOS block 回调

说明:本文是在了解block基础知识基础上的应用,假定阅读者已经具备block基础知识. 目的:通过block回调方式将SecondViewController中的值传入到ViewController中,在某些时候,通过block回调,可以避免delegate的繁琐 1,新建Single View Application工程,新建SecondViewController 2,在ViewController中添加UILabel用于显示SecondViewController传过来的值,把label

ios编程之回调机制

ios编程之回调机制详解:   ———————————————— 函数/方法/block块一系列概念: 函数在大部分高级语言中都是个重要的概念,函数实现就是对一段代码的封装,我们一般会为了完成某一个业务功能或编程逻辑而需要组织数行代码,而这数行代码还有可能被使用多次,所以将它们封装成一个函数,每一次的执行我们称之为调函数或函数调用. 在C程序中,我们知道程序是从main函数开始执行,执行完main函数就退出程序,其实我们程序员很少去跟踪整个程序的执行流,一个程序(一段二进制代码)如何从加载到运行

iOS之Block

iOS中的Block机制,可以简化程序,实现回调功能.跟C语言中的函数指针类似.可以通过传递Block实现函数的回调处理. 简单地例子: 定义一个block int(^myblock)(int a, int b); 解释:定义了一个block,名称是myblock(类似函数指针),它的返回类型是int,传入2个int型的a,b的参数. 定义函数指针具体要做的内容.(函数体) myblock = ^(int a, int b){ retrun a+b; }; 解释:定义了myblock的具体功能,

iOS 中Block以及Blocks的使用

一.ios中block的使用 Block可以帮助我们组织独立的代码段,并提高复用性和可读性.iOS4在UIKit中引入了该特征.超过100个的Apple API都使用了Block,所以这是一个我们必须开始熟悉的知识. Block是什么样的? 你可以使用^操作符来声明一个Block变量,它表示一个Block的开始. int num1 = 7; int(^aBlock)(int) = ^(int num2) { return num1+nunm2; }; 在如上代码中我们将Block声明为一个变量,

ios开发 Block(二) 实现委托

委托和block是IOS上实现回调的两种机制.Block基本可以代替委托的功能,而且实现起来比较简洁,比较推荐能用block的地方不要用委托. 实现效果如图 第一步,自定义CustomCell 1 #import <Foundation/Foundation.h> 2 3 @interface CustomCell : UITableViewCell 4 5 @property (strong, nonatomic) IBOutlet UILabel *labName; 6 @property

iOS之block浅谈

前言 ios4.0系统已开始支持block,在编程过程中,block被Obj-C看成是对象,它封装了一段代码,这段代码可以在任何时候执行.block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值.它和传统的函数指针很类似,但是有区别:block是inline的,并且它对局部变量是只读的. block和函数的相似性:(1)可以保存代码(2)有返回值(3)有形参(4)调用方式一样. block的使用 1.block的定义 // 声明和实现写在一起,就像变量的声明实现 int a

OC3大回调模式使用总结(三)block回调

OC 3大回调模式使用总结(三)block回调 block 又称 代码块,闭包等 是一个匿名的函数,它可以当做一个对象来使用,只不过这个对象很特殊,是一段代码,他可以保存你写的一段预备性质代码,待某个不确定的事件发生时再调用;事件发生时,它可能会给你传递一些状态参数(回传),来方便你的使用 block常用类型(可以看做一个匿名函数的类型): typedef int(^Add)(int,int);//定义一个block类型 typedef void(^Logg)(NSString *);//有一个