2013-12-25 2 views
1

Я использую определенный API, который имеет некоторый класс, например ClassA.Установить значение ivar из категории

ClassA имеет свойство importantProperty, Ивар = _importantProperty, сеттер = setImportantProperty

Что мне нужно на самом деле обрабатывать в отладке, когда это свойство изменяется, и какие значения установлены для этого свойства и распечатать трассировку стека.

Я не могу наследовать этот класс и переопределить этот метод в моем случае, потому что он определил класс.

Поэтому я создал категорию отладки, которая переопределяет метод setImportantProperty, и теперь я могу обрабатывать изменения stacktrace и значений, но этот метод делает исходный метод недоступным, а значение ivar не может быть изменено. Есть ли способ изменить этот ivar?

Вот мой метод:

@implementation ClassA (Test) 

-(void) setImportantProperty:(id) newValue { 

    NSLog(@"%@, %@", newValue, [NSThread callStackSymbols]); 
} 

@end 

Таким образом, вопрос, есть ли способ, чтобы реализовать такой код _importantProperty = newValue в моем методе или есть какие-либо другие способы использования в моем случае?

Заранее благодарен!

+1

звучит так, как будто вы ищете метод swizzeling. http://darkdust.net/writings/objective-c/method-swizzling – vikingosegundo

+2

Вы уверены, что не хотите, чтобы точка останова? – Chuck

+0

Как установить точку останова на метод, который находится в сторонней структуре? –

ответ

1

Как @vikingosegundo предложил, вы можете использовать метод swizzling:

#import "ClassA+Test.h" 
#import <objc/runtime.h> // Needed for method swizzling 

@implementation ClassA(Test) 

-(void) swizzled_setImportantProperty:(id) newValue { 
    [self swizzled_setImportantProperty: newValue]; //it is NOT an endless recursion. 
    NSLog(@"%@, %@", newValue, [NSThread callStackSymbols]); 
} 

+(void)load 
{ 
    Method original, swizzled; 
    original = class_getInstanceMethod(self, @selector(setImportantProperty:)); 
    swizzled = class_getInstanceMethod(self, @selector(swizzled_setImportantProperty:)); //UPDATE: missed a column here, sorry! 
    method_exchangeImplementations(original, swizzled); 
} 

@end 

Здесь мы объявляем новый метод swizzled_setImportantProperty:, и во время выполнения мы переставляем его реализации с setImportantProperty: реализации. Так что, когда мы вызываем setImportantProperty: в код, вызывается реализация для swizzled_setImportantProperty и наоборот.

Именно поэтому, когда мы вызываем swizzled_setImportantProperty: в реализацию swizzled_setImportantProperty:, это не вызовет бесконечную рекурсию, потому что будет называться реализация setImportantProperty:. Только то, что нам нужно.

UPDATE: Поскольку перекрытый метод +load может вызвать проблемы, если он уже реализованы (или могут быть реализованы в будущем библиотеки создателей), есть лучший вариант предложенный @JoshCaswell:

#import "ClassA+Test.h" 
#import <objc/runtime.h> // Needed for method swizzling 

@implementation ClassA(Test) 

-(void) swizzled_setImportantProperty:(id) newValue { 
    [self swizzled_setImportantProperty: newValue]; //it is NOT an endless recursion. 
    NSLog(@"%@, %@", newValue, [NSThread callStackSymbols]); 
} 

@end 

void swizzleSetImportantProperty(void) __attribute__((constructor)) 
{ 
    Method original, swizzled; 
    original = class_getInstanceMethod([ClassA class], @selector(setImportantProperty:)); 
    swizzled = class_getInstanceMethod([ClassA class], @selector(swizzled_setImportantProperty:)); //UPDATE: missed a column here, sorry! 
    method_exchangeImplementations(original, swizzled); 
} 
+0

'load' является потенциально проблематичным выбором здесь; будучи в какой-либо категории, он будет сжимать существующую реализацию на классе (так же, как сеттер был забит). Может быть лучше использовать конструктор. –

+0

Хорошая точка. Я продолжу свой ответ. Но сначала я должен понять это. Я думаю, что '+ load' является хорошим выбором, если класс не имеет собственной реализации. Что касается функции-конструктора, в этом случае мы скроем функцию-конструктор, не так ли? Вы имеете в виду 'init'? В этом случае может быть несколько 'init'. И мы должны обеспечить, чтобы мы обменивались реализациями только один раз (это не большая проблема, но мы должны помнить об этом). – FreeNickname

+0

Нет, я имею в виду функцию, а не метод: 'void swizzleSetImportantProperty (void) __attribute __ ((конструктор)) {/ * Swizzle * /}' См. [Атрибуты функции GCC] (http://gcc.gnu.org/onlinedocs /gcc-4.1.1/gcc/Function-Attributes.html#constructor). В этом случае, поскольку реализация исходного класса не находится под нашим контролем, мы не знаем, будет ли теперь «+ загрузка» реализована или будет в будущем. –

Смежные вопросы