前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >iOS中的「回调(callback)」

iOS中的「回调(callback)」

作者头像
iOS Development
发布于 2019-02-14 09:46:06
发布于 2019-02-14 09:46:06
3.6K00
代码可运行
举报
运行总次数:0
代码可运行

本文主要参考:《Object-C 编程 Big Nerd Ranch Guide》一书第24章

本文适读对象:

  • 想系统了解iOS中若干种回调机制的朋友;
  • 想初步了解Block语法的朋友。
  • 没有自己亲自实现过委托、通告、Block进行回调(传递数据)的朋友;

先用一张图总结本文

iOS中的回调(callback)

「回调(callback)」的定义:

“A callback lets you write a piece of code and then associate that code with a particular event. When the event happens, your code is executed.”

——摘自《Object-C Programming:The Big Nerd Ranch Guide 2nd》P613

解读如下:

callback(回调)就是一段「代码」,我们会通过某种途径,将这段「代码」和一个特定的事件(event)联系起来,当特定事件(event)发生后,这段「代码」被执行。

很好,简单粗暴。

为什么要有「回调(callback)」?

「上帝说要有callback,于是就有了callback。」——佚名

在这里,斗胆将程序分为两种:

  • 「非事件驱动」型程序
  • 「事件驱动(event-driven)」型程序

「非事件驱动」型程序。

这类程序,遵循这样一个流程:启动程序 -> 执行程序(代码) -> 退出程序。程序会在执行完所有代码后,立刻退出,中途不会有任何事件(event)发生(除非有bug)。

比如,我们用Xcode新建一个OS X下的Command Line Tool工具,直接在main.m文件中的main函数写一段从1加到100的代码,然后打印结果出来。如下图:

启动程序->执行代码->退出程序

其中「Program ended with exit code: 0」就表示正确退出了程序。

「事件驱动(event-driven)」型程序

这类程序,遵循这样一个流程:启动程序 -> 等待事件(event) -> 事件被触发 -> 执行callback(回调) -> 继续等待事件(event) -> 人为退出程序

打个比方,我想用淘宝APP帮手机充值,一打开APP,它并不会马上跳到充值页面,是要等待我的点击事件,当点击了充值的按钮,才会跳到充值页面(执行了callback)。

所以,大家应该很容易联想到,iOS的应用几乎都是「事件驱动(event-driven)」的,应用一经启动,就在等待事件的发生,当发生某个事件(比如点击了某个按钮),应用就会执行某段代码(callback)进行响应。

这里的「事件(event)」,是非常宽泛的,可以是使用者的一次点击、可以是系统的一次通知、可以是服务器返回的一次数据、可以是蓝牙外设连接成功后,发送给手机的一条指令等等。

所以,我们得出结论——上帝说:我们需要callback(回调)。

iOS中的Run loop

我们知道自己需要callback,那在iOS中,具体要怎么实现呢?

首先要有专门的人负责等待事件(event),如果没有这个人,程序就会像「非事件驱动」型程序一样,一个劲地从头跑到尾,就结束了~

这砖找谁搬?

苹果工程师找了一个OC类型的对象,专门干这活儿——等待事件(event)的发生。它就是NSRunLoop实例。看名字就大概能猜到,它会不断循环(loop)。

NSRunLoop实例会持续等待着,当特定事件发生时,触发回调(callback)。

调用以下方法,即可得到一个run loop。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[[NSRunLoop currentRunLoop] run];

所以,在上述例子中加入一个run loop,这个程序就永远不会退出了(除非人为关闭),有了这个run loop,就可以等待事件(event)的发生了,如下:

添加run loop,等待事件(event)发生

注意,已经没有「Program ended with exit code: 0」——表示成功退出程序这一句了。

当然,新建iOS工程时,已经帮你干这活了,不需要你再手动去实现。

Objective-C中4种实现「回调(callback)」的途径

好了,有了run loop做基础,我们就可以具体去实现iOS中的各种callback(回调)了。

Objective-C中有4种途径可以实现回调:

1、Target-action/目标-动作对

先看代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 为按钮添加回调——Target-action/目标-动作对
// 第一个参数:发送消息给谁
// 第二个参数:事件发生后,执行什么代码(回调)
// 第三个参数:发生哪类型的点击事件会触发回调
[button addTarget:self
           action:@selector(click:)
 forControlEvents:UIControlEventTouchUpInside];

以上代码,用人话来讲,大意就是:当按钮被点击后(某事件(event)被触发了),执行本类(self)中的click:方法(回调)。

再看一个NSTimer对象的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 一个自定义类对象
Logger *logger = [[Logger alloc] init];

// 为NSTimer对象添加回调——Target-action/目标-动作对
// 第一个参数:发生哪种类型的点击事件会触发回调(这里表示2秒后触发回调)
// 第二个参数:发送消息给一个Logger实例(Logger是自定义的类)
// 第三个参数:事件发生后,执行什么代码(回调)
// 第四个参数:如果有需要传递的数据,可以放在这里
// 第五个参数:这个计时器是否重复执行(也就是说是否重复执行回调)
__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
                                                           target:logger
                                                         selector:@selector(logSomething:)
                                                         userInfo:nil
                                                          repeats:YES];

以上代码,用人话来讲就是:创建一个定时器,2秒后(某事件(event)被触发了),执行logger对象所属类的logSomething:方法(回调)。而且是重复地执行。

所以,Target-action/目标-动作对,就是「当事件发生时,向指定的对象发送某个特定的消息」。

以上是书中的描述,但谁是target,谁又是action,搞得含糊不清。所以更倾向于这样理解:

当事件发生时,执行某个类(target)的某个方法(action)。

这里的「某个类」,指的是target参数(例子1是self,例子2是logger)所属类;而「方法」,也就是该类已经实现的某个方法(例子1是click:方法,例子2是logSomething:方法),就是action。

2、Helper objects/辅助对象

「Helper objects/辅助对象」,可以先这样理解:某些功能,找其他类来辅助实现。

常见的就是「delegates/委托」和「/data sources数据源」。下面我们来动手实现一下「delegates/委托」。

先假设有这么一个需求:我们需要用手机通过BLE(低功耗蓝牙)连接8个蓝牙设备,成功连接到8个蓝牙设备后,弹出提示框,提示使用者已经成功连接了多少个蓝牙设备。

先看代码实现:

我们新建一个类,叫MyCnetralManager,专门负责手机和蓝牙模块之前的通讯,这个类的.h文件如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#import <Foundation/Foundation.h>

// 步骤1:声明一份协议(OC中的协议一般写在类中的.h文件)
// 这个协议只有一个方法
@protocol MyCnetralManagerDelegate <NSObject>

// 标记了optional关键字,表示协议中这个方法是可选择性实现(也就是可以不实现)
@optional
/**
 *  这个方法通知「被委托对象」,所有设备已经连接上了.
 *
 *  @param devicesCount 传递连接上的设备数量给被委托对象
 */
- (void)allDevicesDidConnected:(NSInteger)devicesCount;
@end

@interface MyCnetralManager : NSObject

// 步骤2:声明delegate属性
@property (weak) id<MyCnetralManagerDelegate> delegate;
@end

.m文件如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#import "MyCnetralManager.h"
// 导入CoreBluetooth蓝牙框架(就是用这个框架进行BLE开发的)
@import CoreBluetooth;

/// 默认需要连接的硬件为8个
const NSInteger defaultDivicesCount = 8;

@interface MyCnetralManager ()<CBCentralManagerDelegate>

/// CBCentralManager对象
@property (strong, nonatomic) CBCentralManager *bleManager;

/// 对已经连接上的设备进行计数
@property (nonatomic) NSInteger connectedDiviceCount;
@end


@implementation MyCnetralManager

// 这里省略蓝牙搜索、连接、发现「服务」、发现「特征」等过程

// 在这里,我们也是应用了官方的「delegates/委托」(CBCentralManagerDelegate),实现发生某些事件后,再执行某些代码(回调)
#pragma mark - CBCentralManagerDelegate
// 这个方法标记了@required,所以一定要实现
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
// (手机)蓝牙状态改变后的回调(比如手机打开蓝牙、关闭蓝牙,都会调用这个方法)
}

// 手机每成功连接一个设备(某事件被触发),这个方法都会被调用(回调)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    // 每连接成功一个设备,计数加1
    _connectedDiviceCount++;
    
    // 实现我们自己的「delegates/委托」(MyCnetralManagerDelegate)
    // 如果连接上设备数量已经等于事先定义好的数量(8个),就通知委托对象已经连接成功所有设备,并传递连接数量。
    if (_connectedDiviceCount == defaultDivicesCount) {
        // 步骤3
        if ([_delegate respondsToSelector:@selector(allDevicesDidConnected:)]) {
            [_delegate allDevicesDidConnected:_connectedDiviceCount];
        }
    }
}

@end

以上三个步骤,我们已经实现:当连接成功8个蓝牙设备后,向委托对象发送消息allDevicesDidConnected:,并传递一个参数——连接成功设备的数量。

接下来,我们要找到正真干活(显示提示框)的人,找谁呢?找其中一个控制器,如下(某个控制器的.m文件):

我们的目录结构大概如下:

大概会有这两个类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#import "ViewController.h"
#import "MyCnetralManager.h"

// 遵守协议
@interface ViewController ()<MyCnetralManagerDelegate>

/// 声明一个提示框对象
@property (nonatomic, strong) UIAlertView *alertView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    MyCnetralManager *manager = [[MyCnetralManager alloc] init];
    // 委托谁(找谁干活),就设置delegate是谁
    // 初接触委托的人经常会忘记这步
    // 如果在XIB文件,也可以通过拖线来完成,就不需要用代码实现
    manager.delegate = self;
    
    // 上面这句,可以理解为:MyCnetralManager类委托ViewController类做一些事情。
}

#pragma mark - MyCnetralManagerDelegate
// 实现协议(MyCnetralManagerDelegate)中的方法
// devicesCount是MyCnetralManager类传过来的一个参数
- (void)allDevicesDidConnected:(NSInteger)devicesCount {
    // 拿到参数,显示提示框
    if (!_alertView) {
        _alertView = [[UIAlertView alloc] initWithTitle:@"注意!"
                                                message:[NSString stringWithFormat:@"已经成功连接%@个设备", @(devicesCount)]
                                               delegate:self
                                      cancelButtonTitle:@"OK"
                                      otherButtonTitles:nil, nil];
    }
    
    [_alertView show];
}
@end

到此,我们就自己实现了一次简单的委托。

可以翻译成这样的人话:MyCnetralManager委托ViewController做一件事——成功连接所有设备后,显示提示框。

而书上是这样描述的:「当某事件发生时,向遵守相应协议的辅助对象发送消息。」

上述例子可以这样说:「当成功连接8个蓝牙设备后,向遵守MyCnetralManagerDelegate协议的ViewController对象发送allDevicesDidConnected:消息(并传递一个参数)」

为什么不在CBCentralManagerDelegate中的centralManager:didConnectPeripheral:直接弹出提示框提示使用者,而要搞得这么「复杂」?如果有这个疑问,可以移步到我在知乎回答的问题:如何用简单明了的话解释一下什么是 Objective-C 中的委托?或许可以解答你的部分疑问。

至于「data sources/数据源」,常用UITableView的朋友,应该比较熟悉了,本质上和上面讲的委托,一回事儿。(不过我还没有自己实现过~)

3、Notifications/通告

Notification也可以翻译成「通知」,但是为了不和iOS中的「本地通知」、「远程通知」这类「通知」混淆,这里将Notification统一翻译成「通告」,会比较好区分。

实现上面同样的需求,用通告的方式,就会变成这样:

先在MyCnetralManager.m文件中发送通告

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#import "MyCnetralManager.h"
@import CoreBluetooth;

/// 默认需要连接的硬件为8个
const NSInteger defaultDivicesCount = 8;

/// 定义通告的名称
static NSString *kNotificationAllDevicesDidConnected = @"com.YourCompanyName.YourProjectName.AllDevicesDidConnected";

/// 用于创建字典的key
static NSString *totalConnectedDevicesKey = @"totalConnectedDevices";

@interface MyCnetralManager ()<CBCentralManagerDelegate>

/// CBCentralManager对象
@property (strong, nonatomic) CBCentralManager *bleManager;

/// 对已经连接上的设备计数
@property (nonatomic) NSInteger connectedDiviceCount;

@end

@implementation MyCnetralManager

// 在这里,我们也是应用了官方的「delegates/委托」(CBCentralManagerDelegate),实现发生某些事件后,再执行某些代码(回调)
#pragma mark - CBCentralManagerDelegate
// 这个方法标记了@required,所以一定要实现
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
// (手机)蓝牙状态改变后的回调(比如手机打开蓝牙、关闭蓝牙,都会调用这个方法)
}

// 成功连接一个蓝牙设备的回调(官方框架)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    // 每连接成功一个设备,计数加1
    _connectedDiviceCount++;
    
    // 如果连接上设备数量已经等于事先定义好的数量(8个),就通知委托对象已经连接成功所有设备,并传递连接数量。
    if (_connectedDiviceCount == defaultDivicesCount) {
        // 发送通告
        // 第一个参数:通告名称
        // 第二个参数:谁发送的通告
        // 第三个参数:需要传递的额外数据(是一个字典)
        [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationAllDevicesDidConnected
                                                            object:nil
                                                          userInfo:@{totalConnectedDevicesKey:[NSNumber numberWithInteger:_connectedDiviceCount]}];
    }
}

@end

然后在ViewController.m中的viewDidLoad方法内「监测」这个通告:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 方案一:传统的selector形式
    // 观察通告kNotificationAllDevicesDidConnected,一接收到这个通告,就执行showAlertView:方法(回调)
    // 第一个参数:将谁注册为观察者(这里将自己(控制器类自身)注册为观察者)
    // 第二个参数:接到通告后,要执行什么方法(代码/回调)
    // 第三个参数:接收哪个通告(通告的名称)
    // 第四个参数:接收谁发送的通告(nil表示无论谁发送,只要是kNotificationAllDevicesDidConnected,都接收)
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(showAlertView:)
                                                 name:kNotificationAllDevicesDidConnected
                                               object:nil];
    
    
    // 方案二:Block形式(Block会在下文展开)
    // 用Block语法,使代码更集中、简洁
    // 观察通告kNotificationAllDevicesDidConnected,一接收到这个通告,就执行Block中的代码(回调)
    /*
    [[NSNotificationCenter defaultCenter] addObserverForName:kNotificationAllDevicesDidConnected
                                                      object:nil
                                                       queue:nil
                                                  usingBlock:^(NSNotification * _Nonnull note) {
        // 弹出提示框
        if (!_alertView) {
            _alertView = [[UIAlertView alloc] initWithTitle:@"注意!"
                                                    message:[NSString stringWithFormat:@"已经成功连接%@个设备", note.userInfo[totalConnectedDevicesKey]]
                                                   delegate:self
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil, nil];
        }
        [_alertView show];
                                                      
    }];
    */
}

// 没错,方案一中没有用Block语法,所以这里还要写一个方法
// 方案一中,接收到通告后要执行的方法
- (void)showAlertView:(NSNotification *)note {
    // 弹出提示框
    if (!_alertView) {
        _alertView = [[UIAlertView alloc] initWithTitle:@"注意!"
                                                message:[NSString stringWithFormat:@"已经成功连接%@个设备", note.userInfo[totalConnectedDevicesKey]]
                                               delegate:self
                                      cancelButtonTitle:@"OK"
                                      otherButtonTitles:nil, nil];
    }
    [_alertView show];
}

所以,苹果提供了一个叫做「通告中心」的对象,可以通过[NSNotificationCenter defaultCenter]获得,利用这个通告中心,我们可以「发通告」、「监测(接收)通告」,利用这个机制,实现回调。

上面这个例子,可以说成:「当成功连接8个蓝牙设备后,向通告中心发布kNotificationAllDevicesDidConnected通告(一个字符串),并通过userInfo(一个字典)这个参数传递设备的数量;然后通告中心会转发通告出去;这时候在监测该通告的ViewController类收到通告后,就会执行相应的代码(回调)」。

4、Blocks

Block算是Objective-C中比较高阶的内容。这样理解吧,Block其实就是在大括号里面的一大段代码,这段代码,会在某事件(event)发生后被执行。

Block的一些语法

先看看一些Block相关的语法,熟悉一下:

  • Block常量:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // Block常量 
    ^{
        NSLog(@"我是一个Block常量。^是辨识我身份的标志。记得最后加分号哦,因为我就是一个常量,就像数字「5;」一样");
    };
  • 带实参、会返回值的Block:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 有实参,有返回值的Block
    ^(double dividend, double divisor) {
        NSLog(@"我是一个有参数、有返回值的Block");
        double quotient = dividend / divisor;
        return quotient;
    };
  • 声明Block变量:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 声明一个Block变量(无返回值;有参数), 
   void (^YourBlockName)(id, NSString *, NSUInteger, BOOL *);
   
   // 或
   void (^YourBlockName)(id obj, NSString *yourString, NSUInteger deviceCount, BOOL *stop);
  • 给Block变量赋值:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 给你的Block变量赋值
    // 等号左边是一个Block变量,等号右边是一个Block常量,将常量赋值给变量
    YourBlockName = ^(id array, NSString *theString, NSUInteger count, BOOL *stop){
       // Do something what you want.
    };
  • Block的声明、赋值一起进行:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 声明、赋值一起
    void (^YourBlockName)(id, NSString *, NSUInteger, BOOL *) = ^(id array, NSString *theString, NSUInteger count, BOOL *stop){
        // Do something what you want.
    };
    
    // 其实就像 int a = 5; 一样(只是Block比较长而已,语法有点怪而已)
  • 用C语言的typedef关键字给Block命名为一种新的数据类型(最常用这种形式)。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    // 在文件顶部(#import之下)用typedef将Block重新定义为一种新的数据类型
    typedef void(^YourBlockName)(id, NSString *, NSUInteger, BOOL *);
    
    
    // 利用新的数据类型,声明一个Block变量
    @property (nonatomic, strong) YourBlockName yourBlock;
    
    // 再对这个变量进行必要的操作(赋值)

以上是关于Block的一些语法,帮助不熟悉的朋友熟悉一下。它其实就是大括号括起来的一段代码,只是语法有点「怪异」而已,而且可以作为方法中的参数进行传递。(在Swift中,与之对应的貌似是「闭包(Closures)」)。

利用Block实现回调

下面,来看一下如何用Block实现回调(实现上面一样的需求):

在MyCnetralManager.h文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#import <Foundation/Foundation.h>
@import CoreBluetooth;

// 步骤1:
// 将Block重新定义为一种新的数据类型
// 这个Block无返回值;有一个参数(类型为NSUInteger)
typedef void(^AllDevicesDidConnectedBlock)(NSUInteger divicesCount);

@interface MyCnetralManager : NSObject

// 步骤2:
// 声明一个(Block)变量
@property (nonatomic, strong) AllDevicesDidConnectedBlock callbackForAllDevicesDidConnected;

// 步骤3:
// 声明一个以上述Block作为参数的方法
- (void)callbackForAllDevicesDidConnected:(AllDevicesDidConnectedBlock)allDevicesDidConnectedBlock;
@end

在MyCnetralManager.m文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 成功连接一个蓝牙设备的回调(官方框架)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    // 每连接成功一个设备,计数加1
    _connectedDiviceCount++;
    
    if (_connectedDiviceCount == defaultDivicesCount) {
        // 步骤4:
        // 实现Block回调并进行数据传递
        if (self.callbackForAllDevicesDidConnected) {
            self.callbackForAllDevicesDidConnected(_connectedDiviceCount);
        }
    }
}

- (void)callbackForAllDevicesDidConnected:(AllDevicesDidConnectedBlock)allDevicesDidConnectedBlock {
    // 步骤5:
    // 给我们的Block变量赋值
    self.callbackForAllDevicesDidConnected = allDevicesDidConnectedBlock;
}

最后在ViewController.m中的viewDidLoad方法内进行回调:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- (void)viewDidLoad {
    [super viewDidLoad];

    _myCentralManager = [[MyCnetralManager alloc] init];
    
    // 利用Block进行回调
    // (调用了MyCnetralManager的callbackForAllDevicesDidConnected:方法,传递了一个Block参数)
    [_myCentralManager callbackForAllDevicesDidConnected:^(NSUInteger devicesCount) {
        NSLog(@"执行了回调:已经成功连接%@个设备", @(devicesCount));
        
        // 弹出提示框
        if (!_alertView) {
            _alertView = [[UIAlertView alloc] initWithTitle:@"注意!"
                                                    message:[NSString stringWithFormat:@"已经成功连接%@个设备", @(devicesCount)]
                                                   delegate:self
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil, nil];
        }
        [_alertView show];
    }];
}

以上是将Block作为一个方法的参数,实现的回调。也可以直接用Block(作为属性)进行回调,如下:

在MyCnetralManager.h文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#import <Foundation/Foundation.h>
@import CoreBluetooth;

// 步骤1:
// 将Block重新定义为一种新的数据类型
// 这个Block无返回值;有一个参数(类型为NSUInteger)
typedef void(^AllDevicesDidConnectedBlock)(NSUInteger divicesCount);

@interface MyCnetralManager : NSObject

// 步骤2:
// 声明一个(Block)变量
@property (nonatomic, strong) AllDevicesDidConnectedBlock callbackForAllDevicesDidConnected;

// 其实就是把之前在这里的方法删除
@end

在MyCnetralManager.m文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 成功连接一个蓝牙设备的回调(官方框架)
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    // 每连接成功一个设备,计数加1
    _connectedDiviceCount++;
    
    if (_connectedDiviceCount == defaultDivicesCount) {
        // 步骤3:
        // 利用Block回调并传数据
        if (self.callbackForAllDevicesDidConnected) {
            self.callbackForAllDevicesDidConnected(_connectedDiviceCount);
        }
    }
}

最后在ViewController.m中的viewDidLoad方法内进行回调:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
- (void)viewDidLoad {
    [super viewDidLoad];

    _myCentralManager = [[MyCnetralManager alloc] init];

    // 在Block中调用self,可能会导致「引用循环」,所以使用weakSelf
    __weak typeof(self) weakSelf = self;
    
    // 直接用Block(MyCnetralManager的属性)回调
    _myCentralManager.callbackForAllDevicesDidConnected = ^(NSUInteger devicesCount) {
        NSLog(@"执行了回调:已经成功连接%@个设备", @(devicesCount));
        
        // 弹出提示框
        if (!weakSelf.alertView) {
            _alertView = [[UIAlertView alloc] initWithTitle:@"注意!"
                                                    message:[NSString stringWithFormat:@"已经成功连接%@个设备", @(devicesCount)]
                                                   delegate:self
                                          cancelButtonTitle:@"OK"
                                          otherButtonTitles:nil, nil];
        }
        [weakSelf.alertView show];
    };
}

两者的区别是:是否多一个方法。不过网上建议使用前者。个人也倾向于使用前者,因为作为方法的参数时,一敲回车,整个Block都会自动补全,而用后者,不会自动补全,要自己一个个敲。

总结

上面,简单实现了Objective-C中的4种回调。

那究竟该使用哪种回调呢?总结书上的建议:

  • 当只发生单个事件(event),只需要完成一件事情进行响应,建议用「Target-action/目标-动作对」。比如NSTimer、UIButton等。
  • 当会发生若干事件(event),要完成多件事情进行响应,建议使用「Helper objects/辅助对象」,当然了,最常见的是「delegate/委托」(另外还有「data sources/数据源」)。
  • 当发生单个事件(event),多个对象要进行响应,建议使用「Notifications/通告」
  • Block,当为了写出更简洁的代码、更好的代码结构,建议使用Block(自己总结的)。

以上,就是关于iOS中「回调(callback)」的一些入门级分享。如有谬误,请斧正,谢谢。

尊重劳动成果,转载请注明出处,谢谢。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016.03.05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
iOS简易蓝牙对战五子棋游戏设计思路之一——核心蓝牙通讯类的设计
    本系列博客将系统的介绍一款蓝牙对战五子棋的开发思路与过程,其中的核心部分有两个,一部分是蓝牙通讯中对战双方信息交互框架的设计与开发,一部分是五子棋游戏中棋盘逻辑与胜负判定的算法实现。本篇博客将介绍游戏中蓝牙通讯类的设计思路
珲少
2018/08/15
1.2K0
iOS开发之BLE(二)——外设连接与断开
在iOS开发之BLE(一)——理论知识一文中,主要对iOS开发中BLE的基本理论知识进行了介绍,本文以中心模式为例讲解蓝牙的连接过程,并进行案例实践。
YungFan
2019/03/22
3.1K0
iOS开发之BLE(二)——外设连接与断开
iOS开发·runtime原理与实践: 关联对象篇(Associated Object)(应用场景:为分类添加“属性”,为UI控件关联事件Block体,为了不重复获得某种数据)
分类(category)与关联对象(Associated Object)作为objective-c的扩展机制的两个特性:分类,可以通过它来扩展方法;Associated Object,可以通过它来扩展属性;
陈满iOS
2018/09/10
3K0
iOS开发·runtime原理与实践: 关联对象篇(Associated Object)(应用场景:为分类添加“属性”,为UI控件关联事件Block体,为了不重复获得某种数据)
iOS开发之蓝牙通讯 原
        蓝牙是设备近距离通信的一种方便手段,在iPhone引入蓝牙4.0后,设备之间的通讯变得更加简单。相关的蓝牙操作由专门的CoreBluetooth.framework进行统一管理。通过蓝牙进行通讯交互分为两方,一方为中心设备central,一方为外设peripheral,外设通过广播的方式向外发送信息,中心设备检索到外设发的广播信息,可以进行配对连接,进而进行数据交互。
珲少
2018/08/15
1.2K0
函数响应式编程及ReactiveObjC学习笔记 (二)
之前我们初步认识了RAC的设计思路跟实现方式, 现在我们再来看看如果使用它以及它能帮我们做什么
周希
2019/10/15
5080
iOS Bluetooth 打印小票(二)
在上一篇中介绍了打印小票所需要的命令,这一篇介绍Bluetooth连接蓝牙和打印小票的全过程。
Haley_Wong
2018/08/22
3.3K0
iOS Bluetooth 打印小票(二)
【IOS 开发】基本 UI 控件详解 (UIDatePicker | UIPickerView | UIStepper | UIWebView | UIToolBar )
转载注明出处 : http://blog.csdn.net/shulianghan/article/details/50348982
韩曙亮
2023/03/27
5.1K0
【IOS 开发】基本 UI 控件详解 (UIDatePicker | UIPickerView | UIStepper | UIWebView | UIToolBar )
【IOS 开发】基本 UI 控件详解 (UISegmentedControl | UIImageView | UIProgressView | UISlider | UIAlertView )
转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/50163725
韩曙亮
2023/03/27
3.4K0
【IOS 开发】基本 UI 控件详解 (UISegmentedControl | UIImageView | UIProgressView | UISlider | UIAlertView )
APP与硬件交互-蓝牙连接测试
7. UUID:蓝牙上的唯一标示符,为了区分不同服务和特征,就用UUID来表示。
用户5521279
2019/07/30
5.5K0
iOS单例中 Block 回调一对多设计
 播放器是通过代理委托来告知外部当前展示的 VC 类关于音乐播放信息,但需求迭代过程中新增了一个App全局页面展示的音乐悬浮窗,悬浮窗需要实时监听当前播放器的播放状态并更新 view ,而且保持原有 VC 类遵循播放器的代理并更新 view。原本通过代理委托一对一实现的场景被打破,现在要满足一对多的场景。产品最终要实现下面的效果:
我只不过是出来写写代码
2019/06/15
2.9K2
iOS 蓝牙4.0开发使用(内附 Demo)
近几年,智能设备越来越火,这些智能设备中,有很大一部分是通过手机来控制硬件设备,来达到预期的效果,这中间少不了要使用到蓝牙功能,通过蓝牙来通信来控制设备。
网罗开发
2021/01/29
1.7K0
iOS CoreBluetooth 的使用讲解概念分析代码实战
最近研究了iOS下连接蓝牙打印机,实现打印购物小票的功能,对iOS中BLE 4.0的使用有了一定的了解,这里记录一下对BLE 4.0的理解。 由于很多文章同时讲CBCentralManager和CBPeripheralManager,所以很容易傻傻分不清楚。很少把iPhone作为蓝牙外设在广播发送数据的情形,今天我就从iOS app开发的角度讲一些BLE 4.0的使用。
Haley_Wong
2018/08/22
1.9K0
iOS CoreBluetooth 的使用讲解概念分析代码实战
IOS开发之自动布局显示网络请求内容
  在上一篇博客中详细的介绍了IOS开发中的相对布局和绝对布局,随着手机屏幕尺寸的改变,在App开发中为了适应不同尺寸的手机屏幕,用自动布局来完成我们想要实现的功能和效果显得尤为重要。本人更喜欢使用相对布局。在下面要学习的例子中暂且先用我们的StoryBoard来设置我们组件的约束,以后会在代码中给我们的元素新建约束。iPhone4,5和将要发布的iPhone6的屏幕的大小都不一样,所以屏幕的适配是我们搞App开发必须要考虑的问题。   我们要完成一个什么例子呢,先上两张程序运行最终的结果图,之后看着图提出
lizelu
2018/01/11
8170
IOS开发之自动布局显示网络请求内容
iOS如何优雅的处理“回调地狱Callback hell”(一)——使用PromiseKit
最近看了一些Swift关于封装异步操作过程的文章,比如RxSwift,RAC等等,因为回调地狱我自己也写过,很有感触,于是就翻出了Promise来研究学习一下。现将自己的一些收获分享一下,有错误欢迎大家多多指教。
一缕殇流化隐半边冰霜
2018/08/30
3.9K0
iOS如何优雅的处理“回调地狱Callback hell”(一)——使用PromiseKit
一文学会iOS蓝牙开发
最近做APP对接蓝牙设备开发,这里分享一下iOS对接蓝牙设备中需要注意的东西,大致包含下面这些方面:
莫空9081
2023/12/27
2.3K0
iOS:AVCaptureSession 通过摄像头获取某一帧的画面
1.配置plist 2.上代码 // // ViewController.m // newface // // Created by xc on 2018/8/27. // Copyright
菜菜不吃蔡
2018/09/19
3K0
iOS:AVCaptureSession 通过摄像头获取某一帧的画面
IOS开发之自定义Button(集成三种回调模式)
  前面在做东西的时候都用到了storyboard,在今天的代码中就纯手写代码自己用封装个Button。这个Button继承于UIView类,在封装的时候用上啦OC中的三种回调模式:目标动作回调,委托回调,Block回调。具体的内容请参考之前的博客:“Objective-C中的Block回调模式”,“Target-Action回调模式”,“Objective-C中的委托(代理)模式”。在接下来要封装的button中将要用到上面的知识点。之前在做新浪微博中的Cell的时候用到了Block回调来确定是那个Cel
lizelu
2018/01/11
1.3K0
IOS开发之自定义Button(集成三种回调模式)
浅谈 iOS NSNotification
NSNotificationCenter 就相当于一个广播站,使用 [NSNotificationCenter defaultCenter] 来获取,NSNotificationCenter 实际上是 iOS 程序内部之间的一种消息广播机制,主要为了解决应用程序内部不同对象之间解耦而设计。 NSNotificationCenter 是整个通知机制的关键所在,它管理着监听者的注册和注销,通知的发送和接收。NSNotificationCenter 维护着一个通知的分发表,把所有通知发送者发送的通知,转发给对应的监听者们。每一个 iOS 程序都有一个唯一的通知中心,不必自己去创建一个,它是一个单例,通过 [NSNotificationCenter defaultCenter] 方法获取。 NSNotificationCenter 是基于观察者模式设计的,不能跨应用程序进程通信,当 NSNotificationCenter 接收到消息之后会根据内部的消息转发表,将消息发送给订阅者;它可以向应用任何地方发送和接收通知。 在 NSNotificationCenter 注册观察者,发送者使用通知中心广播时,以 NSNotification 的 name 和 object 来确定需要发送给哪个观察者。为保证观察者能接收到通知,所以应先向通知中心注册观察者,接着再发送通知这样才能在通知中心调度表中查找到相应观察者进行通知。
s_在路上
2018/09/30
1.4K0
iOS开发之ExternalAccessory框架的应用
ExternalAccessory框架用来对外设进行管理,iOS外设通常是通过MFI认证的外部设备,可以通过蓝牙进行连接,也可以使用lighting端口进行连接。
珲少
2019/06/27
1.9K0
iOS代理,通知,block的用法及不同
在开发过程中,总是遇到不同页面之间传参问题,代理,通知,block 都可以实现这种简单功能,但是有时候都是根据自己的熟悉程度选择使用的方法,并没有深度的认识之间的用法和不同,在此系统的整理下。
honey缘木鱼
2019/04/25
1.8K0
iOS代理,通知,block的用法及不同
推荐阅读
相关推荐
iOS简易蓝牙对战五子棋游戏设计思路之一——核心蓝牙通讯类的设计
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验