Mobilization roy marmelstein cover

The Objective-C Runtime & Swift Dynamism

Calls for Swift to be “more dynamic” have been raised since Swift was introduced. But what does being “dynamic” even mean? Why is Objective-C more dynamic than Swift? And with a less dynamic language, how do we build the things we used to depend on dynamism for? Roy Marmelstein covered these and more at Mobilization 2016.


Introduction (0:00)

Today I’m going to speak about the Objective-C runtime and dynamism in Swift. I gave you the tagline a 2016 Perspective because a lot of what I say is subject to change in the coming years.

Let’s start by going back about five months ago. It’s mid-May. The sun is shining. It’s spring. Flowers are blooming, and Brent Simmons, who is a popular Objective-C and iOS developer, published a series of blog posts. In the blog posts, he’s documenting various problems that programmers solve with the help of the Objective-C runtime functions. What he’s trying to argue is that Swift, as a language, doesn’t have native solutions for this. And even though today, Swift ships with the Objective-C runtime, in a future where Swift completely replaces Objective-C, we don’t know how we’re going to solve these problems.

What happened next was this huge fight on Twitter. People picked sides. It was seen as the old guard, people who have been developing Objective-C apps for the Mac for the last 20 years, versus the new idealistic Swift developers. It was pitted as experience versus idealism and flexibility versus type safety. There were so many tweets and Medium posts, and it got so out of hand you that there’s even a t-shirt you can buy.

That was around the time when I thought I should do a talk about this. The way it’s going to work today is we’re going to explore the runtime functions and what we actually mean when we say “dynamism”. We’re going to look at where we are today with Swift, and these issues and look ahead towards the future and see what’s coming. It can be a bit dense, so I promise there’s a really cool cat GIF at the end. Something to look forward to.

Objective-C Runtime (2:06)

Before we start, Objective-C is a runtime-oriented language, which means that all of the links between your methods and variables and classes are deferred to the last moment possible to when your app is actually running, and this gives you great flexibility because you can change those links. The alternative, where Swift is most of the time, is compiletime-oriented languages. So in Swift everything is a bit more harder bound, and you get more safety, but it’s less flexible.

This is what this debate was all about. So without further ado, let’s get started.

The Objective-C runtime is a library. It’s responsible for the “objective” part, so everything that you know and love about object-oriented programming, that’s where it’s implemented. If you want to access the functions, you just import the library:

#import <objc/runtime.h>

And it’s written mostly in C and Assembly, and implements things like classes and objects and how methods are dispatched, protocols, all of that good stuff. It’s completely open source and it’s been open source for a very long time. You can download it, have a look, see how things are implemented, and learn more about the language that we’re all developing in.

Get more development news like this

The runtime is responsible for the object-oriented programming part of Objective-C. Let’s start with the basic building block. What are objects? Objects are defined in runtime.h:

typedef struct objc_class *Class;

struct objc_object {
	Class isa;
};

An object only has a reference to a class. It’s the isa (“is a”) and that’s it. This is what every object in Objective-C needs to implement. Now what’s a class? A class is a bit more complicated.

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

A class has an isa. It has a reference to its superclass, which is never nil apart from for NSObject. Everything else in Objective-C inherits from NSObject in some way. Then we have name, version, and info, and they’re not very interesting.

For us, what’s interesting is the ivar list, method list and protocol list. These are things that you can change at runtime and read from your runtime. We saw that an object is a very simple struct, and class is a very simple struct. And this gives us our first runtime function so we can create the class at runtime.

Why do you want to do that? It’s used a lot by frameworks in library providers. If you don’t know what data the user could create, you can create a class at runtime. It’s used by Core Data. It can be used with JSON parsing, if you want to.

Class myClass =
objc_allocateClassPair([NSObject
class], "MyClass", 0);

// Add ivars, methods, protocols here.

objc_registerClassPair(myClass);
// ivars are locked after registration.

[[myClass alloc] init];

So there’s an Objective-C runtime function called allocateClassPair. You give it the isa, in this case NSObject, and you give it a name. The third argument is for extra bytes, so you often leave it at zero. You add the ivars, methods and protocols, then you register your ClassPair. After you register, you can’t change the ivars but you can change everything else.

That’s it. This class will behave like any other Objective-C class and there’s no difference.

Categories (5:34)

Now, if you have a class that you don’t own and you want to extend it, you want to add functions to it, an easy way to do it is in Objective-C’s categories. Swift’s extensions that are very similar. One of the problems with categories is that you can’t add a stored property. You can add a computed variable, but not a stored property.

Another feature of the runtime is that we can add a stored property to an existing class, with the functions setAssociatedObject and getAssociatedObject.

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

This is very useful for extending classes that you don’t own.

The next thing you want to talk about is figuring out what a class can do. This is what we call “introspection”. The very basic version of introspection is something that you probably use on a regular basis.

[myObject isMemberOfClass:NSObject.class];

[myObject respondsToSelector:@selector(doStuff:)];

// isa == class

class_respondsToSelector(myObject.class, @selector(doStuff:));

So we have isMemberOfClass, which is part of Foundation and NSObject subclasses. Then respondsToSelector:, which you use if you have a protocol with an optional method and you don’t want to crash. At the runtime level, isMemberOfClass compares the isas. respondsToSelector: wraps an Objective-C runtime function called respondsToSelector, where you give it the selector and the class.

Now, if you’ve done any unit tests, you know that when you write XCTestCase you have your setUp and tearDown, and you write your test functions. When you run your tests, it enumerates through the functions and automatically runs them. The way this is implemented is with the Objective-C runtime.

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

You can copy the method list. You can also copy the ivar list, if you wanted to. You get the method name, transform it into a string, check if it contains the string “test”, and then run it. We’ve built a mini-version of XCTest!

So what are ivars and methods?

struct objc_ivar {
	char *ivar_name;
	char *ivar_type;
	int ivar_offset;
}

struct objc_method {
	SEL method_name;
	char *method_types;
	IMP method_imp;
}

ivars are not that different from what you’re actually defining in your code. They have a type and they have a name. The offset is about internal memory management.

An Objective-C method calls its name its “selector”, which is what you use in performSelector. The method also has an encoded string representing the types of the method. Then it has the implementation, which has a specific way we won’t get into.

So methods are simple, we can add a method to an object at runtime as well.

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

The way we do that is there is a class_addMethod function. It takes everything we said we need for a method to exist. It takes a selector, implementation, and types. This particular implementation is cheating, since I’m using the existing doStuff method to get the type string, but you could do it a few other ways.

Of course we call functions in order to use them. We use either [self doStuff] or [self performSelector:@selector(doStuff)], which do the same thing, and what happens on the runtime level is that we actually send a message to the object with objc_msgSend.

[self doStuff];
[self performSelector:@selector(doStuff)];

objc_msgSend(self, @selector(message));

But if we call a method and that object is nil, we will get an exception and the app will crash. But it turns out that there are a few steps that happen beforehand, which allow you to do stuff with a function that’s not there.

Forward a method (9:24)

You can forward a method to another target. That’s very useful if you’re trying to bridge between different frameworks. This is what happens when you call a method that’s not implemented.

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

When you call a method that doesn’t exist, the runtime first calls a class function called a resolveInstanceMethod, or resolveClassMethod if it’s a class method. And that’s an opportunity for you to add the method, the way that we showed before. If you return YES, the original method will be called again.

Or if you don’t want to create a new method, we have forwardingTargetForSelector. You just return the target you want that method to be called on, and then the selector will be called on that target.

There’s a slightly more complicated one, forwardInvocation. The entire call is being wrapped into an NSInvocation object and then you can invoke it with a specific target. When you do that, you also need to implement methodSignatureForSelector.

So you can forward a method to another object, but you can also replace and/or exchange an implementation. The most well-known dynamic feature that you can do with the runtime is called swizzling. This is the basic way to swizzle:

+ (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);
		}
	});
}

When a class is loaded, there’s a class function called load that gets called. We only want the swizzling to occur once, so we use dispatch_once. Then we get the method and use class_replaceMethod or method_exchangeImplementations. The reason why you might want to do this is it’s great for mocking and logging.

Foundation (11:15)

Going up a level from the runtime, we have Foundation. Foundation implements a specific feature that is really built on top of the runtime: key-value-coding (KVC) and key-value observing (KVO). KVC and KVO allow us to bind our UI to data. This is the promise of Rx and all the reactive frameworks, and this already exists in Foundation. This is the way KVC works:

@property (nonatomic, strong) NSNumber *number;

[myClass valueForKey:@"number"];
[myClass setValue:@(4) forKey:@"number"];

If you have a property called code number, for example, you can get the value or set the value with the property name as the key. It works with getting the list of the variables and protocols and dangerous inspection features that we saw before.

Then with KVO, you can register for changes.

[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.
}

When the observed value changes, it will notify the observer by calling this method. From there, you can update to your UI as you wish.

When we talk about “dynamism” in Objective-C, we are generally talking about this. There are many more functions, but these are the most useful ones. These are the ones people say are missing from Swift.

Now, again, all of these come with a warning. With KVO for example, especially if you observe a class that you don’t own, you can have unexpected changes happening in your app. Generally, these are really hard to debug, and hard to reason about. It’s not recommended to use them as much in production, thought it’s very useful when you’re debugging and when you’re creating a framework for yourself. But in production, I would be very very careful with these.

Apple thinks the same thing and they added this private method inside your view controller that you can see with class-dump.

+ (void)                   attentionClassDumpUser:
                                    yesItsUsAgain:
althoughSwizzlingAndOverridingPrivateMethodsIsFun:
          itWasntMuchFunWhenYourAppStoppedWorking:
 pleaseRefrainFromDoingSoInTheFutureOkayThanksBye:

Which is pretty crazy.

Swift (13:29)

So let’s talk about Swift. Swift as a language is generally strongly typed. It’s statically typed, and the default types in Swift are very safe. You do have unsafe types if you want, but the language is definitely pushing you towards using safe static types. Whatever dynamism exists in Swift is available via the Objective-C runtime.

This was fine until Swift was open-sourced and Swift migrated to Linux. Swift on Linux is not shipped with the Objective-C runtime. The word in the community is that this the future direction of Swift, with no Apple dependencies.

What we get with Swift is these two modifiers, @objc and @dynamic, and we also have access to NSObject. @objc exposes your Swift API to the Objective-C runtime, but it still doesn’t guarantee that the compiler won’t try to optimize it. If you really want to use any of the cool dynamic features, you really need to use @dynamic. When you use the @dynamic modifier, you don’t need to use @objc because it’s implied.

Going back to our list of dynamic features, let’s see how they change with Swift. Say we have our introspection one forward methods replacing and binding. Forwarding actually doesn’t change that much:

// 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

We still have resolveInstanceMethod that gets called. You have forwardingTarget even gets like a nice Swift 3–style API. But then NSInvocation doesn’t exist in Swift. We can still do our forwarding, so this is not that bad.

Swizzling is a bit different. load doesn’t get called at all in Swift, so you need to do the swizzling during initialize. In Objective-C, we used dispatch_once, but dispatch_once doesn’t exist in Swift since Swift 3. It’s a bit more complicated. It’s still possible for specific kinds of functions that you can define as dynamic functions, but it removes most of the ability to swizzle.

For introspection, we have some new stuff.

if self is MyClass {
	// YAY
}

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

Instead of isMemberOfClass, we have is, which also works on Swift value types. You can use it on structs and enums and all the great new things that we got with Swift. There’s also a new mirroring API, which is focused on pipes and data.

At the moment, we don’t have a native way to introspect methods. It’s been hinted that that’s coming, but we don’t have it yet. That’s frustrating, especially if you think about the XCTestCase behavior that we showed before. If you want to write unit tests for Linux, we can’t automatically enumerate the functions. You have to implement static var allTests and list all of your tests manually. It’s very sad.

What about KVO and KVC? The power of KVO is that you can also do it on classes that you don’t own, or classes that you just want to respond to changes in. KVO and KVC is much weaker in Swift. The object you are observing has to inherit from NSObject and be an Objective-C type. The variable that you’re observing has to be declared as dynamic. You need to be very specific about the things that you observe.

The problem is that Swift doesn’t have an alternative solution that’s great. You could use Rx or you could use a protocol-based ways of observing things. But there’s nothing native to the language to solve this problem yet.

Swift is an exciting language and there is good news. Recently in the Swift mailing list, Chris Lattner said that he thinks that adding dynamic features is essential to Swift. He also said that even though people don’t agree about what these “dynamic” features are, it’s important to find a native, fluent, Swift-y way of solving these problems.

Conclusion (18:11)

Today, more dynamism is scheduled for stage two of Swift 4. We’re currently at stage one of Swift 4, and their focus is on API stability. As soon as that’s done, in whatever time is left before iOS 11, this will be one of the central focuses for the core team, probably starting with ways to introspect methods.

There’s another thing, which is a side project I’m working on. I’m working on open source project called ObjectiveKit. The idea is to expose some runtime functions in a Swift-y way which I think would be quite a bit of fun.

In conclusion, dynamism in Objective-C is powerful, useful, and a bit dangerous. Swift currently doesn’t have good enough alternatives or solutions for these problems. But it’s coming and there are things to look forward to. This is the cat gif, which I think is great. Thank you very much.

Next Up: Using RxSwift to Build Dynamic Apps

General link arrow white

About the content

This talk was delivered live in October 2016 at Mobilization. The video was transcribed by Realm and is published here with the permission of the conference organizers.

Roy Marmelstein

Creator of PhoneNumberKit, Interpolate and Localize.

4 design patterns for a RESTless mobile integration »

close