面试题答案
一键面试1. Objective - C运行时系统确定方法调用顺序的方式
在Objective - C运行时,当向一个对象发送消息时,运行时系统会按照以下顺序查找方法实现:
- 类的方法列表:首先在接收者对象所属类的方法列表中查找方法。如果找到了对应的方法,就直接调用该方法的实现。
- 父类的方法列表:如果在当前类的方法列表中没有找到,运行时会沿着继承链向上,在父类的方法列表中查找,直到找到方法或到达继承链的顶端(
NSObject
类)。 - 动态方法解析:如果在类和父类的方法列表中都没有找到方法,运行时会进入动态方法解析阶段。它会调用
+ (BOOL)resolveInstanceMethod:(SEL)sel
(实例方法)或+ (BOOL)resolveClassMethod:(SEL)sel
(类方法)方法,允许开发者在运行时动态添加方法实现。如果动态添加了方法,运行时会重新开始查找过程。 - 备用接收者:如果动态方法解析没有找到方法,运行时会调用
- (id)forwardingTargetForSelector:(SEL)aSelector
方法,允许对象返回一个备用的对象来处理这个消息。如果返回了非nil
的对象,运行时会向这个备用对象发送消息。 - 完整的消息转发:如果
forwardingTargetForSelector:
返回nil
,运行时会进入完整的消息转发阶段。它会创建一个NSInvocation
对象,包含方法调用的所有信息,然后调用- (void)forwardInvocation:(NSInvocation *)anInvocation
方法。开发者可以在这个方法中手动处理消息转发,例如将消息转发给其他对象。如果forwardInvocation:
没有处理消息,运行时会调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
方法获取方法签名,如果获取到有效的签名,会再次调用forwardInvocation:
,否则会抛出unrecognized selector sent to instance
异常。
当存在类别(Category)时,类别中的方法会在运行时被添加到类的方法列表头部。这意味着如果类本身和类别中有同名方法,类别中的方法会优先被找到和调用。如果多个类别中有同名方法,最后参与编译的类别中的方法会优先被调用,因为它在方法列表的更靠前位置。
2. 实际开发中设计和管理类别的方法,避免方法冲突
命名规范
- 使用唯一前缀:为类别中的方法命名添加唯一的前缀,以降低与其他类别或类本身方法冲突的可能性。例如,如果类别是用于处理网络相关功能,可以使用如
net_
作为前缀,方法名可以是- (void)net_fetchDataWithCompletion:(void(^)(id data, NSError *error))completion
。 - 明确功能描述:方法名要清晰地描述其功能,这样即使不同类别有相似功能,也能通过方法名区分。例如,一个用于加载图片的类别,方法名可以是
- (void)loadImageFromURL:(NSURL *)url completion:(void(^)(UIImage *image, NSError *error))completion
。
组织类别
- 按功能分组:将相关功能的类别放在一起,便于管理和维护。例如,将所有与用户界面相关的类别放在一个文件夹,与数据处理相关的类别放在另一个文件夹。
- 避免过度使用类别:不要为一个类创建过多的类别,尽量将功能相似的方法放在一个类别中。如果类别过多,会增加方法冲突的风险,也不利于代码的可读性和维护性。
编译顺序控制
虽然不推荐依赖编译顺序来避免方法冲突,但在某些情况下,可以通过调整类别文件的编译顺序来确保特定的方法优先被调用。不过这种方法不够可靠,因为编译顺序可能会因为项目结构的变化而改变。
3. 利用runtime相关知识解决可能出现的问题示例
假设MyClass
有两个类别MyClass+Category1
和MyClass+Category2
,它们都定义了一个名为commonMethod
的方法。我们可以利用runtime的方法交换技术来解决方法冲突问题,同时保留两个方法的功能。
#import <objc/runtime.h>
@implementation MyClass (Category1)
- (void)commonMethod {
NSLog(@"Category1's commonMethod");
}
@end
@implementation MyClass (Category2)
- (void)commonMethod {
NSLog(@"Category2's commonMethod");
}
@end
@implementation MyClass
+ (void)load {
Method originalMethod1 = class_getInstanceMethod(self, @selector(commonMethod));
Method swappedMethod1 = class_getInstanceMethod(self, @selector(category1_commonMethod));
method_exchangeImplementations(originalMethod1, swappedMethod1);
Method originalMethod2 = class_getInstanceMethod(self, @selector(commonMethod));
Method swappedMethod2 = class_getInstanceMethod(self, @selector(category2_commonMethod));
method_exchangeImplementations(originalMethod2, swappedMethod2);
}
- (void)category1_commonMethod {
// 先调用Category2的方法
[self category2_commonMethod];
NSLog(@"After calling Category2's method, execute Category1's commonMethod");
}
- (void)category2_commonMethod {
NSLog(@"Execute Category2's commonMethod");
}
@end
在上述代码中,通过+load
方法在类加载时进行方法交换。这样,当调用commonMethod
时,实际上会先调用category2_commonMethod
,然后再执行category1_commonMethod
中的其他逻辑,从而同时保留了两个类别中commonMethod
的功能,避免了简单的方法覆盖导致的功能丢失。