是否可以在Objective-C中重定向系统API?

时间:2011-06-24 03:10:10

标签: iphone objective-c ios xcode cocoa

由于某些原因,我必须保留原始代码而不进行修改,但尝试将系统API重定向到我的代码中,然后回调原始代码。例如,我想在[NSString stringWithFormat:]中做更多的事情。

现在我尝试使用方法调配。但似乎在main的运行时期间没有加载NSString,我将混合方法移动到MyAppDelegate。之后class_getInstanceMethod([NSString class], @selector(stringWithFormat:))不是零。但是,由于class_getInstanceMethod([NSString class], @selector(override_stringWithFormat:))仍然为零,因此混合方法仍无效,我该如何解决?

谢谢, CLU

@interface NSString (MyNSString)

+ (id)stringWithFormat:(NSString *)format, ...;
@end


@implementation NSString (MyNSString)
+ (id)stringWithFormat:(NSString *)format, ... {
   //do something...
   [NSString stringWithFormat:format];
}
@end

以下是MyAppDelegate中的代码

#import "MyNSString.h"
-(void) MethodSwizzle:(Class)c replaceOrig:(SEL) origSEL withNew:(SEL) overrideSEL {
    Method origMethod = class_getInstanceMethod(c, origSEL);
    Method overrideMethod = class_getInstanceMethod(c, overrideSEL);

    if(class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod)))
    class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
else
    method_exchangeImplementations(origMethod, overrideMethod);    
}

- (BOOL) application:(UIApplication*) application didFinishLaunchingWithOptions:(NSDictionary *) options {
    ...
    //Unit test
    NSString *a=[NSString override_stringWithFormat:@"Test"]; //returned something
    Method b = class_getInstanceMethod([NSString class], @selector(override_stringWithFormat:)); //return nil;

    //do something...
    [self MethodSwizzle:[NSString class] replaceOrig:@selector(stringWithFormat:) withNew:@selector(override_stringWithFormat:)];

}

1 个答案:

答案 0 :(得分:2)

不确定你要做什么,这个问题有点不清楚。另外,不要尝试从NSString继承它,它是类集群的一部分,所以它只是覆盖一个方法要复杂得多。你甚至不得不mplement the storage yourself。快速复杂。

有两种方法可以在Objective-C中为现有类添加方法:Categoriesmethod Swizzling

类别允许您在现有类上定义新方法,您不能可靠地使用类别替换方法。例如:

@interface NSString (MyStuff)
- (id)mySpecialInit;
@end;

@implementation NSString (MyStuff)
/* implementation of -(id)mySpecialInit would go here */
@end

同样,此方法无法可靠地替换类上的现有方法。

为此你需要方法调配。警告 - 这是运行时的一个高级功能,它很容易让你自己陷入调试的噩梦,所以请谨慎使用。 Swizzling允许您使用相同签名的另一种方法替换一种方法的实现。使用一些技巧,您也可以调用超级实现。 Matt Gallagher通常被认为拥有best implementation of method swizzling,因为它允许可靠地调用预先存在的实现(称为超级实现)。看一下关于Cocoa with Love的链接文章,例如用法。您编辑的代码可能不起作用,因为您试图调用类方法,但使用getInstanceMethod函数。请改用class_getClassMethod()。此外,您需要重命名类别stringWithFormat方法,不得与真实方法名称冲突。最后一点需要注意 - 我从未见过在使用可变参数(...)的方法上使用方法调整,而this C q&a让我相信这是不可能的(尽可能尝试this post解)。作为一个学术练习,这里可能可能

<强>的NSString + MyMethods.h

@interface NSString (MyMethods) 
 + (id)clu_stringWithFormat:(NSString *)format,...;
@end

<强>的NSString + MyMethods.m:

@implementation NSString (MyMethods) 
+ (id)clu_stringWithFormat:(NSString *)format,... {
  //Do your stuff here
  //call supersequent implementation
  //again, I have no idea if this will work as is, you might need to tweak how it passes the variable argument list
  invokeSupersequent(format); 
}

+(void)load {
  //do your method swizzle in here, this is called one time only
  Method origMethod = class_getClassMethod([NSString class], @selector(stringWithFormat:);
  Method replMethod = class_getClassMethod([NSString class], @selector(clu_stringWithFormat:);

  method_exchangeImplementations(origMethod, replMethod);  
}

鉴于这可能是不可能的,否则是一个非常糟糕的想法 - 你正在改变类集群中特定类的运作方式,Apple称“不要这样做”。并且有充分理由 - 如果someones调用[NSMutableString stringWithFormat:],或者分配NSString然后调用-initWithFormat:会发生什么?创建带有格式的字符串有很多有效的路径,你有一个悲惨的时间试图维护代码拦截所有这些。我鼓励你重新思考这个问题的设计 - 无论你想解决什么问题,实现它的方法都可能更简单,更易于维护。例如,添加自己的String Factory。