Objective-C RuntimeExamples
https://github.com/storoj/objc_runtime_examples
Example 1.1Observe value changes for all properties of a given class.
Class
Property Method
SEL IMP
getter
setter(objc_property_t)
Sample class@interface ELSample : NSObject !@property (nonatomic, strong) NSArray *values; @property (nonatomic, assign, getter=isValid, setter=setValidCustom:) BOOL valid; !@end !@implementation ELSample !@synthesize valid = ivar_valid; !@end
ClassClass sampleClass1 = [ELSample class]; Class sampleClass2 = NSClassFromString(@"ELSample");
#import <objc/runtime.h> !Class sampleClass3 = objc_getClass(“ELSample"); Class sampleClass4 = objc_lookUpClass(“ELSample");
Propertiesunsigned int propertyCount = 0; objc_property_t *properties = class_copyPropertyList( sampleClass, &propertyCount ); !for (unsigned int i=0; i<propertyCount; i++) { objc_property_t property = properties[i]; ! const char *propertyName = property_getName(property); NSLog(@"%s", propertyName); } free(properties);
Output: values valid
Property attributeschar const *attributesString = property_getAttributes(property); NSLog(@"%s %s", propertyName, attributesString);
Code MeaningT type (c - char, @ - id)N nonatomicG getter selectorS setter selectorV ivar name& strong/retain
More info
Output: values T@"NSArray",&,N,V_values valid Tc,N,GisValid,SsetValidCustom:,Vivar_valid
Property setter/** * @return The value string of the attribute attributeName if it exists in * property, nil otherwise. */ !char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
char *setterAttributeValue = property_copyAttributeValue(property, "S");
if (NULL == setterAttributeValue) { "set" + capitalize(property_getName(property)) + ":" }
// do not forget! free(setterAttributeValue);
Method invocationid objc_msgSend(id self, SEL _cmd, ...)
ELSample *sample = [ELSample new]; sample.valid = YES;
objc_msgSend(sample, @selector(setValidCustom:), YES);
id objc_msgSend(id self, SEL _cmd, ...) { IMP methodFunction = [[self class] methodForSelector:_cmd]; return methodFunction(self, _cmd, ...); }
[sample setValidCustom:YES];
MethodMethod *class_copyMethodList(Class cls, unsigned int *outCount) !Method class_getInstanceMethod(Class cls, SEL name) !Method class_getClassMethod(Class cls, SEL name)
IMP method_getImplementation(Method m) !/** * @return The previous implementation of the method. */ IMP method_setImplementation(Method m, IMP imp)
Replacing method implementation
@interface Sample : NSObject - (id)swizzleMe:(NSInteger)arg; @end
typedef id (*IMP)(id, SEL, ...);
id SwizzleFunction(id self, SEL _cmd, NSInteger arg) { return @(arg+5); } !method_setImplementation(method, (IMP)SwizzleFunction);
IMP method_setImplementation(Method m, IMP imp)
Method method = class_getInstanceMethod(class, @selector(swizzleMe:));
Blocksid(^block)(id, id) = ^id(id self, id arg) { NSLog(@"arg: %@", arg); return nil; };
IMP blockImp = imp_implementationWithBlock(block);
Block can capture original IMP to call it later.
NSString *setterName = property_getSetterName(property); SEL setterSelector = NSSelectorFromString(setterName); !Method method = class_getInstanceMethod(class, setterSelector); IMP originalImp = method_getImplementation(method); !id(^block)(id, ...) = ^id(id self, ...) { NSLog(@"will change %s", property_getName(property)); return originalImp(self, setterSelector, ...); }; !IMP newImp = imp_implementationWithBlock(block); !method_setImplementation(method, newImp);
return originalImp(self, setterSelector, ...);
BUT…
(self, setterSelector, ...);
... F*CK
Cast to concrete typevoid(^block)(id, id) = ^void(id self, id arg) { ((void(*)(id, SEL, id))originalImp)(self, selector, arg); };
// 0 - self, 1 - _cmd char *argumentEncoding = method_copyArgumentType(method, 2);
id block = nil; if (0 == strcmp(argumentEncoding, @encode(NSInteger))) { block = ^void(id self, SEL selector, NSInteger arg) { ((void(*)(id, SEL, NSInteger))originalImp)(self, sel, arg); }; } else if (...) { ... }
Example 1.2
_objc_msgForwardid _objc_msgForward(id receiver, SEL sel, ...) !will call forwardInvocation: with prepared NSInvocation object
Method method = class_getInstanceMethod(class, setterSelector); !IMP originalImp = method_setImplementation(method, _objc_msgForward);
NSString *internalSetterName = [setterName stringByAppendingString:@"_internal"]; SEL internalSelector = NSSelectorFromString(internalSetterName); char const *types = method_getTypeEncoding(method); class_addMethod(class, internalSelector, originalImp, types);
[capturedSelectorsSet addObject:setterName];
Replace forwardInvocation:Method method = class_getInstanceMethod(class, @selector(forwardInvocation:)); IMP originalImp = method_getImplementation(method); !void(^block)(id, NSInvocation *) = ^void(id self, NSInvocation *invocation) { NSString *selectorName = NSStringFromSelector([invocation selector]); ! if ([capturedSelectorsSet containsObject:selectorName]) { NSString *internalSelectorName = [selectorName stringByAppendingString:@“_internal"]; ! [invocation setSelector:NSSelectorFromString(internalSelectorName)]; [invocation invoke]; } else { ((void(*)(id, SEL, NSInvocation *)) originalImp)(self, @selector(forwardInvocation:), invocation); } }; !method_setImplementation(method, imp_implementationWithBlock(block));
Example 1.3
Replace the classClass object_setClass(id obj, Class cls)NSNumber *number = [NSNumber numberWithDouble:5.1f]; !NSLog(@"number: %@ %@ %p", number, class, (__bridge void *)number); // number: 5.099999904632568 __NSCFNumber 0x8d464c0 !object_setClass(number, [NSObject class]); NSLog(@"number: %@", number); // number: <NSObject: 0x8d464c0>
object_setClass(number, class); NSLog(@"number: %@", number); // number: 5.099999904632568
Class class = [number class];
NSProxy“… Subclasses of NSProxy can be used to implement transparent distributed messaging…”
Some people, when confronted with a problem, think "I know, I'll use NSProxy." Now they have two problems.
@interface Observer : NSProxy + (instancetype)observerWithObject:(id)object; @end !@implementation Observer !- (void)forwardInvocation:(NSInvocation *)anInvocation { Class realClass = objc_getAssociatedObject(self, "realClass"); SEL selector = [anInvocation selector]; objc_property_t property = class_getPropertyWithSetter(realClass, selector); if (NULL != property) { NSLog(@"changing %s", property_getName(property)); } ! Class currentClass = [self class]; object_setClass(self, realClass); [anInvocation invoke]; object_setClass(self, currentClass); } !- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { Class realClass = objc_getAssociatedObject(self, "realClass"); NSMethodSignature *sig = [realClass instanceMethodSignatureForSelector:sel]; return signature; } !@end
Example 2Get outlets and actions runtime
UIRuntimeOutletConnection UIRuntimeEventConnection
UIRuntimeOutletCollectionConnection
UIRuntimeConnection
@interface UIRuntimeConnection : NSObject <NSCoding> !@property (nonatomic, strong) id destination; @property (nonatomic, strong) NSString *label; @property (nonatomic, strong) id source; !- (void)connect; - (void)connectForSimulator; !@end !@interface UIRuntimeEventConnection : UIRuntimeConnection !@property (nonatomic, readonly) SEL action; @property (nonatomic, assign) UIControlEvents eventMask; @property (nonatomic, readonly) id target; !@end !@interface UIRuntimeOutletConnection : UIRuntimeConnection @end
kennytmhttps://github.com/kennytm/iphone-private-frameworks
class-dump-zhttps://code.google.com/p/networkpx/wiki/class_dump_z
runtimeClass *objc_copyClassList(unsigned int *outCount) Protocol * __unsafe_unretained *objc_copyProtocolList(unsigned int *outCount) Ivar *class_copyIvarList(Class cls, unsigned int *outCount) Method *class_copyMethodList(Class cls, unsigned int *outCount) Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount) objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
SEL selector = NSSelectorFromString(@"initWithCoder:"); !Class outletClass = NSClassFromString(@"UIRuntimeOutletConnection"); Method outletMethod = class_getInstanceMethod(outletClass, selector); method_swizzle(outletMethod, ...); !Class eventClass = NSClassFromString(@"UIRuntimeEventConnection"); Method eventMethod = class_getInstanceMethod(eventClass, selector); method_swizzle(eventMethod, ...)
NO!
Why not?.. Looks good.
SEL selector = NSSelectorFromString(@"initWithCoder:"); !Class outletClass = NSClassFromString(@"UIRuntimeOutletConnection"); Method outletMethod = class_getInstanceMethod(outletClass, selector); !Class eventClass = NSClassFromString(@"UIRuntimeEventConnection"); Method eventMethod = class_getInstanceMethod(eventClass, selector); !Class baseClass = NSClassFromString(@"UIRuntimeConnection"); Method baseMethod = class_getInstanceMethod(baseClass, selector); !baseMethod == outletMethod baseMethod != eventMethod
@implementation UIRuntimeEventConnection !- (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { // decode eventMask } return self; } !@end
Copy method before swizzlingSEL selector = NSSelectorFromString(@"initWithCoder:"); !Class baseClass = NSClassFromString(@"UIRuntimeConnection"); Method baseMethod = class_getInstanceMethod(baseClass, selector); !Class outletClass = NSClassFromString(@"UIRuntimeOutletConnection"); class_addMethod(outletClass, selector, method_getImplementation(baseMethod), method_getTypeEncoding(baseMethod)); !Method outletMethod = class_getInstanceMethod(outletClass, selector); !method_swizzle(outletMethod, ...);
Be a man!SEL selector = NSSelectorFromString(@"initWithCoder:"); !Class baseClass = NSClassFromString(@"UIRuntimeConnection"); Method baseMethod = class_getInstanceMethod(baseClass, selector); !Class outletClass = NSClassFromString(@"UIRuntimeOutletConnection"); !id(^initWithCoderBlock)(id, id) = ^id(id aSelf, id aDecoder) { struct objc_super _super = { .receiver = aSelf, .super_class = [[aSelf class] superclass] }; ! return objc_msgSendSuper(&_super, selector, aDecoder); }; IMP initWithCoderImp = imp_implementationWithBlock(initWithCoderBlock); !class_addMethod(outletClass, selector, initWithCoderImp, method_getTypeEncoding(baseMethod));
OutputUIStoryboardScene got ELViewController *sceneViewController
ELViewController got UIStoryboard *storyboard
UIControlEventTouchUpInside -[ELViewController buttonTap:(UIButton *)]
UIControlEventEditingChanged -[ELViewController datePickerEditingChanged:(UIDatePicker *)]
UITableView got ELViewController *dataSource
ELViewController got UIDatePicker *datePicker
UITableView got ELViewController *delegate
ELViewController got UILabel *label
ELViewController got UIView *view
Useful links
• Property introspection • imp_implementationWithBlock • class loading and initialization • let's build objc_msgSend • objc_msgSend ARM assembly • private frameworks • Objective-C Runtime Programming Guide