سؤال

I use a specific API, which has some class, for example ClassA.

ClassA has a property importantProperty, ivar = _importantProperty, setter = setImportantProperty

What I need is actually to handle in debug when is property is changed, and what values are set to this property and print the stacktrace.

I can not inherit this class and override this method in my case, because it has defined class.

So I created a debug category, which overrides setImportantProperty method and now I can handle the stacktrace and values changes, but this method makes original method unreachable, and ivar value can not be changed. Is there any way to change this ivar?

Here is my method:

@implementation ClassA (Test)

-(void) setImportantProperty:(id) newValue {

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

@end

So the question is there any way to implement such code _importantProperty = newValue in my method or are there any other ways to use in my case?

Thanks in advance!

هل كانت مفيدة؟

المحلول

As @vikingosegundo suggested, you could use method 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

Here we declare a new method swizzled_setImportantProperty:, and at runtime we swap it's implementation with setImportantProperty: implementation. So that when we call setImportantProperty: in code, the implementation for swizzled_setImportantProperty will be called and vice versa.

That's why when we call swizzled_setImportantProperty: inside the swizzled_setImportantProperty: implementation, it won't call an endless recursion, because setImportantProperty: implementation will be called. Just what we need.

UPDATE: Since overriding a +load method might cause problems if it have already been implemented (or if might be implemented in the future by the library creators), there is a better option suggested by @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);
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top