
Before I joined the light side of the force and started working with Apple devices, I was a .NET developer. Most of us have prior experiences with other platforms and languages and there’s always an argument – whether we should take this new platform as it is or try to adapt it and morph it to what we already know. In my opinion it’s more a question of “how much” rather than “if”.
In C# there’s an as
operator, which tries casting an object to a provided class and if that casting is unsuccessful, the result is null
1. It’s useful if you have an object with some general class type or you’re using duck typing, but expect it to have certain functionalities of a specific class.
I always wondered why Objective-C didn’t have anything like this. It’s a very dynamic language, it has the id
type which makes it extremely easy to provide one type and expect another, without any sort of warning from the compiler. And when you try to run the code with a wrong type, you will experience what is a very rare occurrence in Objective-C development: a crash. Add to it the fact that Objective-C is very forgiving and lets you send messages to nil
(in most cases, at least) and such a feature would be perfect to have out of the box.
One day, after encountering this problem again, I decided to finally try to solve it by writing a tiny category which would “port” the solution from C# to Objective-C. Microsoft describes the solution as:
expression is type ? (type)expression : (type)null
Which makes it very simple to implement in Objective-C. We just have to use NSObject
‘s isKindOfClass:
and isMemberOfClass:
(if you want to be strict) methods to check whether the object can be safely casted to a chosen class and if not, return nil
.
- (id)as:(Class)cls
{
return [self isKindOfClass:cls] ? self : nil;
}
Put the logic into two methods, which are basically one-liners, a few convenience ones to write the code faster and voilà! Less crashes!
The best way to describe how useful that little addition can be is to use it in an example. We all worked with applications which get their data from an outside source. If you’re working with a backend API, it’s likely you’re getting JSON which translates into NSDictionary
.
- (void)fetchPost
{
[self.backendManager fetchPostFromURL:self.postURL
withCompletion:(void (^)(id responseObject)) {
Post *post = [Post new];
post.title = responseObject[@"title"];
post.creationDate = responseObject[@"creationDate"];
post.body = responseObject[@"body"];
post.commentsCount = [responseObject[@"comments"] integerValue];
// ... some code
}];
}
It’s all fine when everything works, the JSON structure mirrors what we expect in the app. But what if instead of NSNumber
we get NSString
? Or even worse – if the backend returns (null)
, instead of empty string or 0, after some property isn’t found in the database? In the latter case you’ll get an NSNull
object, which is very rarely used; in any case, you would probably expect to get nil
where you’re getting something called null
rather than an object.
Here’s where as
casting comes in.
- (void)fetchPost
{
[self.backendManager fetchPostFromURL:self.postURL
withCompletion:(void (^)(id responseObject)) {
NSDictionary *dict = [responseObject asDictionary];
if (dict) {
Post *post = [Post new];
post.title = [responseObject[@"title"] asString];
post.creationDate = [responseObject[@"creationDate"] as:[NSDate class]];
post.body = [responseObject[@"body"] asString];
post.commentsCount = [[responseObject[@"comments"] asNumber] integerValue];
}
// ... some code
}];
}
Now, even if we get a type different than the one we expected, the end result is much safer. You’re getting NSNull
or NSString
instead of NSNumber
? Then you’ll get 0
, which is usually a safe value, rather than crashing the application. A backend-returned dictionary with an error, instead of an array of posts you expected, casting it via the use of [obj as:[NSArray class]]
or simply [obj asArray]
, can save you a lot of trouble.
Another example where as:
can be useful is CoreData subclassing. You can have a relationship which is set to base class, but the actual values can be subclasses of that. There’s not much point in subclassing when you’re using just base class information. So use whatever you can from the base class, and safely cast to type you expect with as
.
@interface Message : NSManagedObject
@property (nonatomic, strong) Attachment *attachment;
/// ...
@end
- (void)showAttachmentForMessage:(Message *)message
{
ImageAttachment *imgAttachment = [message.attachment as:[ImageAttachment class]];
if (imgAttachment) {
[self showImageFullscreen:imgAttachment.image];
return;
}
VideoAttachment *videoAttachment = [message.attachment as:[VideoAttachment class]];
if (videoAttachment) {
[self playVideoFullscreen:videoAttachment];
return;
}
/// etc.
}
In a world when each of your fixes has to go through Apple’s review process, defensive programming like this is a great way to build better experience for your users. And it fits with Objective-C’s concept of handling errors. According to the concept, for your app to display or do nothing, rather than just throw an exception and crash, is actually much better from a user experience perspective. Alas, it sometimes makes it harder to squash some bug; it would be better to just get a crash log stating that you tried perform selector on the wrong class, but the user just doesn’t care.
This one category class is dumb and simple, but it demonstrates something important. As a language, Objective-C is still very much alive (at 31 years and counting). It gets new features, changes over time. Where we used to employ protocols and delegate design patterns (quite successfully, I might add) in the past, we now use callbacks with blocks, mostly because they became available, but also because they can be much more natural in those cases (something which was very natural for Javascript and gives Node.js a lot of its power). People come from different backgrounds, have different experiences, and sometimes those experiences can be used to improve the language or workflow for other developers. It always comes down to finding the right balance. A sweet-spot between seeing everything as a nail just because you’ve been using a hammer for years and taking the language or environment we’re developing in as something finished, designed by people smarter than us, something we should take as-is without trying to change anything.
WWDC 2014 Update
Apple changed that, but instead of adding it as a method or feature of Objective-C, they introduced it as “Downcasting,” with as
and as?
operators in their new, Swift programming language. It acts like methods I described above, but it seems that the type checking is also performed at compile time, if possible. And you always perform the overridden method, not the one implemented in the class you’re casting to.
-
“In C# there’s also the aspect of casting and being able to execute the implementation of the method of that casted class, not the implementation of the class of the original object.” ↩