Objective-C 学习笔记 | 回调

news2025/1/13 7:55:23

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.0
                            target:logger // logger 是 timer 的目标
                            selector:@selector(updateLastTime:) // 传递动作消息的名称
                            userInfo:nil
                            repeats: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;

@end

NS_ASSUME_NONNULL_END

BNRLogger.m:

#import "BNRLogger.h"

@implementation BNRLogger

- (NSString *)lastTimeString
{
    // static 让所有的 BNRLogger 实例共享一个 NSDateFormatter
    static 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:request
                                        delegate:logger // logger 是 NSURLConnection 的委托对象
                                    startImmediately:YES];
        __unused NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0
                            target:logger // logger 是 timer 的目标
                            selector:@selector(updateLastTime:) // 传递动作消息的名称
                            userInfo:nil
                            repeats: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;

@end

NS_ASSUME_NONNULL_END

BNRLogger.m:

#import "BNRLogger.h"

@implementation BNRLogger

- (NSString *)lastTimeString
{
    // static 让所有的 BNRLogger 实例共享一个 NSDateFormatter
    static 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.m
				BNRLogger *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,可以得到与方法名对应的选择器。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1810930.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

git服务器gitblit安装

1、下载 Gitblit 2、下载完后解压&#xff1a; 3、配制&#xff1a; 保存&#xff0c;退出编辑。 4、运行cmd&#xff0c;启用gitblit。 5、根据运行后的提示&#xff0c;也就是我们之间设置的port9990打开&#xff1a; 输入admin,admin就可以登录&#xff0c;这个账号密码&a…

LaTex中`\texorpdfstring`命令的使用方法

LaTex中\texorpdfstring命令的使用方法 \texorpdfstring命令 \texorpdfstring命令是hyperref包提供的一种替换宏&#xff0c;常用于标题中的公式显示。 命令后跟随两个参数&#xff1a; \texorpdfstring{TeXstring}{PDFstring}第一个参数TeXstring在正文标题中显示&#xf…

MySQL存储引擎详述:InnoDB为何胜出?

MySQL作为当前最流行的开源关系型数据库之一,其强大的功能和良好的性能使其广泛应用于各种规模的应用系统中。其中,存储引擎的设计理念是MySQL数据库灵活高效的关键所在。 一、什么是存储引擎 存储引擎是MySQL架构的重要组成部分,负责MySQL中数据的存储和提供了视图,存储过程等…

半导体光电子学最后总结(3)光子晶体

Matrix theory 波传输矩阵 (Wave-Transfer Matrix) 散射矩阵 (Scattering Matrix) 光在均匀介质中的传播公式矩阵化 Relation between Scattering Matrix and Wave-Transfer Matrix 级联系统的投射/反射系数&#xff1a;艾里公式 (Airy Formulas) 无损对称系统 斜入射波的传输…

Golang的context

目录 context的基本使用 为什么需要context Context interface 标准 error emptyCtx cancelCtx Deadline 方法 Done 方法 Err 方法 Value 方法 context.WithCancel() newCancelCtx WithCancel中propagateCancel cancel timerCtx valueCtx context的基本使用…

宽睿数字平台兼容TDengine 等多种数据库,提供行情解决方案

小T导读&#xff1a;最近&#xff0c;涛思数据与宽睿金融宣布了一项重要合作。在此之前&#xff0c;宽睿金融对 TDengine 进行了性能测试&#xff0c;并根据测试报告的结果&#xff0c;决定将 TDengine 接入宽睿数字平台&#xff0c;以提升高密度行情处理效率。本文将详细介绍此…

智慧监狱大数据整体解决方案(51页PPT)

方案介绍&#xff1a; 智慧监狱大数据整体解决方案通过集成先进的信息技术和大数据技术&#xff0c;实现对监狱管理的全面升级和智能化改造。该方案不仅提高了监狱管理的安全性和效率&#xff0c;还提升了监狱的智能化水平&#xff0c;为监狱的可持续发展提供了有力支持。 部…

【Vue】获取模块内的actions方法

目标&#xff1a; 掌握模块中 action 的调用语法 (同理 - 直接类比 mutation 即可) 注意&#xff1a; 默认模块中的 mutation 和 actions 会被挂载到全局&#xff0c;需要开启命名空间&#xff0c;才会挂载到子模块。 调用语法&#xff1a; 直接通过 store 调用 $store.di…

Flutter - Material3适配

demo 地址: https://github.com/iotjin/jh_flutter_demo 代码不定时更新&#xff0c;请前往github查看最新代码 Flutter - Material3适配 对比图具体实现一些组件的变化 代码实现Material2的ThemeDataMaterial3的ThemeData Material3适配官方文档 flutter SDK升级到3.16.0之后 …

ES启动失败原因记录

一、JDK不兼容&#xff1a; es和jdk是一个强依赖的关系&#xff0c;所以当我们在新版本的ElasticSearch压缩包中包含有自带的jdk&#xff0c;但是当我们的Linux中已经安装了jdk之后&#xff0c;就会发现启动es的时候优先去找的是Linux中已经装好的jdk&#xff0c;此时如果jdk的…

35、matlab设置字体、查看工具包版本、窗口默认布局和程序发布

1、matlab设置字体 1&#xff09;找到预设并点击预设 2&#xff09;设置流程&#xff1a;字体——>自定义——>编辑器——>选择字体及格式——>确定 如图序号所示 2、matlab查看工具包版本&#xff1a;ver命令 1&#xff09;命令行窗口输入命令 即可查看工具包…

反射...

一、反射的定义 二、获取Class对象三种方式 全类名&#xff1a;包名类名。 public class test {public static void main(String [] args) throws ClassNotFoundException {//第一种方式Class class1Class.forName("test02.Student");//第二种方法Class class2Stud…

03-240605-Spark笔记

03-240605 1. 行动算子-1 reduce 聚合 格式: def reduce(f: (T, T) > T): T 例子&#xff1a; val sparkConf new SparkConf().setMaster("local[*]").setAppName("Operator")val sc new SparkContext(sparkConf) ​val rdd sc.makeRDD(List(1…

最好用的搜题软件大学?8个公众号和软件推荐清单! #知识分享#知识分享#经验分享

今天&#xff0c;我将分享一些受欢迎的、被大学生广泛使用的日常学习工具&#xff0c;希望能给你的学习生活带来一些便利和启发。 1.彩虹搜题 这个是公众号 一款专供大学生使用的搜题神器专注于大学生校内学习和考研/公考等能力提升 下方附上一些测试的试题及答案 1、行大量…

通过 CartPole 游戏详细说明 PPO 优化过程

CartPole 介绍 在一个光滑的轨道上有个推车&#xff0c;杆子垂直微置在推车上&#xff0c;随时有倒的风险。系统每次对推车施加向左或者向右的力&#xff0c;但我们的目标是让杆子保持直立。杆子保持直立的每个时间单位都会获得 1 的奖励。但是当杆子与垂直方向成 15 度以上的…

Vue3【十七】props的作用和组件之间的传值限定类型和默认值

Vue3【十七】props的作用和组件之间的传值限定类型和默认值 Vue3【十七】props的作用和组件之间的传值限定类型和默认值 父组件传值给子组件 多个值传递 传值限定类型和 默认值 实例截图 目录结构 代码 person.vue <template><div class"person"><p…

硬件开发笔记(十七):RK3568底板电路串口、485、usb原理图详解

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139589308 红胖子网络科技博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬…

格式工厂 v5 解锁版 (免费多媒体文件转换工具)

前言 格式工厂是免费多功能的多媒体文件转换工具&#xff0c;轻松转换一切你想要的格式。利器在手&#xff0c;转换不愁&#xff01;支持几乎所有类型格式的相互转换&#xff0c;各种视频、音频、图片、PDF文档等格式&#xff0c;转换视频过程中&#xff0c;可以修复损坏的文件…

Cesium离线部署影像+地形:从0到1

Cesium加载本地影像地形 本教程记录的是小白从0-1搭建本地cesium服务的过程&#xff0c;踩的各种坑通过查找资料都一一填补&#xff0c;最终达到的效果是在本地上能够跑官网飞机航线的例子。效果如下&#xff1a; 主要流程如下&#xff1a; 1、下载离线地图和地形2、nginx部署…

工业机器人远程运维,增强智慧工厂运营管理

1、需求背景 随着工业自动化技术的普及和工业机器人应用的增加&#xff0c;制造业对于生产线稳定性和效率的要求不断提高。然而&#xff0c;传统的现场监控方式存在着地理位置限制、实时监控难度大以及诊断能力有限等问题&#xff0c;迫切需要一种更具灵活性和效率的监控方式。…