iOS technical deep dive – Medium Engineering


At this point, you may be thinking to yourself, “Isn’t Objective-C a statically typed language? Shouldn’t the compiler have prevented something like this from happening?” The answer to that is Objective-C is statically typed… to an extent. The type system is actually relatively dynamic, thanks to the influence of Smalltalk. Objects can be cast from one type to another without any validation from the compiler. In this case, since the port property of NetRequest was defined as an NSNumber in its interface, the compiler treated the object as an NSNumber and didn’t complain when it was set as the port on NSURLComponents even though it turned out to be an instance of NSString at runtime.

The next thing to look at is where this port value is coming from and to see if there’s anywhere where we could be implicitly casting an NSString to an NSNumber. In the case of the iOS config request, the NetRequest is initialized with data from our AppConfig file, which returns the port in the following method:

// AppConfig.m
- (NSNumber *)mediumApiUrlPort {
return self.infoDictionary[@"MEDIUM_API_URL_PORT"];
}

The infoDictionary in AppConfig is read from our app’s Info.plist file, which we configure with .xcconfig files. This allows us to run the app against local, staging, or the production versions of Medium, depending on which build configuration we’re using.

NSDictionary objects in Objective-C return values as type id by default. Type id is a special type in Objective-C that’s directly inherited from Smalltalk. It’s basically shorthand for any type of object pointer, and it’s the basis for most of the dynamic typing in Objective-C. The compiler allows you to call any method that it knows about (i.e. that it can see the method declaration for) on an object of type id. In this case, it’s also allowing us to return an object of type id as the return value of a method that says it returns an NSNumber, without any additional casting or validation. In the case of this crash, the value in the dictionary was an NSString, but it was getting implicitly cast to an NSNumber when it was returned from this method.

Once you know this, the fix for the crash becomes obvious. You simply need to explicitly convert the NSString object into an NSNumber:

- (NSNumber *)mediumApiUrlPort {
return @([self.infoDictionary[@"MEDIUM_API_URL_PORT"] integerValue]);
}



Source link