Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Roy Marmelstein: THE OBJECTIVE C RUNTIME AND SWIFT DYNAMISM

Realm
October 22, 2016

Roy Marmelstein: THE OBJECTIVE C RUNTIME AND SWIFT DYNAMISM

A new talk introducing advanced techniques with the Objective C runtime and assessing their relevance in an increasingly Swifty world.

Realm

October 22, 2016
Tweet

More Decks by Realm

Other Decks in Technology

Transcript

  1. “I’m documenting problems that Mac and iOS developers solve using

    the dynamic features of the Objective-C runtime. […] The point is that these problems will need solving in a possible future world without the Objective-C runtime. The answers don’t have to be the same answers as Objective- C – but they need to be good answers.” Brent Simmons
  2. VS

  3. - #import <objc/runtime.h> - Written mostly in C & Assembler.

    - Classes. - Method dispatching. - Method forwarding. - Protocols - Open source!
  4. struct objc_class { Class isa; Class super_class; const char *name;

    long version; long info; long instance_size; struct objc_ivar_list *ivars; struct objc_method_list **methodLists; struct objc_cache *cache; struct objc_protocol_list *protocols; };
  5. Class myClass = objc_allocateClassPair([NSObject class], "MyClass", 0); // Add ivars,

    methods, protocols. objc_registerClassPair(myClass); // ivars are locked after registration. [[myClass alloc] init];
  6. - Create a class at runtime. - Add a stored

    property to an existing class. Dynamism!
  7. @implementation NSObject (AssociatedObject) @dynamic associatedObject; - (void)setAssociatedObject:(id)object { objc_setAssociatedObject(self, @selector(associatedObject),

    object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (id)associatedObject { return objc_getAssociatedObject(self, @selector(associatedObject)); }
  8. - Create a class at runtime. - Add a stored

    property to an existing class. - Figure out what a class can do. Dynamism!
  9. unsigned int count; Method *methods = class_copyMethodList(myObject.class, &count); //Ivar *list

    = class_copyIvarList(myObject.class, &count); for(unsigned i = 0; i < count; i++) { SEL selector = method_getName(methods[i]); NSString *selectorString = NSStringFromSelector(selector); if ([selectorString containsString:@"test"]) { [myObject performSelector:selector]; } } free(methods);
  10. struct objc_ivar { char *ivar_name; char *ivar_type; int ivar_offset; }

    struct objc_method { SEL method_name; char *method_types; IMP method_imp; }
  11. - Create a class at runtime. - Add a stored

    property to an existing class. - Figure out what a class can do. - Add a method at runtime. Dynamism!
  12. Method doStuff = class_getInstanceMethod(self.class, @selector(doStuff)); IMP doStuffImplementation = method_getImplementation(doStuff); const

    char *types = method_getTypeEncoding(doStuff); //“v@:@" class_addMethod(myClass.class, @selector(doStuff:), doStuffImplementation, types);
  13. - Create a class at runtime. - Add a stored

    property to an existing class. - Figure out what a class can do. - Add a method at runtime. - Forward a method to another target. Dynamism!
  14. // 1 +(BOOL)resolveInstanceMethod:(SEL)sel{ // A chance to add the instance

    method and return YES. It will then try sending the message again. } // 2 - (id)forwardingTargetForSelector:(SEL)aSelector{ // Return an object that can handle the selector. } // 3 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ // You need to implement this for the creation of an NSInvocation. } - (void)forwardInvocation:(NSInvocation *)invocation { // Invoke the selector on a target of your choice. [invocation invokeWithTarget:target]; }
  15. - Create a class at runtime. - Add a stored

    property to an existing class. - Figure out what a class can do. - Add a method at runtime. - Forward a method to another target. - Replace / exchange an implementation. Dynamism!
  16. + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ Class class

    = [self class]; SEL originalSelector = @selector(doSomething); SEL swizzledSelector = @selector(mo_doSomething); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) { class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else { method_exchangeImplementations(originalMethod, swizzledMethod); } }); }
  17. - Create a class at runtime. - Add a stored

    property to an existing class. - Figure out what a class can do. - Add a method at runtime. - Forward a method to another target. - Replace / exchange an implementation. - Bind UI to Data. Dynamism!
  18. [myClass addObserver:self forKeyPath:@"number" options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew context:nil]; - (void)observeValueForKeyPath:(NSString *)keyPath

    ofObject:(id)object change: (NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ // Respond to observation. } KVO
  19. - Create a class at runtime. - Add a stored

    property to an existing class. - Figure out what a class can do. - Add a method at runtime. - Forward a method to another target. - Replace / exchange an implementation. - Bind UI to Data. Dynamism!
  20. “While the @objc attribute exposes your Swift API to the

    Objective-C runtime, it does not guarantee dynamic dispatch. The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime. When you mark a member declaration with the dynamic modifier, access to that member is always dynamically dispatched. Declarations marked with the dynamic… [are] implicitly marked with the @objc attribute.”
  21. - Create a class at runtime. - Add a stored

    property to an existing class. - Add a method at runtime. - Figure out what a class can do. - Forward a method to another target. - Replace / exchange an implementation. - Bind UI to Data. Dynamism!
  22. // 1 override class func resolveInstanceMethod(_ sel: Selector!) -> Bool

    { // A chance to add the instance method and return YES. It will then try sending the message again. } // 2 override func forwardingTarget(for aSelector: Selector!) -> Any? { // Return an object that can handle the selector. } // 3 - NSInvocation is not available in Swift Forwarding *NSObject
  23. // - Implement on public override class func initialize() instead

    of + (void)load because load is never called in Swift Swizzling ☹
  24. if self is MyClass { // YAY } Introspection let

    myString = "myString"; let mirror = Mirror(reflecting: myString) print(mirror.subjectType) // “String" let string = String(reflecting: type(of: myString)) // Swift.String // No native method introspection
  25. XCTest - Linux static var allTests = { return [

    ("test_URLStrings", test_URLStrings), ("test_fileURLWithPath_relativeToURL", test_fileURLWithPath_relativeToURL), ("test_fileURLWithPath", test_fileURLWithPath), ("test_fileURLWithPath_isDirectory", test_fileURLWithPath_isDirectory), // Other tests go here ] }()
  26. “I personally think that adding “dynamic” features to Swift is

    absolutely essential. I don’t think that it is interesting to provide the “equivalent” features to Objective-C, but I do think it is important that Swift be able to solve the same sorts of problems (including your list of Responder Chain, NSUndoManager, KVC/ KVO/Bindings, …) in a fluent/expressive way, even if it works differently. Chris Lattner
  27. Swift 4 Stage 2 “The core team is committed to

    adding powerful dynamic features to Swift.”
  28. - Dynamism in ObjC is powerful, useful and dangerous. -

    Swift currently doesn’t offer good enough alternatives for most runtime functions. - This will likely improve with future versions of Swift. Conclusion