It’s amazing how quickly I now have become a part of the Swift Fan Club. I recently worked on some old Objective-C code of mine and was amazed how quickly one learns to stop typing semi-colons. 🙂
Today’s post is all about a pattern I use more often in my projects, and it’s one that prefers composition over inheritance. All that really means in this case is that on any UITableViewController
(or similarly, UICollectionViewController
), I prefer to create separate Data Source objects that keep all that code separate from the View Controller itself. (I personally don’t find that MVC stands for Massive View Controller if you don’t let it.)
The issue here is that I pretty much don’t do projects any more without using Core Data. It is the best solution in my opinion because of the code you oftentimes *don’t* have to write. Also, with NSFetchedResultsController
, I like how you can further separate your data layer (think Networking and importing) from you View Controllers. View Controllers concern themselves with *what* they want to display, and not with how it is acquired.
Anyway, the strictly typed language of Swift sometimes makes old approaches not straightforward, and I would like to share what I determined today. It will become a staple in my future Swift projects.
I create a DataSource class that takes a generic type, so that this generic type can be used for Core Data related activities. By default, a NSFetchedResultsController
also takes a generic type of NSFetchRequestResult
. But sometimes that is simply not enough. More on this later. To even make a Generic Data source, we have:
class BasicFetchedResultsDataSource: NSObject, NSFetchedResultsControllerDelegate where T:NSManagedObject { let managedObjectContext: NSManagedObjectContext! let tableView: UITableView! init(context: NSManagedObjectContext!, tableView: UITableView!) { self.managedObjectContext = context self.tableView = tableView } private var _fetchedResultsController: NSFetchedResultsController? = nil var fetchedResultsController: NSFetchedResultsController { if _fetchedResultsController != nil { return _fetchedResultsController! } let request = T.fetchRequest() request.predicate = self.searchPredicateForFetchRequest request.sortDescriptors = self.sortDescriptorsForFetchRequest let controller = NSFetchedResultsController(fetchRequest: request as! NSFetchRequest, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: self.sectionNameKeyPath, cacheName: self.resultsControllerCacheName) controller.delegate = self _fetchedResultsController = controller return _fetchedResultsController! } func updateRequestAndFetch() throws { self.fetchedResultsController.fetchRequest.predicate = self.searchPredicateForFetchRequest self.fetchedResultsController.fetchRequest.sortDescriptors = self.sortDescriptorsForFetchRequest do { try self.fetchedResultsController.performFetch() self.tableView.reloadData() } catch { throw error } } // allows your subclass to override and change this var sectionNameKeyPath: String? { return nil } // allows your subclass to override and change this var resultsControllerCacheName: String? { return nil } // allows your subclass to override and change this according to state var sortDescriptorsForFetchRequest: [NSSortDescriptor]! { return [] } // allows your subclass to override and change this according to state var searchPredicateForFetchRequest: NSPredicate? { return nil } // ... Typical NSFetchedResultsController and UITableViewDataSource code here. }
That’s it for the basics, but what if my data model is a bit more interesting? In my current project I want my data to be sortable, filterable, searchable, and possibly groupable.
So I define the following:
import CoreData @objc protocol Sortable: NSFetchRequestResult { static func defaultSortDescriptors() -> [NSSortDescriptor]! } @objc protocol Groupable: NSFetchRequestResult { var groupIndex: String! { get } } @objc protocol RelationshipFilterable: NSFetchRequestResult { static func relationshipFilterPredicate(for constraintObject:NSManagedObject?) -> NSPredicate? } @objc protocol TextSearchable: NSFetchRequestResult { static func searchPredicate(for searchTerm:String?) -> NSPredicate? } @objc protocol MyGenericDataObject: Sortable, Groupable, RelationshipFilterable, TextSearchable { // combines them }
Then the cool stuff. Subclass the Basic view controller above:
class GenericFetchedResultsDataSource: BasicFetchedResultsDataSource where T:NSManagedObject { let allowsGrouping: Bool let allowsTextSearching: Bool override init(context: NSManagedObjectContext!, tableView: UITableView!) { self.allowsGrouping = true self.allowsTextSearching = true super.init(context: context, tableView: tableView) } var currentSearchTerm: String? { didSet { if self.allowsTextSearching { do { try self.updateRequestAndFetch() } catch { print("Fetch Error: \(error)") } } } } override var sectionNameKeyPath: String? { return self.allowsGrouping ? #keyPath(MyGenericDataObject.groupIndex) : nil } override var resultsControllerCacheName: String? { return nil } override var sortDescriptorsForFetchRequest: [NSSortDescriptor]! { return T.defaultSortDescriptors() } override var searchPredicateForFetchRequest: NSPredicate? { return self.allowsTextSearching ? T.searchPredicate(for: self.currentSearchTerm) : nil } }
And that’s how you can work with generics and their subclasses. It’s why protocol oriented programming and swift go together nicely!