Objective-C Runtime
Examples	

https://github.com/storoj/objc_runtime_examples
Example 1.1
Observe value changes for all properties of a given class.
Class
Property
(objc_property_t)

getter
setter

Method
SEL

IMP
Sample class
@interface ELSample : NSObject
!
@property (nonatomic, strong) NSArray *values;
@property (nonatomic,
assign,...
Class
Class sampleClass1 = [ELSample class];
Class sampleClass2 = NSClassFromString(@"ELSample");
#import <objc/runtime.h>...
Properties
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(
sampleClass,
&propertyCou...
Property attributes
char const *attributesString = property_getAttributes(property);
NSLog(@"%s %s", propertyName, attribu...
Property setter
/**
* @return The value string of the attribute attributeName if it exists in
* property, nil otherwise.
*...
Method invocation
id objc_msgSend(id self, SEL _cmd, ...)
ELSample *sample = [ELSample new];
sample.valid = YES;
[sample s...
Method
Method *class_copyMethodList(Class cls, unsigned int *outCount)
!
Method class_getInstanceMethod(Class cls, SEL nam...
Replacing method implementation
IMP method_setImplementation(Method m, IMP imp)
@interface Sample : NSObject
- (id)swizzle...
Blocks
id(^block)(id, id) = ^id(id self, id arg) {
NSLog(@"arg: %@", arg);
return nil;
};
IMP blockImp = imp_implementatio...
NSString *setterName = property_getSetterName(property);
SEL setterSelector = NSSelectorFromString(setterName);
!
Method m...
Cast to concrete type
void(^block)(id, id) = ^void(id self, id arg) {
((void(*)(id, SEL, id))originalImp)(self, selector, ...
Example 1.2
_objc_msgForward
id _objc_msgForward(id receiver, SEL sel, ...)
!
will call forwardInvocation: with prepared NSInvocation ...
Replace forwardInvocation:
Method method = class_getInstanceMethod(class, @selector(forwardInvocation:));
IMP originalImp ...
Example 1.3
Replace the class
Class object_setClass(id obj, Class cls)
NSNumber *number = [NSNumber numberWithDouble:5.1f];
!
Class cl...
NSProxy
“… Subclasses of NSProxy can be used to
implement transparent distributed messaging…”

Some people, when confronte...
@interface Observer : NSProxy
+ (instancetype)observerWithObject:(id)object;
@end

!

@implementation Observer

!

- (void...
Example 2
Get outlets and actions runtime
UIRuntimeConnection
UIRuntimeOutletConnection

UIRuntimeEventConnection

UIRuntimeOutletCollectionConnection
@interface UIRuntimeConnection : NSObject <NSCoding>
!
@property (nonatomic, strong) id destination;
@property (nonatomic,...
kennytm
https://github.com/kennytm/iphone-private-frameworks

class-dump-z
https://code.google.com/p/networkpx/wiki/class_...
SEL selector = NSSelectorFromString(@"initWithCoder:");

!

Class outletClass = NSClassFromString(@"UIRuntimeOutletConnect...
SEL selector = NSSelectorFromString(@"initWithCoder:");

!

Class outletClass = NSClassFromString(@"UIRuntimeOutletConnect...
Copy method before swizzling
SEL selector = NSSelectorFromString(@"initWithCoder:");

!

Class baseClass = NSClassFromStri...
Be a man!
SEL selector = NSSelectorFromString(@"initWithCoder:");

!

Class baseClass = NSClassFromString(@"UIRuntimeConne...
Output
UIStoryboardScene got ELViewController *sceneViewController
ELViewController got UIStoryboard *storyboard
UIControl...
Useful links
•
•
•
•
•
•
•

Property introspection	

imp_implementationWithBlock	

class loading and initialization	

let'...
Upcoming SlideShare
Loading in …5
×

«Objective-C Runtime в примерах» — Алексей Сторожев, e-Legion

2,399 views

Published on

Цель доклада — вдохновить разработчиков на более глубокое изучение возможностей Objective-C Runtime, показать способы исследования системы, воодушевить аудиторию на эксперименты.
В докладе показаны несколько примеров использования Objective-C Runtime для решения нетипичных задач. Первый пример - реализация простого KVO своими руками тремя способами. Вторым примером показана полезность исследования приватных классов. Рассказано, как во время работы программы получить информацию о протянутых IBOutlet и IBAction в xib и storyboard. В каждом примере присутствуют особенности реализации, на которых сделан акцент и показаны варианты решения или обхода.

  • Be the first to comment

«Objective-C Runtime в примерах» — Алексей Сторожев, e-Legion

  1. 1. Objective-C Runtime Examples https://github.com/storoj/objc_runtime_examples
  2. 2. Example 1.1 Observe value changes for all properties of a given class.
  3. 3. Class Property (objc_property_t) getter setter Method SEL IMP
  4. 4. 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
  5. 5. Class Class sampleClass1 = [ELSample class]; Class sampleClass2 = NSClassFromString(@"ELSample"); #import <objc/runtime.h> ! Class sampleClass3 = objc_getClass(“ELSample"); Class sampleClass4 = objc_lookUpClass(“ELSample");
  6. 6. Properties unsigned 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
  7. 7. Property attributes char const *attributesString = property_getAttributes(property); NSLog(@"%s %s", propertyName, attributesString); Output: values T@"NSArray",&,N,V_values valid Tc,N,GisValid,SsetValidCustom:,Vivar_valid Code Meaning T type (c - char, @ - id) N nonatomic G getter selector S setter selector V ivar name & strong/retain More info
  8. 8. 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);
  9. 9. Method invocation id objc_msgSend(id self, SEL _cmd, ...) ELSample *sample = [ELSample new]; sample.valid = YES; [sample setValidCustom:YES]; objc_msgSend(sample, @selector(setValidCustom:), YES); id objc_msgSend(id self, SEL _cmd, ...) { IMP methodFunction = [[self class] methodForSelector:_cmd]; return methodFunction(self, _cmd, ...); }
  10. 10. Method Method *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)
  11. 11. Replacing method implementation IMP method_setImplementation(Method m, IMP imp) @interface Sample : NSObject - (id)swizzleMe:(NSInteger)arg; @end Method method = class_getInstanceMethod(class, @selector(swizzleMe:)); typedef id (*IMP)(id, SEL, ...); id SwizzleFunction(id self, SEL _cmd, NSInteger arg) { return @(arg+5); } ! method_setImplementation(method, (IMP)SwizzleFunction);
  12. 12. Blocks id(^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.
  13. 13. 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); BUT… return originalImp(self, setterSelector, ...); (self, setterSelector, ...); ... F*CK
  14. 14. Cast to concrete type void(^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 (...) { ... }
  15. 15. Example 1.2
  16. 16. _objc_msgForward id _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];
  17. 17. 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));
  18. 18. Example 1.3
  19. 19. Replace the class Class object_setClass(id obj, Class cls) NSNumber *number = [NSNumber numberWithDouble:5.1f]; ! Class class = [number class]; 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
  20. 20. 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.
  21. 21. @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
  22. 22. Example 2 Get outlets and actions runtime
  23. 23. UIRuntimeConnection UIRuntimeOutletConnection UIRuntimeEventConnection UIRuntimeOutletCollectionConnection
  24. 24. @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
  25. 25. kennytm https://github.com/kennytm/iphone-private-frameworks class-dump-z https://code.google.com/p/networkpx/wiki/class_dump_z runtime Class *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)
  26. 26. 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.
  27. 27. 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
  28. 28. Copy method before swizzling SEL 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, ...);
  29. 29. 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));
  30. 30. Output UIStoryboardScene 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
  31. 31. 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

×