Swift 3.0 Refactoring Cues
I’ve been upgrading the try! Swift app to Swift 3.0 for what feels like 3 days now (I’ve had to take a lot of breaks to be in the right patient mindset, especially with not knowing how many more errors will come up after the immediate ones were fixed).
But anyway, this morning my BUILD SUCCEEDED!! I fixed the final warnings (almost) and tried out the app to see that it works generally. I still have time to fix any bugs in the future before the next version goes in production.
And although the process was definitely frustrating, a lot of the things that needed fixing were very repetitive. For example, the String API has added a new (and very ugly IMHO) describing parameter:
|
1 2 3 4 5 6 |
// covered here: https://www.natashatherobot.com/nsstringfromclass-in-swift/ String(MyTableViewCell) // is now this // * notice the required .self as well! * String(describing: MyTableViewCell.self) |
Well, I have A LOT of TableViewCell’s in my app, and when combined with registering Nibs or Dequeuing Cells, it was just an ugly train wreck:
|
1 2 3 4 5 |
// Swift 3.0 registering a cell example tableView.register(UINib(nibName: String(describing: TextTableViewCell.self), bundle: nil), forCellReuseIdentifier: String(describing: TextTableViewCell.self)) // Swift 3.0 dequeuing a cell example let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TextTableViewCell.self), for: indexPath) as! TextTableViewCell |
I had to fix these with the describing and .self over and over again during my Swift 3.0 upgrading process. As soon as I finished the upgrade, I knew I had to fix this issue. After all, hopefully the String API will be fixed back to having no describing parameter in Swift 4 – I want to refactor this repetitive change in only one place in the future!
The Refactor
Luckily, in this case, I knew just the solution! In fact, I talked about this solution in my POP talk… I should definitely have implemented this earlier, but when I first started making the app, I didn’t know about it, and it hasn’t been a big enough issue to actually do the refactor until this painful Swift 3.0 upgrade 😬
All it took was a few minutes and a few protocols! A timely reminder of how awesome Swift actually is! For a more detailed explanation, make sure to watch my talk or read about it from the source here. Otherwise, here is the quick version:
First, create a Protocol with a default variable for generating the nib name string from the class name:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
protocol NibLoadableView: class { } extension NibLoadableView where Self: UIView { static var nibName: String { // notice the new describing here // now only one place to refactor if describing is removed in the future return String(describing: self) } } // Now all UITableViewCells have the nibName variable // you can also apply this to UICollectionViewCells if you have those // Note that if you have some cells that DO NOT load from a Nib vs some that do, // extend the cells individually vs all of them as below! // In my project, all cells load from a Nib. extension UITableViewCell: NibLoadableView { } |
Next, do the same thing to generate the reuse identifier string from the cell class:
|
1 2 3 4 5 6 7 8 9 10 11 |
protocol ReusableView: class {} extension ReusableView where Self: UIView { static var reuseIdentifier: String { return String(describing: self) } } extension UITableViewCell: ReusableView { } |
Now the good stuff! You can take advantage of the above protocols to simplify the nib registration and cell dequeuing:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// UITableViewExtension.swift extension UITableView { func register<T: UITableViewCell>(_: T.Type) where T: ReusableView, T: NibLoadableView { let nib = UINib(nibName: T.nibName, bundle: nil) register(nib, forCellReuseIdentifier: T.reuseIdentifier) } func dequeueReusableCell<T: UITableViewCell>(forIndexPath indexPath: IndexPath) -> T where T: ReusableView { guard let cell = dequeueReusableCell(withIdentifier: T.reuseIdentifier, for: indexPath) as? T else { fatalError("Could not dequeue cell with identifier: \(T.reuseIdentifier)") } return cell } } |
Now time to refactor!
|
1 2 3 4 5 6 7 8 9 10 11 |
// Swift 3.0 original registering a cell example tableView.register(UINib(nibName: String(describing: TextTableViewCell.self), bundle: nil), forCellReuseIdentifier: String(describing: TextTableViewCell.self)) // Swift 3.0 refactored registering cell example: tableView.register(TextTableViewCell.self) // Swift 3.0 original dequeuing a cell example let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TextTableViewCell.self), for: indexPath) as! TextTableViewCell // Swift 3.0 refactored dequeuing a cell example let cell = tableView.dequeueReusableCell(forIndexPath: indexPath) as TextTableViewCell |
Here is my full commit. I was very happy with this result and look forward to less repetition when upgrading to Swift 4.0 in the future!
So although the Swift 3.0 upgrade is very painful, it is also a good time to notice all those repetitive things in your code and refactor.
Happy upgrading and remember to Breathe!