Objective-C features that I wish existed
This post is an enhancement proposal for Objective-C. I’m going to scratch the itch on the surface of possible Objective-C language improvements. The features listed here are chosen to have a relatively small scope in order to be implemented without major changes and in a backwards compatible way in the spirit of Objective-C. Thus they are not meant to turn Objective-C into a modern experimental language like Swift, but should make programming experience better and reduce boilerplate.
Namespaces
Objective-C is infamously verbose. Part of the reason is the lack of namespaces. By convention people use 2-3 letter uppercase prefixes to all their classes to avoid name clashes. So instead of a class called Product
you write ACMEProduct
. Often you have multiple related class clusters, let’s say we have an ACMEProductTableViewController
based on a UITableViewController
that displays ACMEProduct
items. It will often get a related ACMEProductTableViewCell
and an ACMEProductTableDataSource
or an ACMEProductTableViewModel
. As the app grows it will have “sub-clusters” (-modules/-components) as well, which, for example, group multiple view controllers, so the names become even longer, like ACMEStoreProductTableViewController
(assuming that “Store” was our chosen module name).
Most programming languages have a concept of namespaces (or packages) to deal with this problem. The namespaces can be tied to the file system folder structure (like in Java), or purely virtual tree of names (like in C++), but they make the code cleaner. If Objective-C had namespaces, a name like ACMEStoreProductTableViewController
could become:
ACME.Store.ProductTable.ViewController
and most of the time the long prefix would be understood from the context, or imported once on the top:
@import_namespace ACME.Store.ProductTable;
...
ProductTable.ViewController *vc = ...
And of course, after typing “.” you would get the IDE completion intelligence to help you coding.
enums with strings attached
Objective-C uses the plain C language enumerations. Each enum
value is represented by an integer number. This is well until you run the app, then it is not enough. Most of the time it is better to have a human readable string representation for the values. It is a nice feature even if made just for logging and debugging. At other times the enum list is dictated by some API or a protocol, and you have the interchangeable string keys. Sometimes you want to define an NSError
which requires not only an integer code from an enum, but also a human-readable error description (potentially localizable). By the way, in many languages (Java, Python, C++, Go etc.) errors require to have such a description and the integer code is optional.
Imagine if this code:
@enum ProductType
Free,
Demo,
Paid,
Subscription,
@end
would generate a plain C enum for using in switch
statements and corresponding string constants for each value that could just be used as needed:
ProductType *type = ProductType.Demo;
switch (type.code) {
case ProductTypeDemo:
...
}
NSString *description = type.description;
NSLog(@"type = %@", description); // prints: "type = Demo"
public get, private set
If there is a writable property, it implicitly uses a readwrite
modifier.
If it is not writable, it uses a readonly
modifier.
There are cases when you want to have a public property such that only the class implementation is able to set. For this case Objective-C has an idiom of declaring a property as readonly
in the public interface of the class, and redeclaring it as readwrite
in the private interface. This yields this kind of code duplication:
// public interface
@property (readonly, ...others...) ACMEProduct *product;
// private interface
@property (readwrite, ...repeat others...) ACMEProduct *product;
This feels like boilerplate code when adding or renaming properties. In fact this is the major use case for the readwrite
modifier, because otherwise it is used by default and can be omitted.
The second declaration could be stripped down if only there was a special modifier for this case, like:
// public interface
@property (private_setter) ACMEProduct *product;
In this case the private setter should be only visible and accessible from the @implementation
part much like the generated ivar.
Autogenerated DI constructors
In many cases after passing a parameter to the initializer method, this parameter is used to just set a private instance variable or property with the same name:
// interface
@property (readonly, nonnull) NSString *title;
- (nonnull instancetype)initWithTitle:(nonnull NSString *)title;
// implementation
- (nonnull instancetype)initWithTitle:(nonnull NSString *)title {
...
_title = title;
...
}
As you can see in this example the title
name repeats 7 times! Adding, renaming or removing title
requires 7 modifications. This is a very common conventional code. It is used a lot for dependency injection and just normal initialization. It’s a pity that it requires so much boilerplate.
It’s not trivial to fix this, but much like property setters are generated for us, the DI constructors could be generated too. One possible solution is to generate a constructor method that sets all readonly
properties from its parameters. For example, having 2 properties:
@property (readonly, nonnull) NSString *title;
@property (readonly, nonnull) UIColor *color;
should automatically generate an initializer with 2 parameters initWithTitle:color:
that sets these properties from parameters. In addition this initializer could call a custom initializer method with a fixed name didInit
(if it exists in the class) in order to allow extra initalization steps after the properties were set.
Nullability type checks
In 2015 Xcode 6.3 introduced nullability annotations to Objective-C. With those you can express an intent that a pointer to an object can never be nil
. Unfortunately the only observable check that the Objective-C compiler does with that is that it prevents you from passing the nil
constant in places where a nonnull
type is expected. If you pass in a nullable
pointer variable (not a nil
constant), this is silently allowed:
@property (nullable) NSString *name;
- (void)printName:(nonnull NSString *)name;
...
[self printName:self.name]; // silently allowed
We know that the nonnull
type is more restrictive, but the type system just ignores this restriction, and silently casts it from nullable
to nonnull
or from unspecified to nonnull
. This is similar to “casting away constness” in C/C++, which is not possible to do by accident:
The Objective-C compiler could do the same for nonnull
types, and raise a special warning in such cases that would require to do an explicit type cast from nullable
to nonnull
, or to provide a default nonnull
value with the Elvis operator ?:
.
Automatic weak self in blocks
Fairly often when you use blocks (closures) you want to call a method of your object like so:
[self.api requestDataWithCompletionBlock:^(id data) {
[self displayData:data];
}];
The problem with this code is that it sneaks a retain cycle: the self
object keeps and retains the api
object, the api
object needs to remember the completion block until the request finishes, and the completion block retains self
, hence the cycle. If api
does not clean up the block, the self
object is never released. Even if it does clean up, you lose the ability to destroy self
before the request completes.
There’s an Objective-C idiom called “weak-strong dance” which is used to deal with this:
__weak typeof(self) weakSelf = self;
[self.api requestDataWithCompletionBlock:^(id data) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
... do something with strongSelf that expects a non-nil value
}
}];
In this case the block does not capture self
. Instead it captures the weakSelf
variable, which is fine, because it is a “weak” pointer. A weak pointer doesn’t prevent destruction, and it becomes nil
after that.
Imagine a piece of code that uses a lot of blocks, including nested blocks where on completion you want to do something else asynchronously, so you have to make a second strongSelf
. This pattern becomes boilerplate code! It is possible to shrink the lines by using @weakify/@strongify
custom helper macros, but you still have to write them and have them available in your project.
First of all, it’s a good practice to always avoid capturing self
. It would be nice to have a warning that tells if self
is used anywhere in blocks (excluding “non-escaping” blocks like the one you pass to UIView.animateWithDuration:animations:
or NSArray.enumerateObjectsUsingBlock:
).
Secondly, weakSelf
could be a magic variable that Objective-C understands. Much like self
is a predefined magic variable within any method body, weakSelf
could also be predefined, and weakly reference the self
object.
Finally, strongSelf
, or let it be called “blockSelf”, could be a predefined variable that is unique per block, and strongly captures the known weakSelf
, so the boilerplate is reduced to just if
check in case you need it:
[self.api requestDataWithCompletionBlock:^(id data) {
if (blockSelf) {
...
}
];
or just a method call (because calling it on nil
is a “noop” which is often fine):
[self.api requestDataWithCompletionBlock:^(id data) {
[blockSelf displayData:data];
];
Final notes
There’s plenty of ways to improve Objective-C. One obvious pie in the sky is supporting custom generic types. It is easy to find many more ideas from other popular languages and imagine how their Objective-C applications would look like. If you find doing this interesting please share your Objective-C enhancement ideas in the comments on Reddit or message me on Twitter.
Cover image by Nick Kenrick under CC BY.
Subscribe to get more articles about programming languages | |
|
|
Follow @battlmonstr | Donate |