当前位置: 首页 > news >正文

Objective-C 学习笔记 | 回调

Objective-C 学习笔记 | 回调

  • Objective-C 学习笔记 | 回调
    • 运行循环
    • 目标-动作对(target-action)
    • 辅助对象
    • 通知
    • 回调与对象所有权
    • 深入学习:选择器的工作机制

参考书:《Objective-C 编程(第2版)》

Objective-C 学习笔记 | 回调

回调就是将一段代码和一个事件绑定起来,当事件发生时,就会执行那段代码。

在 Objective-C 中有 4 种方式来实现回调:

请添加图片描述

本文章将介绍如何通过前三种途径来实现回调,以及怎样根据情况选择合适的途径。

运行循环

NSRunLoop 类专门负责等待事件的发生。NSRunLoop 实例会在特定的事件发生时触发回调。

目标-动作对(target-action)

计时器使用的是目标-动作对机制。创建计时器时,要设定延迟、目标和动作。在指定延迟时间后,计时器会向设定的目标发送指定的消息。

创建一个程序,每隔 2 秒,NSTimer 对象会向其目标(BNRLogger)发送指定的动作消息。如下图所示:

请添加图片描述

main.m:

#import <Foundation/Foundation.h>
#import "BNRLogger.h"int main(int argc, const char * argv[]) {@autoreleasepool {BNRLogger *logger = [[BNRLogger alloc] init];// __unused 修饰符,消除编译器警告__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0target:logger // logger 是 timer 的目标selector:@selector(updateLastTime:) // 传递动作消息的名称userInfo:nilrepeats:YES];[[NSRunLoop currentRunLoop] run];}return 0;
}

BNRLogger.h:

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface BNRLogger : NSObject@property (nonatomic) NSDate *lastTime;- (NSString *)lastTimeString;
// 动作方法
- (void)updateLastTime:(NSTimer *)timer;@endNS_ASSUME_NONNULL_END

BNRLogger.m:

#import "BNRLogger.h"@implementation BNRLogger- (NSString *)lastTimeString
{// static 让所有的 BNRLogger 实例共享一个 NSDateFormatterstatic NSDateFormatter *dateFormatter = nil;if (!dateFormatter){dateFormatter = [[NSDateFormatter alloc] init];[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];[dateFormatter setDateStyle:NSDateFormatterMediumStyle];NSLog(@"created dateFormatter");}return [dateFormatter stringFromDate:self.lastTime];
}// 动作方法总有一个实参,它是传入发送动作消息的对象
- (void)updateLastTime:(NSTimer *)timer
{NSDate *now = [NSDate date];[self setLastTime:now];NSLog(@"Just set time to %@", self.lastTimeString);
}@end

运行程序,每隔 2 秒输出当前的日期和时间。

当要向一个对象发送一个回调时,使用目标-动作对。

辅助对象

我们使用一个异步的模式来使用 NSURLConnection,在异步模式下,NSURLConnection 会多次发送块状的数据,BNRLogger 实例会成为 NSURLConnection 的辅助对象,更确切的说,是委托对象。

请添加图片描述

NSURLConnection 有一套协议,协议是一系列方法声明,辅助对象可以根据协议实现这些方法。在下面的程序中,我们声明 BNRLogger 会实现 NSURLConnectionDelegate 和 NSURLConnectionDataDelegate 这两种协议方法,并实现 3 个回调方法。

main.m:

#import <Foundation/Foundation.h>
#import "BNRLogger.h"int main(int argc, const char * argv[]) {@autoreleasepool {BNRLogger *logger = [[BNRLogger alloc] init];NSURL *url = [NSURL URLWithString:@"http://www.gutenberg.org/cache/epub/205/pg205.txt"];NSURLRequest *request = [NSURLRequest requestWithURL:url];// __unused 修饰符,消除编译器警告__unused NSURLConnection *fetchConn =[[NSURLConnection alloc] initWithRequest:requestdelegate:logger // logger 是 NSURLConnection 的委托对象startImmediately:YES];__unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0target:logger // logger 是 timer 的目标selector:@selector(updateLastTime:) // 传递动作消息的名称userInfo:nilrepeats:YES];[[NSRunLoop currentRunLoop] run];}return 0;
}

BNRLogger.h:

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface BNRLogger : NSObject<NSURLConnectionDelegate, NSURLConnectionDataDelegate> // 声明 BNRLogger 会实现这两种协议方法
{NSMutableData *_incomingData; // 保存接收的数据
}
@property (nonatomic) NSDate *lastTime;- (NSString *)lastTimeString;
// 动作方法
- (void)updateLastTime:(NSTimer *)timer;@endNS_ASSUME_NONNULL_END

BNRLogger.m:

#import "BNRLogger.h"@implementation BNRLogger- (NSString *)lastTimeString
{// static 让所有的 BNRLogger 实例共享一个 NSDateFormatterstatic NSDateFormatter *dateFormatter = nil;if (!dateFormatter){dateFormatter = [[NSDateFormatter alloc] init];[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];[dateFormatter setDateStyle:NSDateFormatterMediumStyle];NSLog(@"created dateFormatter");}return [dateFormatter stringFromDate:self.lastTime];
}// 动作方法总有一个实参,它是传入发送动作消息的对象
- (void)updateLastTime:(NSTimer *)timer
{NSDate *now = [NSDate date];[self setLastTime:now];NSLog(@"Just set time to %@", self.lastTimeString);
}/** 协议方法 */
// 来自 NSURLConnectionDataDelegate 协议
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{ // 收到一定字节的数据后就被调用NSLog(@"received %lu bytes", [data length]);if (!_incomingData){_incomingData = [[NSMutableData alloc] init];}[_incomingData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{ // 最后一部分数据处理完毕后会被调用NSLog(@"Got it all!");NSString *str = [[NSString alloc] initWithData:_incomingData encoding:NSUTF8StringEncoding];_incomingData = nil;NSLog(@"string has %lu characters", [str length]);NSLog(@"The whole string is %@", str);
}// 来自 NSURLConnectionDelegate 协议
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{ // 获取数据失败时会被调用NSLog (@"connection failed %@", [error localizedDescription]);_incomingData = nil;
}@end

运行程序,程序会陆续收到来自 Web 服务器的数据,调用回调方法打印接收到的字节数。最后,当获取数据结束时,委托对象会收到相应的消息,打印接收字符数和完整的数据。

当要向一个对象发送多个回调时,使用符合相应协议的辅助对象。

通知

NSNotificationCenter 类是通知中心,程序中的对象可以通过通知中心将自己注册为观察者。当系统发生变化时,会向通知中心发布特定的通知,然后通知中心会将该通知转发给相应的观察者。

我们将 BNRLogger 实例注册成通知中心的观察者,使之能在系统的时区设置发生变化时能够收到相应的通知,代码如下:

// main.mBNRLogger *logger = [[BNRLogger alloc] init];// 通知[[NSNotificationCenter defaultCenter]addObserver:logger // 将 logger 注册为观察者selector:@selector(zoneChange:)name:NSSystemTimeZoneDidChangeNotification // 通知名object:nil];// BNRLogger.m
- (void)zoneChange:(NSNotification *)note
{ // 该方法将在系统发布 NSSystemTimeZoneDidChangeNotification 通知时被调用NSLog(@"The system time zone has changed");
}

当要触发多个(其他对象中的)回调的对象时,使用通知。

回调与对象所有权

如果一个对象拥有一个指向回调对象的指针,而回调对象也有指针指向该对象,那么就会陷入强引用循环,这两个对象都无法释放。

请添加图片描述

所以在编写回调相关代码时,应注意以下三点。

第一,通知中心不拥有观察者,释放对象的同时要将其移出通知中心:

- (void)dealloc
{[[NSNotificationCenter defaultCenter] removeObserver:self];
}

第二,对象不拥有委托对象或数据源对象。如果一个对象是另一个对象的委托对象或数据源对象,那么释放该对象时应该取消所有的关联:

- (void)dealloc
{[windowThatBossesMeAround setDelegate:nil];[tableViewThatBegsForData setDataSource:nil];
}

第二,对象不拥有目标。如果一个对象是另一个对象的目标,那么释放该对象时应该将相应的目标指针置空:

- (void)dealloc
{[buttonThatKeepsSendingMeMessages setTarget:nil]:
}

深入学习:选择器的工作机制

当某个对象收到消息时,会向该对象的类进行查询,检查是否有与消息名称相匹配的方法。该查询过程会沿着继承层次结构向上,直到某个类回应 “我有与消息名称相匹配的方法”。

请添加图片描述

实际上,为了加快查询速度,编译器会为每个方法附上唯一的数字,查询时按数字而不是方法名,这个数字被称为选择器(selector)。

请添加图片描述

通过编译指令 @selector,可以得到与方法名对应的选择器。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 3038. 相同分数的最大操作数目 I(Rust模拟击败100%Rust用户)
  • 解决Spark流处理产生的小文件问题
  • C语言考试内容
  • LangChain + ChatGLM 实现本地知识库问答
  • 【C++】函数模板和类模版
  • 《精通ChatGPT:从入门到大师的Prompt指南》附录C:专业术语表
  • SpringBoot+Vue实现前后端分离基本的环境搭建
  • 王学岗鸿蒙开发(北向)——————(七、八)ArkUi的各种装饰器
  • Kafka 架构
  • 快速排序(Quick_Sort)
  • python一点通: Async异步函数很好,但是如何有效执行阻塞任务?
  • chatgpt 推荐的一些关于提高认知的书,我先存一下
  • OJ3829大石头的搬运工
  • 定时器更新界面,线程报错
  • Hack The Box(黑客盒子)Redeemer篇
  • css布局,左右固定中间自适应实现
  • egg(89)--egg之redis的发布和订阅
  • es的写入过程
  • gulp 教程
  • Java 最常见的 200+ 面试题:面试必备
  • Mithril.js 入门介绍
  • Mysql优化
  • WordPress 获取当前文章下的所有附件/获取指定ID文章的附件(图片、文件、视频)...
  • 高性能JavaScript阅读简记(三)
  • 给初学者:JavaScript 中数组操作注意点
  • 老板让我十分钟上手nx-admin
  • 前端性能优化--懒加载和预加载
  • 世界上最简单的无等待算法(getAndIncrement)
  • 小程序01:wepy框架整合iview webapp UI
  • 一份游戏开发学习路线
  • 阿里云API、SDK和CLI应用实践方案
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • ​​​【收录 Hello 算法】10.4 哈希优化策略
  • ​学习一下,什么是预包装食品?​
  • # dbt source dbt source freshness命令详解
  • ###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目
  • #1015 : KMP算法
  • #快捷键# 大学四年我常用的软件快捷键大全,教你成为电脑高手!!
  • (1)svelte 教程:hello world
  • (30)数组元素和与数字和的绝对差
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • .NET Framework 和 .NET Core 在默认情况下垃圾回收(GC)机制的不同(局部变量部分)
  • .Net Remoting(分离服务程序实现) - Part.3
  • .NET开源纪元:穿越封闭的迷雾,拥抱开放的星辰
  • .net项目IIS、VS 附加进程调试
  • /etc/sudoers (root权限管理)
  • ::
  • ??在JSP中,java和JavaScript如何交互?
  • @Not - Empty-Null-Blank
  • @RequestBody与@ModelAttribute
  • @vue/cli 3.x+引入jQuery
  • [ JavaScript ] JSON方法
  • [ vulhub漏洞复现篇 ] JBOSS AS 4.x以下反序列化远程代码执行漏洞CVE-2017-7504