专栏首页Antony iOS DevelopmentiOS中的「回调(callback)」

iOS中的「回调(callback)」

本文主要参考:《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。

[[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/目标-动作对

先看代码:

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

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

再看一个NSTimer对象的代码:

// 一个自定义类对象
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文件如下:

#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文件如下:

#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文件):

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

大概会有这两个类

#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文件中发送通告

#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方法内「监测」这个通告:

- (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常量:
    // Block常量 
    ^{
        NSLog(@"我是一个Block常量。^是辨识我身份的标志。记得最后加分号哦,因为我就是一个常量,就像数字「5;」一样");
    };
  • 带实参、会返回值的Block:
    // 有实参,有返回值的Block
    ^(double dividend, double divisor) {
        NSLog(@"我是一个有参数、有返回值的Block");
        double quotient = dividend / divisor;
        return quotient;
    };
  • 声明Block变量:
    // 声明一个Block变量(无返回值;有参数), 
   void (^YourBlockName)(id, NSString *, NSUInteger, BOOL *);
   
   // 或
   void (^YourBlockName)(id obj, NSString *yourString, NSUInteger deviceCount, BOOL *stop);
  • 给Block变量赋值:
    // 给你的Block变量赋值
    // 等号左边是一个Block变量,等号右边是一个Block常量,将常量赋值给变量
    YourBlockName = ^(id array, NSString *theString, NSUInteger count, BOOL *stop){
       // Do something what you want.
    };
  • Block的声明、赋值一起进行:
    // 声明、赋值一起
    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命名为一种新的数据类型(最常用这种形式)。
    // 在文件顶部(#import之下)用typedef将Block重新定义为一种新的数据类型
    typedef void(^YourBlockName)(id, NSString *, NSUInteger, BOOL *);
    
    
    // 利用新的数据类型,声明一个Block变量
    @property (nonatomic, strong) YourBlockName yourBlock;
    
    // 再对这个变量进行必要的操作(赋值)

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

利用Block实现回调

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

在MyCnetralManager.h文件

#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文件

// 成功连接一个蓝牙设备的回调(官方框架)
- (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方法内进行回调:

- (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文件

#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文件

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

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

- (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)」的一些入门级分享。如有谬误,请斧正,谢谢。

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

推荐阅读

  • 如何将设计思维应用到精益初创公司的软件开发

    我们所说的设计思维,是指由 IDEO 公司的 Tim Brown 提出,并且正在改变全世界组织的设计思维,简称 DT。(译者注:IDDO,当代最具影响力的设计公司之一)

    Aceyclee
    Serverless无服务器云函数
  • InnoDB 事务加锁分析

    一般大家对数据库事务的了解可能停留在事务的ACID特性以及事务4种不同的隔离级别层面上,而对于事务 4 种不同隔离级别如何实现了解相对较少。

    2020labs小助手
    MySQLSQL数据库MVCMVCC
  • FutureTask 核心源码解析

    研究源码,一般我们都从整体以及实例先入手,再研究细节,不至于一开始就“深陷其中而"当局者迷".

    JavaEdge
    HTTPJava
  • 200行代码落地人脸识别开锁应用

    2019年国庆,帮朋友实现了一个人脸识别进行开锁的功能,用在他的真人实景游戏业务中。几个月来运行稳定,体验良好,借着这个春节宅家的时间,整理一下这个应用的实现过程。

    高树磊
    人脸识别图像处理
  • 滑动验证码攻防对抗

        在业务安全领域,滑动验证码已经是国内继,传统字符型验证码之后的标配。众所周知,打码平台和机器学习这两种绕过验证码的方式,已经是攻击者很主流的思路,不再阐述。冷渗透介绍的是一个冷门的绕过思路和防御方案。这些积累,均来自于实战之中,希望有用。

    周俊辉
    HTTP网络安全安全网站
  • 程序员进阶必读,万字总结Mysql优化精华篇

    price decimal(8,2)有2位小数的定点数,定点数支持很大的数(甚至是超过int,bigint存储范围的数)

    程序员内点事
    全文检索缓存SQL数据库Python
  • 运维转型 | 运维人不再只是“救火英雄”

    各行各业都开启了数字化转型的进程,运维团队在这种时代的浪潮中又该何去何从?我在帮助一些企业落地了运维技术平台之后,开始反思这个问题,并将所思所想整理成本篇文章。

    嘉为科技
    企业运维自动化云计算
  • WEB开发常见的安全漏洞和解决思路

    SQL注入时web开发中最常见也是危害性最大的安全漏洞,SQL注入攻击可能会导致 服务器故障,数据泄漏,数据被恶意删除等等严重后果。

    windwei
    SQLHTTPhttps安全漏洞PHP
  • 详解Winograd变换矩阵生成原理

    文本首发知乎:https://zhuanlan.zhihu.com/p/87516875

    Ldpe2G
    编程算法
  • Linux网络性能优化相关策略

    1. rx-checksumming:校验接收报文的checksum。

    glinuxer
    Linux

扫码关注云+社区

领取腾讯云代金券