【问题标题】:Is it possible to redirect system API in Objective-C?是否可以在 Objective-C 中重定向系统 API?
【发布时间】:2011-06-24 03:10:09
【问题描述】:

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

现在我尝试使用方法调配。但似乎NSString在main运行时没有加载,我将swizzling方法移动到MyAppDelegate。之后class_getInstanceMethod([NSString class], @selector(stringWithFormat:)) 不是零。但是,swizzling 方法仍然不起作用,因为class_getInstanceMethod([NSString class], @selector(override_stringWithFormat:)) 仍然为零,我应该如何解决这个问题?

谢谢, 俱乐部

@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:)];

}

【问题讨论】:

    标签: iphone objective-c ios xcode cocoa


    【解决方案1】:

    不确定您要做什么,这个问题有点不清楚。另外,不要尝试从 NSString 继承,它是类集群的一部分,因此它比仅覆盖一个方法要复杂得多。你甚至必须 implement 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 说“不要这样做”。并且有充分的理由 - 如果有人调用 [NSMutableString stringWithFormat:] 或分配一个 NSString 然后调用 -initWithFormat: 会发生什么?有很多有效的路径可以创建一个格式的字符串,你会很痛苦地试图维护拦截所有这些的代码。我会鼓励你重新考虑这个设计——无论你试图解决什么问题,都可能有一种更容易和更易于维护的方式来实现它。例如,添加您自己的字符串工厂。

    【讨论】:

    • RyanR,感谢您的信息。我知道类别,但它创建了一种新方法,并且重写我的代码以使用新方法很糟糕,因为我在任何地方都使用 NSString。所以我只想导入我的新 MyNSString ,它将替换现有的 NSString 方法来挖掘。如果我想使用原始版本,它会更灵活。在 C++ 中,我可以使用 #typedef 将诸如 printf 之类的调用重定向到我自己的,但我不知道如何在 Objective-c 中做到这一点。
    • 嗨 RyanR,我已经更新了我的问题,但仍然卡在 swizzling 方法中,您对此有什么建议吗?再次感谢。
    • 可能很难重写你的代码来使用一个新的类,但是看看你现在遇到了多少麻烦来做一些可能会给你带来更大问题的事情。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-14
    相关资源
    最近更新 更多