在 Objective-C 中,当一个消息发送给一个对象时,消息查找和转发的过程可以分为几个阶段。这些阶段包括快速查找、慢速查找、动态方法解析、消息转发等,具体如下:
-
快速查找(Fast Path):
- 当一个方法被调用时,Objective-C 运行时会首先检查对象的类是否有一个缓存的入口(
cache_t
)对应该方法。如果缓存命中,将直接调用方法,这是最快的路径。
- 当一个方法被调用时,Objective-C 运行时会首先检查对象的类是否有一个缓存的入口(
-
慢速查找(Slow Path):
- 如果快速查找缓存未命中,运行时会进入慢速查找阶段,它会在类的方法列表中查找,如果没有找到,会继续在父类的方法列表中递归查找,直到找到方法或达到根类。
-
动态方法解析(Dynamic Method Resolution):
- 如果方法依然未找到,运行时将提供一个机会让类动态提供一个方法实现。这通过类的
+resolveInstanceMethod:
(实例方法)或+resolveClassMethod:
(类方法)来完成。如果类在这个阶段提供了一个方法实现,该方法将被调用。
- 如果方法依然未找到,运行时将提供一个机会让类动态提供一个方法实现。这通过类的
-
快速转发(Fast Forwarding):
- 如果类没有动态添加方法,运行时将调用
-forwardingTargetForSelector:
方法,检查是否有其他对象可以处理该消息。如果此方法返回非nil
对象,消息将被转发给该对象。
- 如果类没有动态添加方法,运行时将调用
-
慢速转发(Normal Forwarding):
- 如果
-forwardingTargetForSelector:
返回nil
,则运行时会启动完整的消息转发机制。这涉及到-methodSignatureForSelector:
和-forwardInvocation:
两个方法。首先,运行时调用-methodSignatureForSelector:
来获取方法的参数和返回类型的签名。如果返回有效的NSMethodSignature
,则创建NSInvocation
对象并调用-forwardInvocation:
。在-forwardInvocation:
中,你可以自定义如何处理转发,包括将消息转发给另一个对象,或者直接响应。
- 如果
-
结束处理:
- 如果
-methodSignatureForSelector:
返回nil
,或者-forwardInvocation:
没有实现,运行时将调用-doesNotRecognizeSelector:
方法。默认实现会抛出异常,指示无法处理未知的选择器。
- 如果
整个过程是系统为Objective-C消息发送机制提供的一系列逐步尝试的方法,目的是给开发者足够的灵活性来定制消息传递过程,以及提供多种方法来处理方法调用失败的情况。
消息转发机制(Message Forwarding)是 Objective-C 中处理未识别消息的一种机制。它允许对象在运行时动态地处理未知的消息,可以用于实现一些高级的功能。以下是几个常见的应用场景:
- 动态方法解析:在运行时为对象动态添加方法。
- 消息转发:将消息转发给另一个对象处理。
- AOP(面向切面编程):在方法调用前后插入额外的逻辑,如日志记录、性能监控等。
- 代理方法的简化实现:自动将未实现的代理方法转发给另一个对象。
动态方法解析
使用动态方法解析(Dynamic Method Resolution)而不是直接在类中定义方法,主要是为了实现一些高级功能和特定的需求。以下是一些常见的应用场景和理由:
1. 惰性加载
有时候,我们可能希望在实际需要时才加载某些方法,而不是在类加载时就全部加载。这种方式可以节省内存和启动时间。
示例:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(expensiveMethod)) {
class_addMethod([self class], sel, (IMP)expensiveMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void expensiveMethodIMP(id self, SEL _cmd) {
NSLog(@"Expensive method called!");
}
2. 插件机制
在某些情况下,我们希望一个应用或框架能够通过插件机制扩展其功能。这时可以用动态方法解析来实现插件的动态加载。
示例:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([self loadPluginForMethod:methodName]) {
return YES;
}
return [super resolveInstanceMethod:sel];
}
+ (BOOL)loadPluginForMethod:(NSString *)methodName {
// 动态加载插件并添加方法实现
// ...
return YES;
}
在插件机制中,直接在类中预定义方法和使用动态方法解析各有优缺点。以下是两种方法的对比:
直接预定义方法
优点:
- 代码直观:方法在编译时就已经定义,代码清晰易懂,便于维护。
- 编译时检查:编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
- 性能:由于方法在编译时已经确定,不需要在运行时进行方法解析,调用速度更快。
缺点:
- 灵活性不足:所有可能的插件方法都需要在类中预定义,限制了插件的灵活性和扩展性。
- 内存占用:即使某些插件方法在运行时未被使用,这些方法依然会占用内存。
- 代码维护复杂:当插件数量增加时,需要在类中不断添加新方法,增加了代码的复杂性和维护成本。
示例:
假设我们有一个类 PluginHost
,需要支持多个插件方法。
@interface PluginHost : NSObject
- (void)pluginMethod1;
- (void)pluginMethod2;
@end
@implementation PluginHost
- (void)pluginMethod1 {
NSLog(@"Plugin Method 1");
}
- (void)pluginMethod2 {
NSLog(@"Plugin Method 2");
}
@end
在这种情况下,所有可能的插件方法都需要在编译时定义,即使某些方法可能在运行时未被使用。
动态方法解析
优点:
- 高灵活性:可以在运行时动态添加或修改方法,实现更灵活的插件机制。
- 内存优化:只有在需要时才会加载和添加方法,减少不必要的内存占用。
- 代码简化:不需要在类中预定义所有可能的插件方法,代码更简洁。
缺点:
- 复杂性增加:动态方法解析增加了代码的复杂性,可能需要处理更多的运行时错误。
- 性能开销:在运行时解析和添加方法会有一定的性能开销。
- 缺少编译时检查:编译器无法对动态方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用动态方法解析实现插件机制。
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
@interface PluginHost : NSObject
@end
@implementation PluginHost
void pluginMethod1IMP(id self, SEL _cmd) {
NSLog(@"Dynamic Plugin Method 1");
}
void pluginMethod2IMP(id self, SEL _cmd) {
NSLog(@"Dynamic Plugin Method 2");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"pluginMethod1"]) {
class_addMethod([self class], sel, (IMP)pluginMethod1IMP, "v@:");
return YES;
} else if ([methodName isEqualToString:@"pluginMethod2"]) {
class_addMethod([self class], sel, (IMP)pluginMethod2IMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
PluginHost *host = [PluginHost new];
[host performSelector:@selector(pluginMethod1)];
[host performSelector:@selector(pluginMethod2)];
}
return 0;
}
总结
- 直接预定义方法适用于插件数量有限且变化不频繁的场景,代码简单直观,性能较好。
- 动态方法解析则适用于需要高扩展性和灵活性的场景,可以在运行时动态添加或修改方法,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果插件数量较少且变化不大,直接预定义方法可能更合适。如果需要高扩展性和灵活性,动态方法解析则是更好的选择。
。
3. 消息转发
有时候,我们希望将某些方法的实现委托给其他对象。动态方法解析可以作为消息转发机制的一部分,实现更灵活的消息处理。
示例:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(forwardedMethod)) {
class_addMethod([self class], sel, (IMP)forwardedMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void forwardedMethodIMP(id self, SEL _cmd) {
NSLog(@"This method is forwarded!");
}
在消息转发机制中,直接在类中预定义方法和使用动态方法解析各有优缺点。消息转发通常用于将一个对象的消息转发给另一个对象。以下是两种方法的对比:
直接预定义方法
优点:
- 代码直观:方法在编译时就已经定义,代码清晰易懂,便于维护。
- 编译时检查:编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
- 性能:由于方法在编译时已经确定,不需要在运行时进行方法解析,调用速度更快。
缺点:
- 灵活性不足:所有可能的转发方法都需要在类中预定义,限制了消息转发的灵活性和扩展性。
- 代码冗余:当需要转发大量方法时,需要在类中预定义所有可能的方法,导致代码冗余。
- 维护复杂:当需要修改或添加新的转发方法时,需要在类中添加或修改相应的方法,增加了维护成本。
示例:
假设我们有一个类 MessageHost
,需要将某些方法转发给另一个对象 MessageReceiver
。
@interface MessageReceiver : NSObject
- (void)forwardedMethod;
@end
@implementation MessageReceiver
- (void)forwardedMethod {
NSLog(@"MessageReceiver forwardedMethod called");
}
@end
@interface MessageHost : NSObject
@property (nonatomic, strong) MessageReceiver *receiver;
- (void)forwardedMethod;
@end
@implementation MessageHost
- (instancetype)init {
if (self = [super init]) {
_receiver = [MessageReceiver new];
}
return self;
}
- (void)forwardedMethod {
[self.receiver forwardedMethod];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MessageHost *host = [MessageHost new];
[host forwardedMethod];
}
return 0;
}
在这种情况下,需要在 MessageHost
中预定义 forwardedMethod
,并在方法内部转发给 MessageReceiver
。
动态方法解析
优点:
- 高灵活性:可以在运行时动态添加或修改方法,实现更灵活的消息转发机制。
- 代码简洁:不需要在类中预定义所有可能的转发方法,代码更简洁。
- 减少代码冗余:当需要转发大量方法时,可以通过动态方法解析减少代码冗余。
缺点:
- 复杂性增加:动态方法解析增加了代码的复杂性,可能需要处理更多的运行时错误。
- 性能开销:在运行时解析和添加方法会有一定的性能开销。
- 缺少编译时检查:编译器无法对动态方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用动态方法解析实现消息转发。
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
@interface MessageReceiver : NSObject
- (void)forwardedMethod;
@end
@implementation MessageReceiver
- (void)forwardedMethod {
NSLog(@"MessageReceiver forwardedMethod called");
}
@end
@interface MessageHost : NSObject
@property (nonatomic, strong) MessageReceiver *receiver;
@end
@implementation MessageHost
- (instancetype)init {
if (self = [super init]) {
_receiver = [MessageReceiver new];
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"forwardedMethod"]) {
class_addMethod([self class], sel, (IMP)dynamicForwardedMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicForwardedMethodIMP(id self, SEL _cmd) {
MessageHost *host = (MessageHost *)self;
[host.receiver performSelector:_cmd];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MessageHost *host = [MessageHost new];
[host performSelector:@selector(forwardedMethod)];
}
return 0;
}
总结
- 直接预定义方法适用于消息转发方法数量有限且变化不频繁的场景,代码简单直观,性能较好。
- 动态方法解析则适用于需要高扩展性和灵活性的场景,可以在运行时动态添加或修改方法,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果消息转发方法数量较少且变化不大,直接预定义方法可能更合适。如果需要高扩展性和灵活性,动态方法解析则是更好的选择。
4. 兼容性和向后兼容
在框架或库的开发中,可能需要兼容不同版本的功能。动态方法解析允许我们根据运行时的环境决定是否添加某些方法。
示例:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(newFeatureMethod)) {
if ([self isCompatibleWithNewFeature]) {
class_addMethod([self class], sel, (IMP)newFeatureMethodIMP, "v@:");
return YES;
}
}
return [super resolveInstanceMethod:sel];
}
BOOL isCompatibleWithNewFeature() {
// 检查兼容性
return YES;
}
void newFeatureMethodIMP(id self, SEL _cmd) {
NSLog(@"New feature method called!");
}
在处理兼容性和向后兼容性的问题时,直接在类中预定义方法和使用动态方法解析各有优缺点。
直接预定义方法
优点:
- 代码直观:方法在编译时就已经定义,代码清晰易懂,便于维护。
- 编译时检查:编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
- 性能:由于方法在编译时已经确定,不需要在运行时进行方法解析,调用速度更快。
缺点:
- 灵活性不足:所有可能的新特性方法都需要在类中预定义,限制了代码的灵活性和扩展性。
- 代码冗余:即使某些方法在当前版本不需要,也需要在类中定义,以确保向后兼容。
- 维护复杂:当需要修改或添加新的特性方法时,需要在类中添加或修改相应的方法,增加了维护成本。
示例:
假设我们有一个类 FeatureHost
,需要支持新特性方法 newFeatureMethod
,但只有在特定版本中才需要。
@interface FeatureHost : NSObject
- (void)commonMethod;
- (void)newFeatureMethod;
@end
@implementation FeatureHost
- (void)commonMethod {
NSLog(@"Common method called");
}
- (void)newFeatureMethod {
if ([self isCompatibleWithNewFeature]) {
NSLog(@"New feature method called");
} else {
NSLog(@"New feature not supported");
}
}
- (BOOL)isCompatibleWithNewFeature {
// 检查兼容性
return YES;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
FeatureHost *host = [FeatureHost new];
[host commonMethod];
[host newFeatureMethod];
}
return 0;
}
在这种情况下,需要在 FeatureHost
中预定义 newFeatureMethod
,并在方法内部检查是否兼容新特性。
动态方法解析
优点:
- 高灵活性:可以在运行时动态添加或修改方法,实现更灵活的版本兼容性处理。
- 代码简洁:不需要在类中预定义所有可能的新特性方法,代码更简洁。
- 减少代码冗余:只有在需要时才会加载和添加方法,减少不必要的代码和内存占用。
缺点:
- 复杂性增加:动态方法解析增加了代码的复杂性,可能需要处理更多的运行时错误。
- 性能开销:在运行时解析和添加方法会有一定的性能开销。
- 缺少编译时检查:编译器无法对动态方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用动态方法解析实现版本兼容性处理。
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
@interface FeatureHost : NSObject
- (void)commonMethod;
@end
@implementation FeatureHost
- (void)commonMethod {
NSLog(@"Common method called");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([methodName isEqualToString:@"newFeatureMethod"]) {
if ([self isCompatibleWithNewFeature]) {
class_addMethod([self class], sel, (IMP)newFeatureMethodIMP, "v@:");
return YES;
} else {
class_addMethod([self class], sel, (IMP)unsupportedFeatureMethodIMP, "v@:");
return YES;
}
}
return [super resolveInstanceMethod:sel];
}
BOOL isCompatibleWithNewFeature() {
// 检查兼容性
return YES;
}
void newFeatureMethodIMP(id self, SEL _cmd) {
NSLog(@"New feature method called");
}
void unsupportedFeatureMethodIMP(id self, SEL _cmd) {
NSLog(@"New feature not supported");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
FeatureHost *host = [FeatureHost new];
[host commonMethod];
[host performSelector:@selector(newFeatureMethod)];
}
return 0;
}
总结
- 直接预定义方法适用于版本兼容性需求较少且变化不频繁的场景,代码简单直观,性能较好。
- 动态方法解析则适用于需要高扩展性和灵活性的场景,可以在运行时动态添加或修改方法,减少代码冗余,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果版本兼容性需求较少且变化不大,直接预定义方法可能更合适。如果需要高扩展性和灵活性,动态方法解析则是更好的选择。
5. 动态代理
在某些设计模式中,例如动态代理(Dynamic Proxy),动态方法解析可以用于创建代理对象,拦截并处理方法调用。
示例:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if ([self shouldInterceptMethod:sel]) {
class_addMethod([self class], sel, (IMP)interceptedMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
BOOL shouldInterceptMethod(SEL sel) {
// 判断是否需要拦截
return YES;
}
void interceptedMethodIMP(id self, SEL _cmd) {
NSLog(@"Intercepted method called!");
}
动态代理是一种设计模式,它允许在运行时动态创建代理对象,以拦截和处理方法调用。在动态代理中,可以使用动态方法解析来实现灵活的代理,而直接预定义方法则是另一种实现方式。以下是两种方法的优缺点对比。
直接预定义方法
优点:
- 代码直观:方法在编译时就已经定义,代码清晰易懂,便于维护。
- 编译时检查:编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
- 性能:由于方法在编译时已经确定,不需要在运行时进行方法解析,调用速度更快。
缺点:
- 灵活性不足:所有可能的代理方法都需要在类中预定义,限制了代理的灵活性和扩展性。
- 代码冗余:当需要代理大量方法时,需要在类中预定义所有可能的方法,导致代码冗余。
- 维护复杂:当需要修改或添加新的代理方法时,需要在类中添加或修改相应的方法,增加了维护成本。
示例:
假设我们有一个类 ProxyHost
,需要代理某些方法到另一个对象 RealSubject
。
@interface RealSubject : NSObject
- (void)realMethod1;
- (void)realMethod2;
@end
@implementation RealSubject
- (void)realMethod1 {
NSLog(@"RealSubject realMethod1 called");
}
- (void)realMethod2 {
NSLog(@"RealSubject realMethod2 called");
}
@end
@interface ProxyHost : NSObject
@property (nonatomic, strong) RealSubject *realSubject;
- (void)realMethod1;
- (void)realMethod2;
@end
@implementation ProxyHost
- (instancetype)init {
if (self = [super init]) {
_realSubject = [RealSubject new];
}
return self;
}
- (void)realMethod1 {
[self.realSubject realMethod1];
}
- (void)realMethod2 {
[self.realSubject realMethod2];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ProxyHost *host = [ProxyHost new];
[host realMethod1];
[host realMethod2];
}
return 0;
}
在这种情况下,需要在 ProxyHost
中预定义 realMethod1
和 realMethod2
,并在方法内部转发给 RealSubject
。
动态方法解析
优点:
- 高灵活性:可以在运行时动态添加或修改方法,实现更灵活的代理机制。
- 代码简洁:不需要在类中预定义所有可能的代理方法,代码更简洁。
- 减少代码冗余:只有在需要时才会加载和添加方法,减少不必要的代码和内存占用。
缺点:
- 复杂性增加:动态方法解析增加了代码的复杂性,可能需要处理更多的运行时错误。
- 性能开销:在运行时解析和添加方法会有一定的性能开销。
- 缺少编译时检查:编译器无法对动态方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用动态方法解析实现动态代理。
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
@interface RealSubject : NSObject
- (void)realMethod1;
- (void)realMethod2;
@end
@implementation RealSubject
- (void)realMethod1 {
NSLog(@"RealSubject realMethod1 called");
}
- (void)realMethod2 {
NSLog(@"RealSubject realMethod2 called");
}
@end
@interface ProxyHost : NSObject
@property (nonatomic, strong) RealSubject *realSubject;
@end
@implementation ProxyHost
- (instancetype)init {
if (self = [super init]) {
_realSubject = [RealSubject new];
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([self instancesRespondToSelector:NSSelectorFromString([NSString stringWithFormat:@"real%@", methodName])]) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodIMP(id self, SEL _cmd) {
NSString *methodName = NSStringFromSelector(_cmd);
SEL realSelector = NSSelectorFromString([NSString stringWithFormat:@"real%@", methodName]);
if ([self respondsToSelector:realSelector]) {
((void (*)(id, SEL))objc_msgSend)([self realSubject], realSelector);
} else {
NSLog(@"Method not found: %@", methodName);
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ProxyHost *host = [ProxyHost new];
[host performSelector:@selector(Method1)];
[host performSelector:@selector(Method2)];
}
return 0;
}
总结
- 直接预定义方法适用于代理方法数量有限且变化不频繁的场景,代码简单直观,性能较好。
- 动态方法解析则适用于需要高扩展性和灵活性的场景,可以在运行时动态添加或修改方法,减少代码冗余,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果代理方法数量较少且变化不大,直接预定义方法可能更合适。如果需要高扩展性和灵活性,动态方法解析则是更好的选择。
6. 简化代码和减少重复
在某些场景下,动态方法解析可以减少代码的重复,提高代码的可维护性。
示例:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *methodName = NSStringFromSelector(sel);
if ([self shouldAddMethodDynamically:methodName]) {
IMP imp = [self implementationForMethod:methodName];
const char *types = [self typesForMethod:methodName];
class_addMethod([self class], sel, imp, types);
return YES;
}
return [super resolveInstanceMethod:sel];
}
BOOL shouldAddMethodDynamically(NSString *methodName) {
// 判断是否需要动态添加方法
return YES;
}
IMP implementationForMethod(NSString *methodName) {
// 根据方法名返回对应的实现
return (IMP)dynamicMethodIMP;
}
const char *typesForMethod(NSString *methodName) {
// 根据方法名返回对应的方法签名
return "v@:";
}
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"Dynamic method called!");
}
总结
动态方法解析是一种强大的工具,允许在运行时灵活地处理方法调用。尽管在大多数情况下,直接在类中定义方法是最简单和最直接的方式,但在某些高级场景中,动态方法解析可以提供更强的灵活性和功能扩展能力。
使用动态方法解析的场景包括但不限于惰性加载、插件机制、消息转发、兼容性处理、动态代理和代码简化等。通过合理使用动态方法解析,可以实现更灵活、更高效的应用设计。
消息转发机制(Message Forwarding)
在iOS开发中,消息转发机制(Message Forwarding)是Objective-C的一项重要特性,允许开发者在运行时动态处理未能直接响应的消息。这一机制在以下实际应用场景中非常有用:
1. 动态代理
应用场景:动态代理模式允许一个对象在运行时为另一个对象处理消息。这在实现设计模式中的代理模式或装饰器模式时非常有用。
示例:
@interface ProxyObject : NSObject
@property (nonatomic, strong) id target;
@end
@implementation ProxyObject
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.target respondsToSelector:aSelector]) {
return self.target;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
// 使用 ProxyObject 代理真实对象
ProxyObject *proxy = [ProxyObject new];
proxy.target = realObject;
[proxy someMethod]; // 将由 realObject 处理
在动态代理的实现中,如果选择不使用消息转发而直接预定义方法,直接写出所有可能的代理方法,那么这种方法的优缺点如下:
直接预定义方法
优点:
-
代码直观:
- 所有代理方法在编译时已经定义,代码清晰易懂,便于维护。
-
编译时检查:
- 编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
-
性能:
- 方法在编译时已经确定,不需要在运行时进行方法解析或转发,调用速度较快。
缺点:
-
灵活性不足:
- 所有可能的代理方法都需要在类中预定义,限制了代理的灵活性和扩展性。
-
代码冗余:
- 当需要代理大量方法时,需要在类中预定义所有可能的方法,导致代码冗余。
-
维护复杂:
- 当需要修改或添加新的代理方法时,需要在类中添加或修改相应的方法,增加了维护成本。
示例:
假设我们有一个类 ProxyObject
,需要代理某些方法到另一个对象 RealSubject
。
@interface RealSubject : NSObject
- (void)realMethod1;
- (void)realMethod2;
@end
@implementation RealSubject
- (void)realMethod1 {
NSLog(@"RealSubject realMethod1 called");
}
- (void)realMethod2 {
NSLog(@"RealSubject realMethod2 called");
}
@end
@interface ProxyObject : NSObject
@property (nonatomic, strong) RealSubject *realSubject;
- (void)realMethod1;
- (void)realMethod2;
@end
@implementation ProxyObject
- (instancetype)init {
if (self = [super init]) {
_realSubject = [RealSubject new];
}
return self;
}
- (void)realMethod1 {
[self.realSubject realMethod1];
}
- (void)realMethod2 {
[self.realSubject realMethod2];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ProxyObject *proxy = [ProxyObject new];
[proxy realMethod1];
[proxy realMethod2];
}
return 0;
}
在这种情况下,需要在 ProxyObject
中预定义 realMethod1
和 realMethod2
,并在方法内部转发给 RealSubject
。
使用消息转发的动态代理
优点:
-
高灵活性:
- 可以在运行时动态处理方法调用,实现更灵活的代理机制。
-
代码简洁:
- 不需要在类中预定义所有可能的代理方法,代码更简洁。
-
减少代码冗余:
- 只有在需要时才会处理和转发方法调用,减少不必要的代码和内存占用。
缺点:
-
复杂性增加:
- 消息转发增加了代码的复杂性,可能需要处理更多的运行时错误。
-
性能开销:
- 在运行时解析和转发方法会有一定的性能开销。
-
缺少编译时检查:
- 编译器无法对动态方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用消息转发实现动态代理。
@interface RealSubject : NSObject
- (void)realMethod1;
- (void)realMethod2;
@end
@implementation RealSubject
- (void)realMethod1 {
NSLog(@"RealSubject realMethod1 called");
}
- (void)realMethod2 {
NSLog(@"RealSubject realMethod2 called");
}
@end
@interface ProxyObject : NSObject
@property (nonatomic, strong) RealSubject *realSubject;
@end
@implementation ProxyObject
- (instancetype)init {
if (self = [super init]) {
_realSubject = [RealSubject new];
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.realSubject respondsToSelector:aSelector]) {
return self.realSubject;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ProxyObject *proxy = [ProxyObject new];
[proxy performSelector:@selector(realMethod1)];
[proxy performSelector:@selector(realMethod2)];
}
return 0;
}
总结
直接预定义方法适用于代理方法数量有限且变化不频繁的场景,代码简单直观,性能较好,但灵活性不足,代码冗余且维护复杂。
使用消息转发的动态代理则适用于需要高扩展性和灵活性的场景,可以在运行时动态处理方法调用,减少代码冗余,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果代理方法数量较少且变化不大,直接预定义方法可能更合适。如果需要高扩展性和灵活性,消息转发则是更好的选择。
2. 消息转发与动态方法解析
应用场景:在某些情况下,需要在运行时动态决定如何处理某个方法调用。例如,动态创建方法实现、拦截特定消息等。
示例:
@interface DynamicObject : NSObject
@end
@implementation DynamicObject
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethod)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"Dynamic method called");
}
@end
DynamicObject *dynamicObject = [DynamicObject new];
[dynamicObject performSelector:@selector(dynamicMethod)];
在动态方法解析的实现中,如果选择不使用消息转发而直接预定义所有方法,那么这种方法的优缺点如下:
直接预定义方法
优点:
-
代码直观:
- 所有方法在编译时已经定义,代码清晰易懂,便于维护。
-
编译时检查:
- 编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
-
性能:
- 方法在编译时已经确定,不需要在运行时进行方法解析或转发,调用速度较快。
缺点:
-
灵活性不足:
- 所有可能的方法都需要在类中预定义,限制了动态添加或修改方法的灵活性和扩展性。
-
代码冗余:
- 当需要定义大量方法时,需要在类中预定义所有可能的方法,导致代码冗余。
-
维护复杂:
- 当需要修改或添加新的方法时,需要在类中添加或修改相应的方法,增加了维护成本。
示例:
假设我们有一个类 MyClass
,需要定义多个方法。
@interface MyClass : NSObject
- (void)method1;
- (void)method2;
@end
@implementation MyClass
- (void)method1 {
NSLog(@"Method1 called");
}
- (void)method2 {
NSLog(@"Method2 called");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [MyClass new];
[myObject method1];
[myObject method2];
}
return 0;
}
在这种情况下,需要在 MyClass
中预定义 method1
和 method2
方法。
使用消息转发与动态方法解析
优点:
-
高灵活性:
- 可以在运行时动态添加或修改方法,实现更灵活的动态方法解析机制。
-
代码简洁:
- 不需要在类中预定义所有可能的方法,代码更简洁。
-
减少代码冗余:
- 只有在需要时才会动态添加或解析方法,减少不必要的代码和内存占用。
缺点:
-
复杂性增加:
- 动态方法解析增加了代码的复杂性,可能需要处理更多的运行时错误。
-
性能开销:
- 在运行时解析和添加方法会有一定的性能开销。
-
缺少编译时检查:
- 编译器无法对动态添加的方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用动态方法解析实现动态添加方法。
#import <objc/runtime.h>
@interface MyClass : NSObject
@end
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethod)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"Dynamic method called");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [MyClass new];
[myObject performSelector:@selector(dynamicMethod)];
}
return 0;
}
总结
直接预定义方法适用于方法数量有限且变化不频繁的场景,代码简单直观,性能较好,但灵活性不足,代码冗余且维护复杂。
使用消息转发与动态方法解析则适用于需要高扩展性和灵活性的场景,可以在运行时动态添加或修改方法,减少代码冗余,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果方法数量较少且变化不大,直接预定义方法可能更合适。如果需要高扩展性和灵活性,消息转发与动态方法解析则是更好的选择。
3. 消息转发用于多继承模拟
应用场景:Objective-C 不支持多重继承,但通过消息转发,可以模拟多重继承的某些行为。
示例:
@interface Parent1 : NSObject
- (void)method1;
@end
@implementation Parent1
- (void)method1 {
NSLog(@"Parent1 method1");
}
@end
@interface Parent2 : NSObject
- (void)method2;
@end
@implementation Parent2
- (void)method2 {
NSLog(@"Parent2 method2");
}
@end
@interface Child : NSObject
@property (nonatomic, strong) Parent1 *parent1;
@property (nonatomic, strong) Parent2 *parent2;
@end
@implementation Child
- (instancetype)init {
if (self = [super init]) {
self.parent1 = [Parent1 new];
self.parent2 = [Parent2 new];
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.parent1 respondsToSelector:aSelector]) {
return self.parent1;
} else if ([self.parent2 respondsToSelector:aSelector]) {
return self.parent2;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
Child *child = [Child new];
[child method1]; // 由 Parent1 处理
[child method2]; // 由 Parent2 处理
在多继承模拟的实现中,如果选择不使用消息转发而直接在子类中实现所有需要的方法,那么这种方法的优缺点如下:
直接在子类中实现所有方法
优点:
-
代码直观:
- 所有方法在编译时已经定义,代码清晰易懂,便于维护。
-
编译时检查:
- 编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
-
性能:
- 方法在编译时已经确定,不需要在运行时进行方法解析或转发,调用速度较快。
缺点:
-
代码冗余:
- 当需要模拟多继承时,子类可能需要实现多个父类的方法,导致代码冗余。
-
维护复杂:
- 当父类的方法发生变化时,子类需要同步修改相应的方法,增加了维护成本。
-
灵活性不足:
- 无法在运行时动态处理不同父类的方法调用,限制了灵活性和扩展性。
示例:
假设我们有两个父类 Parent1
和 Parent2
,需要在子类 Child
中实现这些父类的方法。
@interface Parent1 : NSObject
- (void)method1;
@end
@implementation Parent1
- (void)method1 {
NSLog(@"Parent1 method1");
}
@end
@interface Parent2 : NSObject
- (void)method2;
@end
@implementation Parent2
- (void)method2 {
NSLog(@"Parent2 method2");
}
@end
@interface Child : NSObject
@end
@implementation Child
- (void)method1 {
Parent1 *parent1 = [Parent1 new];
[parent1 method1];
}
- (void)method2 {
Parent2 *parent2 = [Parent2 new];
[parent2 method2];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Child *child = [Child new];
[child method1];
[child method2];
}
return 0;
}
在这种情况下,需要在 Child
类中实现 method1
和 method2
,并在方法内部调用相应的父类方法。
使用消息转发模拟多继承
优点:
-
高灵活性:
- 可以在运行时动态处理不同父类的方法调用,实现更灵活的多继承模拟机制。
-
代码简洁:
- 不需要在子类中实现所有父类的方法,代码更简洁。
-
减少代码冗余:
- 只有在需要时才会转发方法调用,减少不必要的代码和内存占用。
缺点:
-
复杂性增加:
- 消息转发增加了代码的复杂性,可能需要处理更多的运行时错误。
-
性能开销:
- 在运行时解析和转发方法会有一定的性能开销。
-
缺少编译时检查:
- 编译器无法对动态方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用消息转发模拟多继承。
@interface Parent1 : NSObject
- (void)method1;
@end
@implementation Parent1
- (void)method1 {
NSLog(@"Parent1 method1");
}
@end
@interface Parent2 : NSObject
- (void)method2;
@end
@implementation Parent2
- (void)method2 {
NSLog(@"Parent2 method2");
}
@end
@interface Child : NSObject
@property (nonatomic, strong) Parent1 *parent1;
@property (nonatomic, strong) Parent2 *parent2;
@end
@implementation Child
- (instancetype)init {
if (self = [super init]) {
_parent1 = [Parent1 new];
_parent2 = [Parent2 new];
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.parent1 respondsToSelector:aSelector]) {
return self.parent1;
} else if ([self.parent2 respondsToSelector:aSelector]) {
return self.parent2;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Child *child = [Child new];
[child method1]; // 由 Parent1 处理
[child method2]; // 由 Parent2 处理
}
return 0;
}
总结
直接在子类中实现所有方法适用于方法数量有限且变化不频繁的场景,代码简单直观,性能较好,但代码冗余、维护复杂且灵活性不足。
使用消息转发模拟多继承则适用于需要高扩展性和灵活性的场景,可以在运行时动态处理不同父类的方法调用,减少代码冗余,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果需要模拟的多继承关系复杂且方法数量较少,直接在子类中实现方法可能更合适。如果需要高扩展性和灵活性,消息转发则是更好的选择。
4. 实现懒加载或延迟初始化
应用场景:使用消息转发机制,可以实现一些复杂的懒加载或延迟初始化逻辑。
示例:
@interface LazyLoadingObject : NSObject
@property (nonatomic, strong) id heavyObject;
@end
@implementation LazyLoadingObject
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (!self.heavyObject) {
self.heavyObject = [HeavyObject new];
}
if ([self.heavyObject respondsToSelector:aSelector]) {
return self.heavyObject;
}
return [super forwardingTargetForSelector:aSelector];
}
@end
LazyLoadingObject *lazyObject = [LazyLoadingObject new];
[lazyObject someHeavyMethod]; // HeavyObject 会在第一次调用时初始化
在实现懒加载或延迟初始化时,如果选择不使用消息转发而直接在类中预定义所有需要的方法,那么这种方法的优缺点如下:
直接预定义方法
优点:
-
代码直观:
- 所有方法在编译时已经定义,代码清晰易懂,便于维护。
-
编译时检查:
- 编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
-
性能:
- 方法在编译时已经确定,不需要在运行时进行方法解析或转发,调用速度较快。
缺点:
-
代码冗余:
- 需要在类中预定义懒加载的属性和方法,导致代码冗余。
-
维护复杂:
- 当需要修改或添加新的懒加载属性时,需要在类中添加或修改相应的方法,增加了维护成本。
-
灵活性不足:
- 无法在运行时动态处理属性的初始化和加载,限制了灵活性和扩展性。
示例:
直接在类中预定义懒加载的属性和方法。
@interface MyClass : NSObject
@property (nonatomic, strong) NSString *lazyProperty;
@end
@implementation MyClass
- (NSString *)lazyProperty {
if (!_lazyProperty) {
_lazyProperty = [[NSString alloc] initWithFormat:@"Lazy loaded string"];
}
return _lazyProperty;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [MyClass new];
NSLog(@"%@", [myObject lazyProperty]); // Lazy loading happens here
}
return 0;
}
在这种情况下,需要在 MyClass
中预定义 lazyProperty
的懒加载方法。
使用消息转发实现懒加载或延迟初始化
优点:
-
高灵活性:
- 可以在运行时动态处理属性的初始化和加载,实现更灵活的懒加载机制。
-
代码简洁:
- 不需要在类中预定义所有可能的懒加载属性和方法,代码更简洁。
-
减少代码冗余:
- 只有在需要时才会执行懒加载逻辑,减少不必要的代码和内存占用。
缺点:
-
复杂性增加:
- 消息转发增加了代码的复杂性,可能需要处理更多的运行时错误。
-
性能开销:
- 在运行时解析和转发方法会有一定的性能开销。
-
缺少编译时检查:
- 编译器无法对动态方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用消息转发实现懒加载。
@interface MyClass : NSObject
@property (nonatomic, strong) NSMutableDictionary *lazyProperties;
@end
@implementation MyClass
- (instancetype)init {
if (self = [super init]) {
_lazyProperties = [NSMutableDictionary dictionary];
}
return self;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *selectorString = NSStringFromSelector(aSelector);
if ([selectorString hasPrefix:@"lazy"]) {
id value = self.lazyProperties[selectorString];
if (!value) {
if ([selectorString isEqualToString:@"lazyProperty"]) {
value = [[NSString alloc] initWithFormat:@"Lazy loaded string"];
[self.lazyProperties setObject:value forKey:selectorString];
}
}
return value ? self : nil;
}
return [super forwardingTargetForSelector:aSelector];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
NSString *selectorString = NSStringFromSelector(sel);
if ([selectorString hasPrefix:@"lazy"]) {
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
return [super methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
NSString *selectorString = NSStringFromSelector([invocation selector]);
id value = self.lazyProperties[selectorString];
if (value) {
[invocation setReturnValue:&value];
} else {
[super forwardInvocation:invocation];
}
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [MyClass new];
NSLog(@"%@", [myObject performSelector:@selector(lazyProperty)]); // Lazy loading happens here
}
return 0;
}
总结
直接预定义方法适用于懒加载属性数量有限且变化不频繁的场景,代码简单直观,性能较好,但灵活性不足,代码冗余且维护复杂。
使用消息转发实现懒加载则适用于需要高扩展性和灵活性的场景,可以在运行时动态处理属性的初始化和加载,减少代码冗余,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果懒加载的属性较少且变化不大,直接预定义方法可能更合适。如果需要高扩展性和灵活性,消息转发则是更好的选择。
5. 消息转发用于调试和日志记录
应用场景:通过消息转发,可以拦截对象的所有方法调用,用于调试和记录日志。
示例:
@interface LoggingObject : NSObject
@property (nonatomic, strong) id target;
@end
@implementation LoggingObject
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"Method %@ is called", NSStringFromSelector(aSelector));
return self.target;
}
@end
LoggingObject *loggingObject = [LoggingObject new];
loggingObject.target = realObject;
[loggingObject someMethod]; // 调用时会记录日志
总结
消息转发机制在iOS开发中有广泛的应用场景,特别是在动态代理、方法解析、多继承模拟、懒加载和调试日志记录等方面。通过巧妙地使用消息转发,开发者可以实现更加灵活和动态的应用逻辑。
在调试和日志记录中,如果选择不使用消息转发而直接在类中编写相应的日志记录代码,那么这种方法的优缺点如下:
直接编写日志记录代码
优点:
-
代码直观:
- 日志记录代码直接嵌入目标方法中,代码清晰易懂,便于维护。
-
编译时检查:
- 编译器可以对所有方法进行类型检查和语法检查,减少运行时错误。
-
性能:
- 方法在编译时已经确定,不需要在运行时进行方法解析或转发,调用速度较快。
缺点:
-
代码冗余:
- 需要在每个方法中手动添加日志记录代码,导致代码冗余。
-
维护复杂:
- 当需要修改日志记录逻辑时,需要在每个方法中同步修改,增加了维护成本。
-
侵入性强:
- 日志记录代码紧耦合在业务逻辑中,影响代码的清晰度和可读性。
示例:
直接在方法中编写日志记录代码。
@interface MyClass : NSObject
- (void)method1;
- (void)method2;
@end
@implementation MyClass
- (void)method1 {
NSLog(@"Entering method1");
// 方法的实际逻辑
NSLog(@"Exiting method1");
}
- (void)method2 {
NSLog(@"Entering method2");
// 方法的实际逻辑
NSLog(@"Exiting method2");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [MyClass new];
[myObject method1];
[myObject method2];
}
return 0;
}
在这种情况下,需要在 method1
和 method2
中手动添加日志记录代码。
使用消息转发实现调试和日志记录
优点:
-
高灵活性:
- 可以在运行时动态处理方法调用,统一实现日志记录逻辑,实现更灵活的调试和日志记录机制。
-
减少代码冗余:
- 不需要在每个方法中手动添加日志记录代码,减少代码冗余。
-
非侵入性:
- 日志记录逻辑通过消息转发实现,不影响业务逻辑代码的清晰度和可读性。
缺点:
-
复杂性增加:
- 消息转发增加了代码的复杂性,可能需要处理更多的运行时错误。
-
性能开销:
- 在运行时解析和转发方法会有一定的性能开销。
-
缺少编译时检查:
- 编译器无法对动态方法进行类型和语法检查,增加了运行时错误的可能性。
示例:
使用消息转发实现统一的日志记录。
@interface MyClass : NSObject
@end
@implementation MyClass
- (void)method1 {
// 方法的实际逻辑
}
- (void)method2 {
// 方法的实际逻辑
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
return nil; // 让 `methodSignatureForSelector` 和 `forwardInvocation` 处理
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
// 创建一个通用的方法签名
signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
NSString *selectorName = NSStringFromSelector(selector);
NSLog(@"Entering %@", selectorName);
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
} else {
[super forwardInvocation:anInvocation];
}
NSLog(@"Exiting %@", selectorName);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [MyClass new];
[myObject method1];
[myObject method2];
}
return 0;
}
总结
直接编写日志记录代码适用于方法数量有限且变化不频繁的场景,代码简单直观,性能较好,但代码冗余、维护复杂且侵入性强。
使用消息转发实现调试和日志记录则适用于需要高扩展性和灵活性的场景,可以在运行时动态处理方法调用,统一实现日志记录逻辑,减少代码冗余,但增加了代码的复杂性和运行时的性能开销。
在实际应用中,可以根据具体需求选择合适的方法。如果需要记录的日志较少且方法数量不多,直接编写日志记录代码可能更合适。如果需要高扩展性和灵活性,消息转发则是更好的选择。
methodSignatureForSelector
methodSignatureForSelector:
是 Objective-C 中用于消息转发机制的一部分。它在以下几种场景中特别有用:
1. 消息转发机制的实现
当一个对象收到一个无法处理的消息时,Objective-C 运行时系统会先调用 forwardingTargetForSelector:
方法。如果该方法返回 nil
或者返回的对象也无法处理该消息,运行时系统会调用 methodSignatureForSelector:
方法来获取方法签名。如果返回一个有效的 NSMethodSignature
对象,接着会调用 forwardInvocation:
方法来处理这个消息。
示例:
@interface MyClass : NSObject
@end
@implementation MyClass
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
if (!signature) {
signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if ([self respondsToSelector:selector]) {
[anInvocation invokeWithTarget:self];
} else {
[super forwardInvocation:anInvocation];
}
}
@end
2. 动态方法解析和消息转发
在某些场景下,你可能希望动态地处理某些方法调用,而不需要在编译时明确地定义这些方法。使用 methodSignatureForSelector:
可以让你灵活地定义方法签名,并在 forwardInvocation:
中进行具体的消息处理。
示例:
@interface DynamicMethodHandler : NSObject
@end
@implementation DynamicMethodHandler
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(dynamicMethod:)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
SEL selector = [anInvocation selector];
if (selector == @selector(dynamicMethod:)) {
NSString *arg;
[anInvocation getArgument:&arg atIndex:2];
NSLog(@"Dynamic method called with argument: %@", arg);
} else {
[super forwardInvocation:anInvocation];
}
}
@end
3. 实现代理方法或接口的扩展
在实现一些框架或库时,可能需要动态地处理代理方法或接口的调用。通过 methodSignatureForSelector:
和 forwardInvocation:
,可以在运行时动态地响应这些方法调用,而不需要在编译时明确地实现所有的代理方法。
示例:
@interface MyProxy : NSProxy
@property (nonatomic, strong) id target;
@end
@implementation MyProxy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [[self target] methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
if ([[self target] respondsToSelector:[invocation selector]]) {
[invocation invokeWithTarget:[self target]];
} else {
[self doesNotRecognizeSelector:[invocation selector]];
}
}
@end
4. 调试与日志记录
在调试和日志记录场景下,可以使用 methodSignatureForSelector:
和 forwardInvocation:
来记录方法的调用情况。这样可以在不改变原有业务逻辑的情况下实现日志记录功能。
示例:
@interface LoggingProxy : NSProxy
@property (nonatomic, strong) id target;
@end
@implementation LoggingProxy
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [[self target] methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL selector = [invocation selector];
NSLog(@"Before calling %@", NSStringFromSelector(selector));
if ([[self target] respondsToSelector:selector]) {
[invocation invokeWithTarget:[self target]];
}
NSLog(@"After calling %@", NSStringFromSelector(selector));
}
@end
5. 实现懒加载或延迟初始化
在实现懒加载或延迟初始化时,可以利用 methodSignatureForSelector:
来动态创建方法签名,并在 forwardInvocation:
中进行实际的初始化和调用。
示例:
@interface LazyLoader : NSObject
@property (nonatomic, strong) NSMutableDictionary *lazyProperties;
@end
@implementation LazyLoader
- (instancetype)init {
if (self = [super init]) {
_lazyProperties = [NSMutableDictionary dictionary];
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSString *selectorString = NSStringFromSelector(aSelector);
if ([selectorString hasPrefix:@"lazy"]) {
return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
NSString *selectorString = NSStringFromSelector([invocation selector]);
id value = self.lazyProperties[selectorString];
if (!value) {
value = [[NSString alloc] initWithFormat:@"Lazy loaded %@", selectorString];
[self.lazyProperties setObject:value forKey:selectorString];
}
[invocation setReturnValue:&value];
}
@end
总结
methodSignatureForSelector:
主要用于增强 Objective-C 动态特性,使得开发者可以在运行时动态处理方法调用。它在需要动态方法解析、消息转发、调试与日志记录、代理方法实现以及懒加载等场景下非常有用。通过合理使用 methodSignatureForSelector:
和 forwardInvocation:
,可以实现更加灵活和动态的代码逻辑。
示例场景:日志记录
假设我们有一个类 Logger
,我们希望在所有方法调用前后记录日志。可以使用消息转发机制来实现这一点。
代码实现
- 定义 Logger 类
@interface Logger : NSObject
@end
@implementation Logger
- (void)logBefore:(SEL)selector {
NSLog(@"Before calling %@", NSStringFromSelector(selector));
}
- (void)logAfter:(SEL)selector {
NSLog(@"After calling %@", NSStringFromSelector(selector));
}
@end
- 定义目标类
假设我们有一个目标类 MyClass
,我们希望在其方法调用前后记录日志。
@interface MyClass : NSObject
- (void)exampleMethod;
@end
@implementation MyClass
- (void)exampleMethod {
NSLog(@"Executing exampleMethod");
}
@end
- 消息转发机制实现
在 MyClass
中实现消息转发机制。
#import <objc/runtime.h>
@implementation MyClass {
Logger *_logger;
}
- (instancetype)init {
self = [super init];
if (self) {
_logger = [Logger new];
}
return self;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return NO; // 不动态添加方法,将消息转发给下一步处理
}
- (id)forwardingTargetForSelector:(SEL)selector {
return nil; // 不指定转发对象,进入完整消息转发流程
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return signature;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL selector = [invocation selector];
[_logger logBefore:selector];
if ([self respondsToSelector:selector]) {
[invocation invokeWithTarget:self];
} else {
[self doesNotRecognizeSelector:selector];
}
[_logger logAfter:selector];
}
@end
- 测试代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *obj = [MyClass new];
[obj exampleMethod];
}
return 0;
}
运行结果
Before calling exampleMethod
Executing exampleMethod
After calling exampleMethod
解释
- 动态方法解析:通过重写
+resolveInstanceMethod:
方法,返回NO
,表示不动态添加方法。 - 快速转发:通过重写
-forwardingTargetForSelector:
方法,返回nil
,表示不指定转发对象,进入完整消息转发流程。 - 正常转发:
- 方法签名:通过重写
-methodSignatureForSelector:
方法,获取方法签名。如果父类没有实现该方法,则返回一个默认的方法签名。 - 消息转发:通过重写
-forwardInvocation:
方法,将消息转发给Logger
对象进行日志记录,然后再调用原方法。
- 方法签名:通过重写
通过这种方式,我们可以在方法调用前后插入额外的逻辑,实现日志记录功能。这是一个使用消息转发机制的典型应用场景。