【iOS】继承链

文章目录

  • 前言
  • 什么是继承链
  • OC中的根类
    • 关于NSProxy
  • 关键作用
    • 1.方法查找与动态绑定
    • 2. 消息转发
    • 3. **类型判断与多态**
  • 继承链的底层实现
    • 元类的继承链
  • 总结

前言

  在objective-c中,继承链是类与类之间通过父类(Superclass)关系形成的一层层继承结构,在我们之前的学习中,我们发现无论是方法的动态查找、消息的传递还是代码的复用,都需要用到继承链,今天我们就来系统地了解一下继承链。

什么是继承链

  每个OC类(除根类外)都有一个直接父类,通过这种层级关系形成一条从子类到根类的单向链。当向一个对象发送消息(调用方法)时,OC运行时会沿着这条链自底向上查找对应的方法实现:

  • 若当前类实现了该方法,直接调用;
  • 若未实现,则跳转到父类继续查找;
  • 直到根类仍无实现时,触发「消息转发」机制(否则程序崩溃)。

OC中的根类

  OC 中几乎所有类的最终父类都是 NSObject(少数特殊类如 NSProxy可能作为独立根类)。NSObject定义了OC对象的基础行为(如内存管理、反射、消息传递等)。

eg:

// 自定义类继承链示例
@interface MyBaseClass : NSObject
@end@interface MySubClass : MyBaseClass
@end// MySubClass 的继承链:MySubClass → MyBaseClass → NSObject → nil(根类无父类)

关于NSProxy

  在 Objective-C(OC)中,NSProxy是一个特殊的抽象基类,与 NSObject并列作为 OC 类体系的两大根类(但 NSObject是绝大多数类的最终父类,而 NSProxy更专注于消息转发场景)。它的核心设计目标是==轻量级消息转发,常用于实现代理模式(Proxy Pattern)、动态消息拦截或替代复杂的继承结构。

NSProxy的底层源码如下:

/*	NSProxy.hCopyright (c) 1994-2019, Apple Inc. All rights reserved.
*/#import <Foundation/NSObject.h>@class NSMethodSignature, NSInvocation;NS_HEADER_AUDIT_BEGIN(nullability, sendability)NS_ROOT_CLASS
@interface NSProxy <NSObject> {__ptrauth_objc_isa_pointer Class	isa;
}+ (id)alloc;
+ (id)allocWithZone:(nullable NSZone *)zone NS_AUTOMATED_REFCOUNT_UNAVAILABLE;
+ (Class)class;- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel NS_SWIFT_UNAVAILABLE("NSInvocation and related APIs not available");
- (void)dealloc;
- (void)finalize;
@property (readonly, copy) NSString *description;
@property (readonly, copy) NSString *debugDescription;
+ (BOOL)respondsToSelector:(SEL)aSelector;- (BOOL)allowsWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);
- (BOOL)retainWeakReference API_UNAVAILABLE(macos, ios, watchos, tvos);// - (id)forwardingTargetForSelector:(SEL)aSelector;@endNS_HEADER_AUDIT_END(nullability, sendability)

通过上述代码,我们可以发现NSProxy是一个实现了NSObject协议的根类。

在这里插入图片描述
由此我们知道:

  • NSProxy 是一个抽象类,跟 NSObject 一样的基类,都遵守NSObject协议

  • NSProxy是一个抽象类,必须继承实例化其子类才能使用。

  • NSProxy从类名来看是代理类专门负责代理对象转发消息的。相比NSObject类来说NSProxy更轻量级,OC是单继承的语言,但通过NSProxy可以帮助Objective-C间接的实现多重继承的功能(“伪多继承”)。

在NSProxy源码中,运用消息转发机制的核心方法有两个:

在这里插入图片描述

通过methodSignatureForSelector:方法获取一个NSMethodSignature类型的对象,调用forwardInvocation:方法。该方法传入一个封装了NSMethodSignatureNSInvocation对象。然后该对象通过invakeWithTarget:方法将消息转发给其它对象。

顺便回顾一下消息转发:

OC的消息转发流程:当向一个对象发送消息,而该对象没有实现对应的方法时,运行时会触发消息转发机制。这个过程分为几个步骤:动态方法解析、备用接收者转发,最后是完整消息转发。这两个方法主要参与最后一步,即完整消息转发阶段。

methodSignatureForSelector:的作用是为指定的选择器生成方法签名。方法签名包含了方法的参数类型、返回类型等信息,是构造NSInvocation对象所必需的。如果这个方法返回nil,运行时会认为该消息无法处理,进而触发doesNotRecognizeSelector:导致崩溃。

然后是forwardInvocation:,它的作用是处理那些无法被当前对象或其继承链中其他类处理的方法调用。在这个方法里,我们可以自定义如何处理这些未被识别的消息,比如将消息转发给其他对象,或者执行一些额外的逻辑。

当消息转发到NSProxy或自定义类时,首先会调用methodSignatureForSelector:来获取方法签名,如果返回有效的签名,才会调用forwardInvocation:来处理消息。如果methodSignatureForSelector:返回nil,则不会调用forwardInvocation:,直接崩溃。

关键作用

1.方法查找与动态绑定

OC 是动态语言,方法的调用(消息发送)发生在运行时。继承链的存在使得对象可以「继承」父类的方法,无需重复实现。例如:

MySubClass *obj = [[MySubClass alloc] init];
NSString *desc = [obj description]; // 实际调用 NSObject 的 description 方法

即使 MySubClass未重写 description,运行时仍会沿继承链找到 NSObject的实现。

2. 消息转发

若继承链中所有类都未实现目标方法,OC 会尝试通过消息转发机制处理,避免直接崩溃。典型流程如下:

  1. 动态方法解析:调用 +resolveInstanceMethod:(实例方法)或 +resolveClassMethod:(类方法)尝试动态添加方法实现;
  2. 备用接收者转发:调用 -forwardingTargetForSelector:将消息转发给其他对象;
  3. 完整消息转发:调用 -methodSignatureForSelector:生成方法签名,再通过 -forwardInvocation:转发(可自定义处理逻辑)。

3. 类型判断与多态

通过继承链可以实现多态(Polymorphism)。例如,isKindOfClass:isMemberOfClass:方法通过检查对象继承链判断类型:

id obj = [[MySubClass alloc] init];
BOOL isMyBase = [obj isKindOfClass:[MyBaseClass class]]; // YES(继承链包含 MyBaseClass)
BOOL isNSObject = [obj isKindOfClass:[NSObject class]];   // YES(继承链最终到 NSObject)

继承链的底层实现

OC 类的底层通过 objc_class结构体表示,其中 superclass字段指向父类。通过运行时函数可手动操作继承链:

  • class_getSuperclass(Class cls):获取类的直接父类;
  • class_getClass(Class cls):获取类对应的元类(Meta Class);
  • object_getClass(id obj):获取对象的类(等价于 [obj class])。

元类的继承链

OC 中类(Class)本身也是对象,其类型为元类(Meta Class)。元类的继承链与普通类不同:

  • 普通类的 isa指针指向其元类;
  • 元类的 isa指针指向根元类(通常是 NSObject元类的父类);
  • 根元类的 isa指针指向自身(形成闭环)。

例如,NSObject类的元类继承链为:NSObject_Meta → Root_Meta(自身)

总结

  1. 在编程设计时,我们要避免继承链过长,过深的继承链会增加方法查找时间,降低性能。推荐优先使用组合(Composition)而非继承。
  2. 在继承中,根类(如 NSObject)需自行实现部分基础方法(如 allocinit),否则其子类无法正常使用。
  3. 类方法存储在元类中,其继承链为「元类 → 父元类 → … → 根元类」;实例方法的继承链为「类 → 父类 → … → 根类」。

在面向对象编程(OOP)中,组合(Composition) 是一种通过「持有其他对象实例」来实现功能复用的设计模式,与「继承(Inheritance)」共同构成代码复用的两大核心手段。它的核心思想是「整体-部分」(Whole-Part)关系,即一个对象(整体)由多个其他对象(部分)组成,通过调用这些「部分」对象的方法来实现自身功能。

在这里插入图片描述

在面向对象编程(OOP)中,组合(Composition) 是一种通过「持有其他对象实例」来实现功能复用的设计模式,与「继承(Inheritance)」共同构成代码复用的两大核心手段。它的核心思想是「整体-部分」(Whole-Part)关系,即一个对象(整体)由多个其他对象(部分)组成,通过调用这些「部分」对象的方法来实现自身功能。

组合的本质是「has-a」关系(拥有关系),而非继承的「is-a」关系(是一种关系)。例如:

一辆 Car(整体)「拥有」一个 Engine(部分),因此 Car通过持有 Engine实例来调用 startstop等方法;

一个 Computer(整体)「拥有」一个 CPU和一个 Memory(部分),通过调用它们的计算和存储方法完成功能。

与继承相比,组合不要求整体类与部分类存在继承层级,而是通过动态的消息传递(调用部分对象的方法)实现功能复用

维度组合(Composition)继承(Inheritance)
关系类型「has-a」(整体拥有部分)「is-a」(子类是一种父类)
耦合度低:整体与部分通过接口(协议)交互,解耦性强高:子类依赖父类的实现细节(如私有方法、属性)
灵活性高:运行时可动态替换部分对象(如依赖注入)低:继承关系编译时确定,无法动态修改
复用粒度细粒度:仅复用需要的部分功能粗粒度:必须继承整个父类的功能(包括不需要的)
设计复杂度需定义清晰的接口(协议),规范部分对象的行为需维护继承链,可能导致「脆弱基类」问题

组合的具体使用:

在 Objective-C 中,组合通过「属性持有其他对象实例」实现。

1.定义部分对象(Component)

首先定义需要被组合的功能模块(部分),通常通过协议(Protocol)规范其行为,以降低耦合。

// 定义 Engine 协议(部分对象的行为规范)
@protocol Engine <NSObject>
- (void)start;
- (void)stop;
- (NSString *)engineInfo;
@end// 具体实现:GasEngine(汽油发动机)
@interface GasEngine : NSObject <Engine>
@end@implementation GasEngine
- (void)start { NSLog(@"汽油发动机启动..."); }
- (void)stop { NSLog(@"汽油发动机停止..."); }
- (NSString *)engineInfo { return @"Gas Engine v1.0"; }
@end

2. 定义整体对象(Composite)

整体对象通过属性持有部分对象的实例,并在需要时调用其方法。

// 整体对象:Car(汽车)
@interface Car : NSObject
@property (nonatomic, strong) id<Engine> engine; // 持有符合 Engine 协议的对象
- (instancetype)initWithEngine:(id<Engine>)engine;
- (void)startCar;
- (void)stopCar;
@end@implementation Car
- (instancetype)initWithEngine:(id<Engine>)engine {if (self = [super init]) {_engine = engine; // 注入部分对象(依赖注入)}return self;
}// 调用部分对象的方法实现自身功能
- (void)startCar {[self.engine start];NSLog(@"汽车启动,使用发动机:%@", [self.engine engineInfo]);
}- (void)stopCar {[self.engine stop];NSLog(@"汽车停止");
}
@end

3. 使用组合

通过创建部分对象的实例,并注入到整体对象中,即可完成功能复用。

// 创建部分对象(GasEngine)
id<Engine> engine = [[GasEngine alloc] init];// 创建整体对象(Car)并注入部分对象
Car *car = [[Car alloc] initWithEngine:engine];// 调用整体对象的方法(内部调用部分对象的方法)
[car startCar]; 
// 输出:
// 汽油发动机启动...
// 汽车启动,使用发动机:Gas Engine v1.0

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

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

相关文章

论文阅读:Instruct BLIP (2023.5)

文章目录InstructBLIP&#xff1a;迈向通用视觉语言模型的指令微调研究总结一、研究背景与目标二、核心方法数据构建与划分模型架构训练策略三、实验结果零样本性能消融实验下游任务微调定性分析可视化结果展示四、结论与贡献InstructBLIP&#xff1a;迈向通用视觉语言模型的指…

Elasticsearch+Logstash+Filebeat+Kibana部署【7.1.1版本】

目录 一、准备阶段 二、实验阶段 1.配置kibana主机 2.配置elasticsearch主机 3.配置logstash主机 4.配置/etc/filebeat/filebeat.yml 三、验证 1.开启Filebeat 2.在logstash查看 3.浏览器访问kibana 一、准备阶段 1.准备四台主机kibana、es、logstash、filebeat 2.在…

Vue开发前端报错:‘vue-cli-service‘ 不是内部或外部命令解决方案

1.Bug: 最近调试一个现有的Vue前端代码&#xff0c;发现如下错误&#xff1a; vue-cli-service’ 不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。 2.Bug原因&#xff1a; 导入的工程缺少依赖包&#xff1a;即缺少node_modules文件夹 3.解决方案&#xff1…

AI生态,钉钉再「出招」

如果说之前钉钉的AI生态加持更多的围绕资源和商业的底层助力&#xff0c;那么如今这种加持则是向更深层次进化&#xff0c;即真正的AI模型训练能力加持&#xff0c;为垂类大模型创业者提供全方位的助力&#xff0c;提高创业成功率和模型产品商业化确定性。作者|皮爷出品|产业家…

XSS GAME靶场

要求用户不参与&#xff0c;触发alert(1337) 目录 Ma Spaghet! Jefff Ugandan Knuckles Ricardo Milos Ah Thats Hawt Ligma Mafia Ok, Boomer Exmaple 1 - Create Example 2 - Overwrite Example 3 - Overwrite2 toString Ma Spaghet! <h2 id"spaghet&qu…

Unity学习笔记(五)——3DRPG游戏(2)

添加更多的敌人 编辑EnemyController&#xff0c;解决报错导致敌人无法注册观察者模式&#xff0c;从而无法执行敌人庆祝动画 using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public enum EnemyStatus { GUARD,PATROL…

2025测绘程序设计国赛实战:一轮终章 | 单向后方交会C#实现

前言本文是小编对六道国赛试题中的最后一个试题&#xff0c;单向后方交会的一篇学习日志。本文的整体架构&#xff0c;依旧首先拿训练数据跟大家介绍本题涉及到的数据的属性含义&#xff0c;涉及到算法的原理、执行流程和终极目的。然后附上小编用C#来实现的程序&#xff0c;从…

基于Echarts的气象数据可视化网站系统的设计与实现(Python版)

本系统旨在构建一个基于Echarts的气象数据可视化系统&#xff0c;本系统能够从中国天气网爬取实时天气数据&#xff0c;并进行存储、分析和可视化展示。用户可以通过网页界面查看不同地区的天气情况&#xff0c;以及历史天气数据的变化趋势。 技术栈&#xff1a;Python语言、My…

HarmonyOS 启动提速秘籍:懒加载全链路实战解析

摘要 随着移动应用功能越来越复杂、界面越来越丰富&#xff0c;应用启动慢、内存占用高等问题也越来越普遍。特别是在 HarmonyOS NEXT 应用开发中&#xff0c;如果不加优化&#xff0c;用户打开页面时可能要等好几秒&#xff0c;体验就很差了。 懒加载&#xff08;Lazy Loading…

全新安装Proxmox VE启动时卡在Loading initial ramdisk

原因&#xff1a; 使用了Ventoy启动盘装载 Proxmox ISO 文件安装。 要用Ventoy优盘启动&#xff0c;选择Advance Option里的Rescue Boot&#xff0c; 修改文件/etc/default/grub.d/installer.cfg&#xff0c;删除rdinit/vtoy/vtoy运行 update-grub 更新grub配置&#xff0c;重启…

【Java项目安全基石】登录认证实战:Session/Token/JWT用户校验机制深度解析

目录 1.前言 2.正文 2.1Cookie—Session机制 2.1.1核心原理图解&#xff1a; 2.1.2四步核心流程&#xff1a; 2.1.3存储架构对比 2.1.4集群部署方案&#xff08;Spring Session Redis&#xff09; 2.2Token令牌 2.2.1核心原理图解&#xff1a; 2.2.2四步核心流程&am…

融合优势:SIP 广播对讲联动华为会议 全场景沟通响应提速​

SIP 广播对讲与华为视频会议融合解决方案&#xff0c;是基于 SIP 协议将广播对讲系统与华为视频会议系统进行整合&#xff0c;实现通信资源共享与业务流程联动&#xff0c;可提升应急响应效率与沟通协作能力。融合原理&#xff1a;SIP 是一种基于文本的应用层协议&#xff0c;具…

Milvus Dify 学习笔记

目录 docker方式&#xff1a; 模式一&#xff1a;Milvus Lite linux docker方式&#xff1a; 下载yml文件&#xff0c; https://github.com/milvus-io/milvus/releases docker启动&#xff1a; docker compose up -d from pymilvus import connections connections.conne…

汽车ECU控制器通信架构

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

【Linux】基本指令(入门篇)(上)

目录 前言 1.目录操作指令 1.1指令 1.2理论 1.2.1文件 1.2.2目录与路径 2.文件操作指令 2.1指令 2.2理论 2.2.1输出与输入 2.2.2一切皆文件 前言 这是Linux学习下的第一篇文章&#xff0c;后续Linux的学习也会持续更新分享。 Linux的基本指令是使用Linux操作系统的基础…

正向代理与反向代理理解

问&#xff1a; 应用a请求ng&#xff0c;然后ng根据不同请求路径将请求转发到不同的服务器&#xff0c;对于应用a来说这个ng是正向代理角色还是反向代理呢&#xff1f; 答&#xff1a; 在这个场景中&#xff0c;Nginx 扮演的是反向代理的角色&#xff0c;而不是正向代理。以下是…

【Kafka】深入理解 Kafka MirrorMaker2 - 实战篇

文章目录一、把“家伙事儿”都备齐二、部署其实很简单三、配置 MirrorMaker2四、修改启动脚本五、集群启动与验证六、这集群“结实”吗&#xff1f;聊聊它的高可用它没有“大脑”&#xff0c;但活得很好极限测试&#xff1a;干掉两个节点会怎样&#xff1f;写在最后最近在跟 Ka…

借助AI学习开源代码git0.7之四update-cache

借助AI学习开源代码git0.7之四update-cache update-cache.c 主要负责对索引&#xff08;index&#xff09;&#xff0c;也即缓存&#xff08;cache&#xff09;&#xff0c;进行增、删、改操作。现在的高层命令 git add 的部分核心功能就是由这个代码实现的。 核心功能 该程序的…

【48】MFC入门到精通——MFC 文件读写总结 CFile、CStdioFile、CFileDialog

文章目录1 打开文件1.2 打开文件模式总结2 常用函数2.1 写文件2.2 读文件2.3 获取文件长度3. 文件打开读写实力3.1 写文件 覆盖写3.2 文尾追加写3.3 换行写4 文件对话框 CFileDialog4.2 文件对话框实例5 CStdioFile 类 读写CStingMFC提供了一个文件操作的基类CFile&#xff0c;…

Leetcode 124. 二叉树中的最大路径和

递归/*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* TreeNode…