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

Taming Core Data

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Taming Core Data

Avatar for Arkadiusz Holko

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