«objective-c runtime в примерах» — Алексей Сторожев, e-legion

31
Objective-C Runtime Examples https://github.com/storoj/objc_runtime_examples

Upload: e-legion

Post on 15-Jan-2015

1.069 views

Category:

Documents


2 download

DESCRIPTION

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

TRANSCRIPT

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

Objective-C RuntimeExamples

https://github.com/storoj/objc_runtime_examples

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

Example 1.1Observe value changes for all properties of a given class.

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

Class

Property Method

SEL IMP

getter

setter(objc_property_t)

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

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

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

ClassClass sampleClass1 = [ELSample class]; Class sampleClass2 = NSClassFromString(@"ELSample");

#import <objc/runtime.h> !Class sampleClass3 = objc_getClass(“ELSample"); Class sampleClass4 = objc_lookUpClass(“ELSample");

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

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

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

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

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

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);

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

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];

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

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)

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

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:));

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

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.

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

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

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

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 (...) { ... }

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

Example 1.2

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

_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];

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

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));

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

Example 1.3

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

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];

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

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.

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

@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

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

Example 2Get outlets and actions runtime

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

UIRuntimeOutletConnection UIRuntimeEventConnection

UIRuntimeOutletCollectionConnection

UIRuntimeConnection

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

@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

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

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)

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

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.

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

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

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

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, ...);

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

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));

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

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