Fetchclass TweetListViewController: UITableViewController { ... func fetchTweets() { let application = UIApplication.sharedApplication() guard let appDelegate = application.delegate as? AppDelegate else { return } let managedObjectContext = appDelegate.managedObjectContext
}}
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)]
}}
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 } }}
data propertyclass TweetListViewController: UITableViewController { ... var data: [ManagedTweet] = [] { didSet { tableView.reloadData() } }}
cellForRowAtIndexPath:override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! TweetTableViewCell
}
cellForRowAtIndexPath:override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier, forIndexPath: indexPath) as! TweetTableViewCell
let tweet = data[indexPath.row]
}
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}
The app delegate works alongside the app object to ensure your app interacts
properly with the system and with other apps.
— Apple
PersistentStackclass PersistentStack { let managedObjectContext: NSManagedObjectContext
init(storeType: String) { ... }}
AppDelegate nowclass AppDelegate: UIResponder, UIApplicationDelegate {
let persistentStack = PersistentStack(NSSQLiteStoreType)
...}
The Easy Wayclass TweetListViewController: UITableViewController { let managedObjectContext: NSManagedObjectContext
init() { if let appDelegate = UIApplication.sharedApplication().delegate as? AppDelegate { managedObjectContext = appDelegate.persistentStack.managedObjectContext } super.init(nibName: nil, bundle: nil) }}
Singletonclass TweetListViewController: UITableViewController { let managedObjectContext: NSManagedObjectContext
init() { managedObjectContext = PersistentStack.sharedInstance().managedObjectContext super.init(nibName: nil, bundle: nil) }}
Dependency Injectionclass TweetListViewController: UITableViewController { let managedObjectContext: NSManagedObjectContext
init(managedObjectContext: NSManagedObjectContext) { self.managedObjectContext = managedObjectContext super.init(nibName: nil, bundle: nil) }}
NSManagedObject with KVClet tweet: NSManagedObject = data[indexPath.row]if let text = tweet.valueForKey("text") as? String { cell.multilineTextLabel?.text = text}
Subclassed NSManagedObjectlet tweet: ManagedTweet = data[indexPath.row] cell.multilineTextLabel.text = tweet.text
class _ManagedTweet: NSManagedObject { ... // MARK: - Properties @NSManaged var identifier: NSNumber
@NSManaged var text: String
// MARK: - Relationships @NSManaged var homeTimeline: ManagedHomeTimeline?
@NSManaged var user: ManagedUser}
enum ManagedTweetAttributes: String { case identifier = "identifier" case text = "text"}
enum ManagedTweetRelationships: String { case homeTimeline = "homeTimeline" case user = "user"}
Repositoryclass TweetListRepository { let managedObjectContext: NSManagedObjectContext
init(managedObjectContext: NSManagedObjectContext) { self.managedObjectContext = managedObjectContext }
...}
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) } }}
Impact on the view controlleroverride func viewDidLoad() { super.viewDidLoad()
setupTableView()
repository.performFetch { [weak self] managedTweets -> Void in self?.data = managedTweets }}
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) }
...}
Data repositories' flowclass ProfileRepository {
let managedObjectContext: NSManagedObjectContext let profileIdentifier: Int
init(managedObjectContext: NSManagedObjectContext, profileIdentifier: Int) { self.managedObjectContext = managedObjectContext self.profileIdentifier = profileIdentifier }}
protocol Repository { var managedObjectContext: NSManagedObjectContext { get }}
extension ProfileRepository { convenience init(repository: Repository, profileIdentifier: Int64) { self.init(managedObjectContext: repository.managedObjectContext, profileIdentifier: profileIdentifier) }}
let profileRepository = ProfileRepository(repository: repository, profileIdentifier: profileIdentifier)let viewController = ProfileViewController(repository: profileRepository)
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
extension Request {
var method: Method { return .GET } var path: String { return "" } var parameters: Dictionary<String, String> { return Dictionary() }
}
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)) } }}
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}
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
class TweetListImporter {
let managedObjectContext: NSManagedObjectContext
init(managedObjectContext: NSManagedObjectContext) { self.managedObjectContext = managedObjectContext }
func importTweets(tweets: [Tweet], completion: (Result<Bool, NSError> -> Void)) { ... }}
Value Type → NSManagedObject» Objective-C
» Mantle with MTLManagedObjectAdapter
» Swift
» CoreValue, but too powerful
» own solution similar to JSON parsing libraries, such as Argo or Decodable
Problem withNSFetchedResultsControllerDelegateoptional public func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
TweetListRepository APIclass TweetListRepository: Repository {
weak var delegate: NSFetchedResultsControllerDelegate?
var objects: [ManagedTweet] // computed property}
TweetListRepository APIclass TweetListRepository: Repository {
weak var delegate: RepositoryDelegate?
var objects: [ManagedTweet] // computed property}
Our own delegateprotocol 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)}
From NSFRCDelegate to RepositoryDelegateclass 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!) } }}
Reacting to changesextension TweetListViewController: RepositoryDelegate {
// 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) }}
ProfileRepositoryprotocol SingleElementRepository { func performFetch()}
protocol SingleElementRepositoryDelegate: class { func singleElementRepositoryDidUpdate(repository: SingleElementRepository)}
class ProfileRepository: SingleElementRepository {
weak var delegate: SingleElementRepositoryDelegate? var user: User? // computed property
...}
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 }}
class TweetListRepository: Repository {
weak var delegate: RepositoryDelegate?
var objects: [TweetType] // computed property}
Those Structsstruct 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}
class TweetListRepository: Repository {
weak var delegate: RepositoryDelegate?
var objects: [TweetType] { // non-optimized version let fetchedObjects = fetchedResultsController.fetchedObjects return structsFromManagedObjects(fetchedObjects) }}
Mutationfunc favoriteTweet(tweet: TweetType) { // modifies corresponding managed object under the hood}
New Issues» data duplication in memory
» CPU time for conversions (can be in the background)
» possible lack of synchronization
Slideshttps://speakerdeck.com/fastred/taming-core-data-cocoaheads
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/
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