Tuesday, October 11, 2016

Swift 3 and Generics

Soon after Apple offically released Swift 3 and Xcode 8 after WWDC 2016 on September 13, 2016, I started a process to convert my iOS application to this new version. This time, Xcode provides an auto-conversion tool to convert most of my codes to Swift 3. However, I still had manually to make some changes to handle compiling errors and warnings.

For the errors, I had no choice to fix my codes. One interesting feature in Xcode 3 is that any function call which returns a type, a warning message is generated even the result is assigned to a variable. For example, MyLoger class uses chaining method a lot, that is, method call returning back to class itself.

  1. self.logger.debug() {
  2.   return "[\(#function)] is called." }.indent(true)

even the above method call does not return its result to a variable, Xcode still shows a warning message: result of call to 'indent' is unused. Since MyLogger is used in many places, a long list warning appears very annoying. What my fix is to add a no result call method endChain() to the log class.

  1. self.logger.debug() {
  2.   return "[\(#function)] is called." }.indent(true).endChain()

Those changes are OK, except one I still cannot find a solution, as explained in the following section.

Subclass Based on Generic Class not Available in Storyboard

I have a base class based on UITableViewController and NSFetchedResultsControllerDelegate. This base class contains most of basic codes for table view and table view's data source. I need to utilize Swift generic feature to define this base class as generic class, holding common codes. Then my subclass based on the generic base class would have specialized codes for various data entities.

The problem is that all my subclasses based on this generic base class are not showing up in Storyboard (Showing the identity inspector tab, Custom class dropdown list). By narrowing down to a simple generic class and a subclass from the base class, I found this problem persistence.

I think this most likely being a bug in the current version of Xcode.

Eventually I got an alternative solution: removing generic from the base class, and duplicating the base class into several classes based on my entity data types(fortunately, only a limited number of data types in my project).  I tried to make as less changes as possible in all those base classes. First, I changed generic type to a entity data type. Then each class is divided into two parts, common codes which takes majority, and entity data type specific part only a small portion of codes.

I also tried to closure block as method parameter or local data members so that my subclass will plug or assign block of codes for the closure. The example:

  1. typealias FetchRequestBlock_t = () -> NSFetchRequest<MyEntity1>
  2. class MyEntity1TableViewController : UITableViewController, NSFetchedResultsControllerDelegate
  3. {
  4.    // MARK: Properties, methods special for MyEntity1
  5.    // Block perperty to be initialized by subclass
  6.    var fetchRequestBlock : FetchRequestBlock_t?
  7.    var fetchedResultsController : NSFetchedResultsController<MyEntity1>?
  8.    // Block as parameter: plug in codes by subclass call
  9.    func setupFetchResultController(fetchRequestBlock: FetchRequestBlock_t) {
  10.      let fetchRequest = fetchRequestBlock()
  11.      ...
  12.    }
  13.    fileprivate func methodSave(entity: MyEntity1) {
  14.      // Use block var in local usages
  15.      MyEntity1.save(entity, self.fetchRequestBlock!)
  16.      self.tableView.reloadData()
  17.    }
  18.    ...
  19.    // MARK: -
  20.    // Common codes
  21.    // MARK: Propeties and methods
  22.    ...
  23. }

Here is an example of subclass to initialize block use base class method:

  1. class MyEntity1ListViewController : MyEntity1TableViewController
  2. {
  3.    ...
  4.    override open func viewDidLoad()
  5.    {
  6.        super.viewDidLoad()
  7.        let block: FetchRequestBlock_t = {
  8.          let fetchRequest: FetchRequest<MyEntity1> = NSFetchRequest(entityName: "MyEntity1")
  9.          ...
  10.          return fetchRequest
  11.        }
  12.        self.fetchRequestBlock = fetchRequest
  13.        self.setupFetchResultController(fetchRequestBlock: fetchRequest)
  14.        ...
  15.    }
  16.    ...
  17. }

With this not-perfect-solution, all my subclasses are visible in Storyboard. I hope the next version of Xcode would fix this bug.