Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Taming Core Data

Taming Core Data

Arkadiusz Holko

November 23, 2015
Tweet

More Decks by Arkadiusz Holko

Other Decks in Programming

Transcript

  1. Core Data is a framework that you use to manage

    the model layer objects in your application.
  2. Fetch class TweetListViewController: UITableViewController { ... func fetchTweets() { let

    application = UIApplication.sharedApplication() guard let appDelegate = application.delegate as? AppDelegate else { return } let managedObjectContext = appDelegate.managedObjectContext } }
  3. Fetch class TweetListViewController: UITableViewController { ... func fetchTweets() { let

    application = UIApplication.sharedApplication() guard let appDelegate = application.delegate as? AppDelegate else { return } let managedObjectContext = appDelegate.managedObjectContext let fetchRequest = NSFetchRequest(entityName: "ManagedTweet") fetchRequest.predicate = NSPredicate(format: "homeTimeline != NULL") fetchRequest.sortDescriptors = [NSSortDescriptor(key: "identifier", ascending: false)] } }
  4. Fetch class TweetListViewController: UITableViewController { ... func fetchTweets() { let

    application = UIApplication.sharedApplication() guard let appDelegate = application.delegate as? AppDelegate else { return } let managedObjectContext = appDelegate.managedObjectContext let fetchRequest = NSFetchRequest(entityName: "ManagedTweet") fetchRequest.predicate = NSPredicate(format: "homeTimeline != NULL") fetchRequest.sortDescriptors = [NSSortDescriptor(key: "identifier", ascending: false)] let results = try? managedObjectContext.executeFetchRequest(fetchRequest) if let managedObjects = results as? [NSManagedObject] { data = managedObjects } } }
  5. cellForRowAtIndexPath: override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

    { let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! TweetTableViewCell }
  6. cellForRowAtIndexPath: override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

    { let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! TweetTableViewCell let tweet = data[indexPath.row] }
  7. cellForRowAtIndexPath: override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell

    { let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! TweetTableViewCell let tweet = data[indexPath.row] if let text = tweet.valueForKey("text") as? String { cell.multilineTextLabel?.text = text } // set up other views return cell }
  8. The app delegate works alongside the app object to ensure

    your app interacts properly with the system and with other apps. — Apple
  9. The Easy Way class TweetListViewController: UITableViewController { let managedObjectContext: NSManagedObjectContext

    init() { if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate { managedObjectContext = appDelegate.persistentStack.managedObjectContext } super.init(nibName: nil, bundle: nil) } }
  10. Singleton class TweetListViewController: UITableViewController { let managedObjectContext: NSManagedObjectContext init() {

    managedObjectContext = PersistentStack.sharedInstance().managedObjectContext super.init(nibName: nil, bundle: nil) } }
  11. Dependency Injection class TweetListViewController: UITableViewController { let managedObjectContext: NSManagedObjectContext init(managedObjectContext:

    NSManagedObjectContext) { self.managedObjectContext = managedObjectContext super.init(nibName: nil, bundle: nil) } }
  12. NSManagedObject with KVC let tweet: NSManagedObject = data[indexPath.row] if let

    text = tweet.valueForKey("text") as? String { cell.multilineTextLabel?.text = text }
  13. class _ManagedTweet: NSManagedObject { ... // MARK: - Properties @NSManaged

    var identifier: NSNumber @NSManaged var text: String // MARK: - Relationships @NSManaged var homeTimeline: ManagedHomeTimeline? @NSManaged var user: ManagedUser }
  14. enum ManagedTweetAttributes: String { case identifier = "identifier" case text

    = "text" } enum ManagedTweetRelationships: String { case homeTimeline = "homeTimeline" case user = "user" }
  15. class TweetListRepository { ... func performFetch(completion: [ManagedTweet] -> Void) {

    let fetchRequest = NSFetchRequest(entityName: ManagedTweet.entityName()) let homeTimelineKey = ManagedTweetRelationships.homeTimeline.rawValue fetchRequest.predicate = NSPredicate(format: "%@ != NULL", homeTimelineKey); let identifierKey = ManagedTweetAttributes.identifier.rawValue fetchRequest.sortDescriptors = [NSSortDescriptor(key: identifierKey, ascending: false)] let results = try? managedObjectContext.executeFetchRequest(fetchRequest) if let managedTweets = results as? [ManagedTweet] { completion(managedTweets) } } }
  16. Impact on the view controller override func viewDidLoad() { super.viewDidLoad()

    setupTableView() repository.performFetch { [weak self] managedTweets -> Void in self?.data = managedTweets } }
  17. NSManagedObjectContext gets hidden from the view controller class TweetListViewController: UITableViewController

    { let repository: TweetListRepository init(repository: TweetListRepository) { self.repository = repository super.init(nibName: nil, bundle: nil) } ... }
  18. Data repositories' flow class ProfileRepository { let managedObjectContext: NSManagedObjectContext let

    profileIdentifier: Int64 init(managedObjectContext: NSManagedObjectContext, profileIdentifier: Int64) { self.managedObjectContext = managedObjectContext self.profileIdentifier = profileIdentifier } }
  19. protocol Request { typealias ResultType var method: Method { get

    } var path: String { get } var parameters: Dictionary<String, String> { get } func send(completion: (Result<ResultType, NSError> -> Void)) func resultFromJSON(JSON: AnyObject) throws -> ResultType } Result: github.com/antitypical/Result
  20. extension Request { var method: Method { return .GET }

    var path: String { return "" } var parameters: Dictionary<String, String> { return Dictionary() } }
  21. extension Request { var method: Method { return .GET }

    var path: String { return "" } var parameters: Dictionary<String, String> { return Dictionary() } func send(completion: (Result<ResultType, NSError> -> Void)) { let response = RequestSender().send(self) do { let result = try self.resultFromJSON(response) completion(Result(result)) } catch let nserror as NSError { completion(Result(error: nserror)) } } }
  22. struct Tweet { let identifier: Int let text: String let

    user: User } struct User { let identifier: Int let name: String let profileImageUrl: String? let screenName: String }
  23. class TweetListRequest: Request { typealias ResultType = [Tweet] var path:

    String { return "statuses/home_timeline" } func resultFromJSON(JSON: AnyObject) throws -> [Tweet] { return try [Tweet].decode(JSONString) } } decode: github.com/Anviking/Decodable
  24. class TweetListImporter { let managedObjectContext: NSManagedObjectContext init(managedObjectContext: NSManagedObjectContext) { self.managedObjectContext

    = managedObjectContext } func importTweets(tweets: [Tweet], completion: (Result<Bool, NSError> -> Void)) { ... } }
  25. Value Type → NSManagedObject » Objective-C » Mantle with MTLManagedObjectAdapter

    » Swift » CoreValue, but too powerful » own solution similar to Argo/Decodable
  26. Problem with NSFetchedResultsControllerDelegate optional public func controller(controller: NSFetchedResultsController, didChangeObject anObject:

    AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
  27. Our own delegate protocol Repository { func performFetch() } protocol

    RepositoryDelegate: class { func repository(repository: Repository, didFinishInitialRequestWithResult result: Result<Bool, NSError>) func repositoryWillChangeContent(repository: Repository) func repositoryDidChangeContent(repository: Repository) func repository(repository: Repository, didInsertRowAtIndexPath indexPath: NSIndexPath) func repository(repository: Repository, didDeleteRowAtIndexPath indexPath: NSIndexPath) func repository(repository: Repository, didUpdateRowAtIndexPath indexPath: NSIndexPath) }
  28. From NSFRCDelegate to RepositoryDelegate class TweetListRepository: Repository { // other

    delegate methods omitted for clarity func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { switch (type) { case .Insert: delegate?.repository(self, didInsertRowAtIndexPath: newIndexPath!) case .Delete: delegate?.repository(self, didDeleteRowAtIndexPath: indexPath!) case .Update: delegate?.repository(self, didUpdateRowAtIndexPath: indexPath!) case .Move: // consider adding separate update callback delegate?.repository(self, didDeleteRowAtIndexPath: indexPath!) delegate?.repository(self, didInsertRowAtIndexPath: newIndexPath!) } } }
  29. Reacting to changes class TableViewUpdater: RepositoryDelegate { let tableView: UITableView

    init(tableView: UITableView) { self.tableView = tableView } // some methods omitted func repository(repository: Repository, didInsertRowAtIndexPath indexPath: NSIndexPath) { tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } func repository(repository: Repository, didDeleteRowAtIndexPath indexPath: NSIndexPath) { tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } func repository(repository: Repository, didUpdateRowAtIndexPath indexPath: NSIndexPath) { tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } }
  30. ProfileRepository protocol SingleElementRepository { func performFetch() } protocol SingleElementRepositoryDelegate: class

    { func singleElementRepositoryDidUpdate(repository: SingleElementRepository) } class ProfileRepository: SingleElementRepository { weak var delegate: SingleElementRepositoryDelegate? var user: User? // computed property ... }
  31. protocol TweetType { var identifier: Int { get } var

    text: String { get } var user: UserType { get } } protocol UserType { var identifier: Int { get } var name: String { get } var profileImageUrl: String? { get } var screenName: String { get } }
  32. Reminder struct Tweet: TweetType { let identifier: Int let text:

    String let user: User } struct User: UserType { let identifier: Int let name: String let profileImageUrl: String? let screenName: String }
  33. class TweetListRepository: Repository { weak var delegate: RepositoryDelegate? var objects:

    [TweetType] { let fetchedObjects = fetchedResultsController.fetchedObjects return structsFromManagedObjects(fetchedObjects) } }
  34. Issues » mutability » faulting » lack of thread safety

    » data duplication in memory » CPU time for conversions (can be in the background) » possible lack of synchronization
  35. Links (1/2) » https://www.youtube.com/watch? v=WpkDN78P884 » https://www.destroyallsoftware.com/talks/ boundaries » http://objectsonrails.com/

    » http://blog.8thlight.com/uncle-bob/ 2012/08/13/the-clean-architecture.html » https://realm.io/news/andy-matuschak- controlling-complexity/
  36. Links (2/2) » http://khanlou.com/2015/06/protocol- oriented-networking/ » https://twitter.com/andy_matuschak/status/ 560857237640343552 » https://github.com/rentzsch/mogenerator

    » https://www.objc.io/issues/13-architecture/ viper/ » https://developers.facebooklive.com/videos/ 525/facebook-on-ios-inside-the-big-blue-app