This article is also available in Chinese.
Swift is a successor to Objective-C only because it has the same vendor. It doesn’t look the same, doesn’t act the same, and doesn’t feel the same. Paradigms that worked great in Objective-C, like method names, are slowly being updated for a bright new Swift world. For example, in Objective-C, this method was called with its full form:
[string dataUsingEncoding:NSUTF8StringEncoding];
In Swift 2.2, it was somewhat ungainly:
string.dataUsingEncoding(NSUTF8StringEncoding)
In Swift 3, the method became much more streamlined:
string.data(using: .utf8)
The Swift 3 version of this method is right for Swift, in the same way that the Objective-C version was right for Objective-C. I think this article does a good job covering how to update your own method names for this new world.
There are other parts of the frameworks and the idioms that we use to write our apps that also need to be updated in a Swiftier world. Today, I’d like to focus on delegate naming.
Delegates in Swift don’t translate very well from Objective-C. Objective-C was very much about “senders” and “receivers”. Much of Apple’s Objective-C documentation is written in these terms. For example, the method isFirstResponder
on UIResponder
has documentation that reads:
Returns a Boolean value indicating whether the receiver is the first responder.
And of course, when setting up a target-action selector, the idiomatic name for the first argument has historically been sender
:
- (void)buttonTapped:(UIButton *)sender {
Delegates work much the same way: the first argument for a delegate method in Objective-C has always been the sender. You can see why this is useful: if you (the receiver) are the delegate of multiple objects of the same type, you’ll need a way to differentiate them. The delegate provides you with the first parameter, and you can switch on it.
I’m going to use some examples from the Backchannel SDK with some class names simplified for brevity.
There are two main types of delegate methods. First, those that just indicate that an event happened.
- (void)messageFormDidTapCancel:(BAKMessageForm *)messageForm;
In Swift, this translates to:
func messageFormDidTapCancel(_ messageForm: BAKMessageForm)
This doesn’t look right in Swift 3 anymore. In Swift 3, redundancy is eliminated (the two messageForm
names), and first arguments should generally be named rather than removed using the underscore.
The second type of delegate method indicates that an event happened and includes some data. I want to look at two examples of this one.
- (void)messageForm:(BAKMessageForm *)messageForm didTapPostWithDraft:(BAKDraft *)draft;
- (void)messageForm:(BAKMessageForm *)messageForm didTapAttachment:(BAKAttachment *)attachment;
In Swift, these translate to:
func messageForm(_ messageForm: BAKMessageForm, didTapPostWithDraft draft: BAKDraft)
func messageForm(_ messageForm: BAKMessageForm, didTapAttachment attachment: BAKAttachment)
These are horrible. Why are both of these methods called messageForm
? Also, starting a method with a noun here doesn’t make sense: it usually suggests that you’ll be returning an object of that type (think about data(using:)
on NSString
, which returns a Data
). We’re not returning any message form objects here. That “message form” is actually the name of the first parameter. These are very confusing method names!
Both of these types of delegate methods can be fixed by moving the “sender” to the back of the line, and bringing the verbs forward. For the first one, the event that the sender is informing the delegate about is didTapCancel
, instead of messageFormDidTapCancel
. Let’s start with that:
func didTapCancel(messageForm: BAKMessageForm)
This is already much better. The action is brought to the front, and becomes the name of the method, and it’s a lot clearer what this method does. I think we could use a preposition instead for the parameter name, to make it read a little nicer at the call site:
func didTapCancel(on messageForm: BAKMessageForm)
I haven’t found a hard and fast rule for which preposition to use yet. I’ve found “on”, “for”, “with”, and “in” to all be useful in different circumstances. Users tap “on” a form, so I think “on” is appropriate here.
Let’s also take a look at delegate methods that have data to pass back. Bringing the verb to the front helps, and switching to a preposition for the delegate name also cleans up these types of methods. Instead of:
func messageForm(_ messageForm: BAKMessageForm, didTapPostWithDraft draft: BAKDraft)
we have the much Swiftier:
func didTapPost(with draft: BAKDraft, on messageForm: BAKMessageForm)
and
func didTap(attachment: BAKAttachment, on messageForm: BAKMessageForm)
These rules aren’t endorsed by anyone except for me, but I think they make much more sense than the current rules by which we write delegate methods. Going forward, I’ll probably start writing my Swift delegate methods with this structure.
I’ll leave you with some UITableView
delegate and data source methods and what they could look like in this awesome future:
func numberOfSections(in tableView: UITableView) -> Int
numberOfSections
follows this scheme and looks pretty good already.
These methods, however, don’t look so great:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
Here they are with a little love:
func numberOfRows(inSection section: Int, in tableView: UITableView) -> Int
func cellForRow(at indexPath: IndexPath, in tableView: UITableView) -> UITableViewCell
func didSelectRow(at indexPath: IndexPath, in tableView: UITableView)