Можно ли перенаправить системный API в Objective-C? - PullRequest
2 голосов
/ 24 июня 2011

По некоторым причинам я должен сохранить исходный код без изменений, но попытаться перенаправить системный API в мой код, а затем перезвонить на исходный.Например, я хотел бы сделать что-то еще в [NSString stringWithFormat:].

Теперь я пытаюсь использовать метод Swizzling.Но похоже, что NSString не загружается во время выполнения main, я перехожу методом swizzling на MyAppDelegate.После этого class_getInstanceMethod([NSString class], @selector(stringWithFormat:)) не ноль.Тем не менее, метод Swizzling все еще не работает, потому что 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 Ответ

2 голосов
/ 24 июня 2011

Не уверен, что вы пытаетесь сделать, вопрос немного неясен. Кроме того, не пытайтесь наследовать от NSString, это часть кластера классов, так что это намного сложнее, чем просто переопределить один метод. Вы даже должны пополнить хранилище самостоятельно . Сложно очень быстро.

Существует два способа добавления методов в существующий класс в Objective-C: Категории и Метод Swizzling .

Категории позволяют вам определять новый метод в существующем классе, вы не можете надежно заменить метод, используя категории. Например:

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

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

Опять же, этот метод не будет надежно заменять существующий метод в классе.

Для этого вам нужен метод Swizzling. Предостережение - это расширенная функция среды выполнения, и очень легко погрузиться в отладочный кошмар, поэтому используйте ее с осторожностью. Swizzling позволяет заменить реализацию одного метода другим методом с той же сигнатурой. Используя некоторые приемы, вы также можете вызвать супер реализацию. Мэтту Галлахеру обычно приписывают лучшую реализацию метода swizzling , поскольку он позволяет надежно вызывать ранее существующую реализацию (называемую последующей реализацией). Взгляните на связанную статью «Какао с любовью», например, об использовании. Ваш отредактированный код, вероятно, не работает, потому что вы пытаетесь использовать метод класса, но используете функцию getInstanceMethod. Вместо этого используйте class_getClassMethod (). Кроме того, вам необходимо переименовать вашу категорию в метод stringWithFormat, он не должен конфликтовать с реальным именем метода. Последнее замечание: я никогда не видел, чтобы метод swizzling использовался в методе с переменными аргументами (...), и this C q & a заставляет меня поверить, что это невозможно (попробуйте this сообщение как возможное решение). В качестве академического упражнения, вот как это будет выглядеть , вероятно :

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 :? Существует множество допустимых путей для создания строки с форматом, и вам будет ужасно сложно поддерживать код, который перехватывает все из них. Я бы посоветовал вам переосмыслить структуру этого - независимо от того, какую проблему вы пытаетесь решить, вероятно, существует гораздо более простой и более приемлемый способ ее достижения. Например, добавление собственной фабрики строк.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...