OC中的内省方法(Introspection)

简介: OC中的内省方法(Introspection)

内省(Introspection)是面向对象语言和环境的一个强大特性,Objective-C和Cocoa在这个方面尤其丰富。内省是对象揭示自己作为一个运行时对象的详细信息的一种能力。这些详细信息包括对象在继承树上的位置、对象是否遵循特定的协议,以及是否可以响应特定的消息。NSObject协议和类定义了很多内省方法,用于查询运行时信息,以便根据对象的特征进行识别。


明智地使用内省可以使面向对象的程序更加高效和健壮。它有助于避免错误地进行消息派发、错误地假设对象相等,以及类似的问题。下面将介绍如何在代码中有效地使用NSObject的内省方法。


1. 评估继承关系


一旦知道一个对象属于什么类,就可能已经相当了解这个对象了。可以知道它具有什么能力、哪些属性,以及可以响应哪些消息。即使在内省之后不能了解对象所属的类,也可以知道该对象不能响应特定的消息。


NSObject协议声明了几个方法,用于确定对象在类层次中的位置。这些方法在不同粒度上进行操作,比如class和superclass实例方法分别返回代表类和基类的Class对象。使用这些方法需要将一个Class对象和另一个进行对比。代码清单5-1给出了一个简单(可能没有价值)的用法实例。


代码清单5-1 使用类和基类的方法

// ...    while ( id anObject = [objectEnumerator nextObject] ) {      if ( [self class] == [anObject superclass] ) {      // do something appropriate...      }    }


注意


有些时候需要通过class或superclass方法得到正确的类消息接收者。


更加常见的是检查对象类的从属关系,这种情况下需要向该对象发送isKindOfClass:方法或isMemberOfClass:消息。isKindOfClass方法返回接收者是否为给定类或其继承类的实例,isMemberOfClass:消息则告诉接收者是否为指定类的实例。isKindOfClass: 方法通常更有用,因为通过它可以知道是否可以向该对象发送一系列消息。考虑代码清单5-2中的代码片段:


代码清单5-2 使用isKindOfClass:方法


if ([item isKindOfClass:[NSData class]]) {      const unsigned char *bytes = [item bytes];      unsigned int length = [item length];      // ...    }


确定item对象是NSData类的继承类的实例之后,代码就知道可以向它发送NSData的bytes和length消息。假定item是NSMutableData类的一个实例,则isKindOfClass:和isMemberOfClass:之间的差别就变得更加明显。如果调用的是isMemberOfClass:,而不是isKindOfClass:,条件控制块中的代码将永远不会被执行。因为item并不是NSData类的实例,而是其子类NSMutableData的实例。


2. 方法实现和协议遵循


NSObject还有两个功能更加强大的内省方法,即respondsToSelector:和conforms-ToProtocol:。这两个方法分别告诉一个对象是否实现特定的方法,以及是否遵循指定的正式协议(即该对象是否采纳了该协议,且实现了该协议的所有方法)。


在代码中,可以在类似的情况下使用这些方法。通过这些方法,可以在将消息或消息集合发送给某些潜在的匿名对象之前,确定它们是否可以正确地进行响应。在发送消息之前进行检查可以避免由不能识别的选择器引起的运行时例外。在实现非正式协议(这种协议是委托技术的基础)时,Application Kit就是在调用委托方法之前检查委托对象是否实现该方法(通过respondsToSelector:方法)。


代码清单5-3显示了如何在代码中使用respondsToSelector:方法。


代码清单5-3 使用respondsToSelector:方法


- (void)doCommandBySelector:(SEL)aSelector {      if ([self respondsToSelector:aSelector]) {        [self performSelector:aSelector withObject:nil];      } else {        [_client doCommandBySelector:aSelector];      }    }

代码清单5-4显示如何在代码中使用conformsToProtocol:方法。


代码清单5-4 使用conformsToProtocol:方法

// ...    if (!([((id)testObject) conformsToProtocol:@protocol(NSMenuItem)])) {      NSLog(@"Custom MenuItem, '%@', not loaded; it must conform to the        'NSMenuItem' protocol.\n", [testObject class]);      [testObject release];      testObject = nil;    }


3. 对象的比较


hash和isEqual:方法虽然不是严格的内省方法,但是可以发挥类似的作用,是进行对象的识别和比较时不可或缺的运行时工具。它们并不向运行环境查询对象信息,而是依赖于具体类的比较逻辑。


hash和isEqual:方法都在NSObject协议中声明,且彼此关系紧密。实现hash方法必须返回一个整型数,作为哈希表结构中的表地址。两个对象相等(isEqual:方法的判断结果)意味着它们有相同的哈希值。如果对象可能被包含在像NSSet这样的集合中,则需要定义hash方法,并确保该方法在两个对象相等的时候返回相同的哈希值。NSObject类中默认的isEqual:实现只是简单地检查指针是否相等。


isEqual:的使用相当直接,它将消息的接收者和通过参数传入的对象进行比较。对象的比较常常可以在运行时决定应该对对象做些什么。如代码清单5-5所示,可以通过isEqual:来确定是否执行某一个动作。在这个例子中,动作是指保存被修改了的预置信息。


代码清单5-5 使用isEqual:方法

- (void)saveDefaults {      NSDictionary *prefs = [self preferences];      if (![origValues isEqual:prefs])        [Preferences savePreferencesToDefaults:prefs];    }

如果正在创建子类,则可能需要重载isEqual:方法,以进一步检查对象是否相等。子类可能定义额外的属性,当两个实例被认为相等时,属性的值必须相同。举例来说,假定创建一个名为MyWidget的NSObject子类,类中包含两个实例变量:name和data。当MyWidget的两个实例被认为是相等时,这些变量必须具有相同的值。代码清单5-6显示如何在MyWidget类中实现isEqual:方法。


代码清单5-6 重载isEqual:方法

- (BOOL)isEqual:(id)other {      if (other == self)        return YES;      if (!other || ![other isKindOfClass:[self class]])        return NO;      return [self isEqualToWidget:other];}    - (BOOL)isEqualToWidget:(MyWidget *)aWidget {      if (self == aWidget)      return YES;      if (![(id)[self name] isEqual:[aWidget name]])        return NO;      if (![[self data] isEqualToData:[aWidget data]])        return NO;      return YES;    }


isEqual:方法首先检查指针的等同性,然后是类的等同性,最后调用对象的比较器进行比较。比较器的名称指示出参与比较的对象的类名称。这种类型的比较器对传入的对象进行强制类型检查,是Cocoa中常见的约定,NSString的isEqualToString:和NSTimeZone的isEqualToTimeZone:就是两个这样的例子。特定类的比较器(在这个例子中是isEqualToWidget:)负责执行name和data变量的等同性。


要点


(1)内省是对象揭示自己作为一个运行时对象的详细信息的一种能力。这些详细信息包括对象在继承树上的位置,对象是否遵循特定的协议,以及是否可以响应特定的消息。


(2)内省有助于避免错误地进行消息派发、错误地假设对象相等,以及类似的问题。


(3)NSObject协议和类定义了很多内省方法,用于查询运行时信息,以便根据对象的特征进行识别,例如isEqual方法,可用来比较对象。


(4)在Cocoa框架的所有isEqualToType:方法中,nil都不是正当的参数,这些方法的实现在接收到nil参数时会抛出异常。然而为了向后兼容,Cocoa框架中的isEqual:方法可以接收nil值,在这种情况下返回NO。

目录
相关文章
关于block的本质,你懂了吗?
block应用的目的: 把将来想要执行的代码封装起来,然后在恰当的时刻再执行代码。 block本质: 1、block是封装了函数调用和函数调用环境(如:block内部要使用的参数)的OC对象。 2、block本质上也是一个OC对象,它内部也有一个isa指针(只要内部有一个isa指针,我们就可以认为他是OC对象,因为NSObject作为最基础的OC对象,第一个成员变量就是isa指针,这是OC对象的特征)。
403 0
关于block的本质,你懂了吗?
|
iOS开发 C++
几个示例带你去了解OC对象是怎么分配内存的
1、64位系统环境的OC对象最小分配内存为16字节。 2、结构体内存对齐,是指在计算结构体大小的时候,其分配的原则:结构体大小必须是最大成员变量分配内存的倍数。 3、iOS操作系统在分配内存的时候,也有内存对齐的概念,为16的倍数 :在iOS的堆空间中,如果要创建一个OC对象,分配内存时,都是16的倍数。
299 0
|
1天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1069 0
|
10天前
|
人工智能 运维 安全
|
1天前
|
弹性计算 Kubernetes jenkins
如何在 ECS/EKS 集群中有效使用 Jenkins
本文探讨了如何将 Jenkins 与 AWS ECS 和 EKS 集群集成,以构建高效、灵活且具备自动扩缩容能力的 CI/CD 流水线,提升软件交付效率并优化资源成本。
256 0
|
8天前
|
人工智能 异构计算
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
|
9天前
|
人工智能 测试技术 API
智能体(AI Agent)搭建全攻略:从概念到实践的终极指南
在人工智能浪潮中,智能体(AI Agent)正成为变革性技术。它们具备自主决策、环境感知、任务执行等能力,广泛应用于日常任务与商业流程。本文详解智能体概念、架构及七步搭建指南,助你打造专属智能体,迎接智能自动化新时代。
|
10天前
|
机器学习/深度学习 人工智能 自然语言处理
B站开源IndexTTS2,用极致表现力颠覆听觉体验
在语音合成技术不断演进的背景下,早期版本的IndexTTS虽然在多场景应用中展现出良好的表现,但在情感表达的细腻度与时长控制的精准性方面仍存在提升空间。为了解决这些问题,并进一步推动零样本语音合成在实际场景中的落地能力,B站语音团队对模型架构与训练策略进行了深度优化,推出了全新一代语音合成模型——IndexTTS2 。
749 23