[observable] Implement Observable feature for Storage

This commit is contained in:
Pedro Piñera Buendía 2016-05-07 22:13:54 +02:00
parent 16a81a4554
commit 74b4f98c2a
17 changed files with 363 additions and 148 deletions

View File

@ -47,16 +47,6 @@
2310154E1CDCCA42004486FA /* CoreDataObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2310154C1CDCCA42004486FA /* CoreDataObservable.swift */; };
2310154F1CDCCA42004486FA /* CoreDataObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2310154C1CDCCA42004486FA /* CoreDataObservable.swift */; };
231015501CDCCA42004486FA /* CoreDataObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2310154C1CDCCA42004486FA /* CoreDataObservable.swift */; };
231015511CDCCA42004486FA /* CoreDataObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2310154C1CDCCA42004486FA /* CoreDataObservable.swift */; };
231015531CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
231015541CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
231015551CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
231015561CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
231015571CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
231015581CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
231015591CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
2310155A1CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
2310155B1CDCCE9F004486FA /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 231015521CDCCE9F004486FA /* ArrayExtension.swift */; };
2310155D1CDCCF8D004486FA /* CoreDataChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2310155C1CDCCF8D004486FA /* CoreDataChange.swift */; };
2310155E1CDCCF8D004486FA /* CoreDataChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2310155C1CDCCF8D004486FA /* CoreDataChange.swift */; };
2310155F1CDCCF8D004486FA /* CoreDataChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2310155C1CDCCF8D004486FA /* CoreDataChange.swift */; };
@ -268,6 +258,16 @@
237A93D01C62A28B006BC2F1 /* iCloudConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2388637E1C5C285F0048B691 /* iCloudConfig.swift */; };
239C8E051BF48B680025DB9A /* ContextParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239C8E041BF48B680025DB9A /* ContextParent.swift */; };
239C8E061BF48B680025DB9A /* ContextParent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239C8E041BF48B680025DB9A /* ContextParent.swift */; };
239FE4CE1CDE67DE0050502F /* CoreDataObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CD1CDE67DE0050502F /* CoreDataObservableTests.swift */; };
239FE4D11CDE7FCE0050502F /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */; };
239FE4D21CDE7FCE0050502F /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */; };
239FE4D31CDE7FCF0050502F /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */; };
239FE4D41CDE7FCF0050502F /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */; };
239FE4D51CDE7FD00050502F /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */; };
239FE4D61CDE7FD00050502F /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */; };
239FE4D71CDE7FD10050502F /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */; };
239FE4D81CDE7FD10050502F /* ArrayExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */; };
239FE4DA1CDE81D00050502F /* CoreDataChangeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 239FE4D91CDE81D00050502F /* CoreDataChangeTests.swift */; };
23A916751C17365A005E57D8 /* DirUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A916741C17365A005E57D8 /* DirUtilsTests.swift */; };
23A916771C173CAF005E57D8 /* OptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A916761C173CAF005E57D8 /* OptionsTests.swift */; };
23A916791C173E5F005E57D8 /* CoreDataDefaultStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23A916781C173E5F005E57D8 /* CoreDataDefaultStorageTests.swift */; };
@ -339,7 +339,6 @@
231015401CDC9F0B004486FA /* Observable+ReactiveCocoa.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Observable+ReactiveCocoa.swift"; sourceTree = "<group>"; };
2310154A1CDCB529004486FA /* RealmObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmObservableTests.swift; sourceTree = "<group>"; };
2310154C1CDCCA42004486FA /* CoreDataObservable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataObservable.swift; sourceTree = "<group>"; };
231015521CDCCE9F004486FA /* ArrayExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = "<group>"; };
2310155C1CDCCF8D004486FA /* CoreDataChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataChange.swift; sourceTree = "<group>"; };
2319B7FA1C23FF8F0050ABE8 /* SugarRecordRealm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SugarRecordRealm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2319B81F1C2400420050ABE8 /* SugarRecordRealm.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SugarRecordRealm.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -394,6 +393,9 @@
238863791C5C239C0048B691 /* CoreDataiCloudStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataiCloudStorage.swift; sourceTree = "<group>"; };
2388637E1C5C285F0048B691 /* iCloudConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = iCloudConfig.swift; sourceTree = "<group>"; };
239C8E041BF48B680025DB9A /* ContextParent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextParent.swift; sourceTree = "<group>"; };
239FE4CD1CDE67DE0050502F /* CoreDataObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataObservableTests.swift; sourceTree = "<group>"; };
239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArrayExtension.swift; sourceTree = "<group>"; };
239FE4D91CDE81D00050502F /* CoreDataChangeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataChangeTests.swift; sourceTree = "<group>"; };
23A916741C17365A005E57D8 /* DirUtilsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirUtilsTests.swift; sourceTree = "<group>"; };
23A916761C173CAF005E57D8 /* OptionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OptionsTests.swift; sourceTree = "<group>"; };
23A916781C173E5F005E57D8 /* CoreDataDefaultStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CoreDataDefaultStorageTests.swift; path = Storage/CoreDataDefaultStorageTests.swift; sourceTree = "<group>"; };
@ -578,6 +580,8 @@
2305F8ED1BF0FAFC00F9D379 /* ObjectModelTests.swift */,
2305F8EB1BF0F89200F9D379 /* StoreTests.swift */,
23A916761C173CAF005E57D8 /* OptionsTests.swift */,
239FE4CD1CDE67DE0050502F /* CoreDataObservableTests.swift */,
239FE4D91CDE81D00050502F /* CoreDataChangeTests.swift */,
);
path = Entities;
sourceTree = "<group>";
@ -722,7 +726,7 @@
isa = PBXGroup;
children = (
2353E6331BED4AC00081E6E7 /* RequestExtension.swift */,
231015521CDCCE9F004486FA /* ArrayExtension.swift */,
239FE4CF1CDE7FC90050502F /* ArrayExtension.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -1704,7 +1708,7 @@
2319B7DF1C23FF8F0050ABE8 /* Requestable.swift in Sources */,
2319B7E11C23FF8F0050ABE8 /* ReactiveError.swift in Sources */,
2319B7E21C23FF8F0050ABE8 /* NSPredicateConvertible.swift in Sources */,
231015541CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
239FE4D71CDE7FD10050502F /* ArrayExtension.swift in Sources */,
2319B7E31C23FF8F0050ABE8 /* Error.swift in Sources */,
2319B7E41C23FF8F0050ABE8 /* Context.swift in Sources */,
2319B7E51C23FF8F0050ABE8 /* Results.swift in Sources */,
@ -1734,7 +1738,7 @@
2319B8001C2400420050ABE8 /* Request.swift in Sources */,
2319B8061C2400420050ABE8 /* NSSortDescriptorConvertible.swift in Sources */,
2319B80A1C2400420050ABE8 /* NSPredicateConvertible.swift in Sources */,
231015561CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
239FE4D51CDE7FD00050502F /* ArrayExtension.swift in Sources */,
2319B80B1C2400420050ABE8 /* Error.swift in Sources */,
2319B80C1C2400420050ABE8 /* Context.swift in Sources */,
2319B80E1C2400420050ABE8 /* RequestExtension.swift in Sources */,
@ -1764,7 +1768,7 @@
233390171C67B41400844B9D /* ReactiveStorage+ReactiveCocoa.swift in Sources */,
2319B87F1C2402300050ABE8 /* NSSortDescriptorConvertible.swift in Sources */,
2319B8801C2402300050ABE8 /* Context.swift in Sources */,
231015581CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
239FE4D31CDE7FCF0050502F /* ArrayExtension.swift in Sources */,
2319B8811C2402300050ABE8 /* Entity.swift in Sources */,
2319B8821C2402300050ABE8 /* Requestable.swift in Sources */,
2319B8741C2402290050ABE8 /* Realm.swift in Sources */,
@ -1794,7 +1798,7 @@
233390191C67B41400844B9D /* ReactiveStorage+ReactiveCocoa.swift in Sources */,
2319B8D51C2402C30050ABE8 /* NSSortDescriptorConvertible.swift in Sources */,
2319B8D61C2402C30050ABE8 /* Context.swift in Sources */,
2310155A1CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
239FE4D11CDE7FCE0050502F /* ArrayExtension.swift in Sources */,
2319B8D71C2402C30050ABE8 /* Entity.swift in Sources */,
2319B8D81C2402C30050ABE8 /* Requestable.swift in Sources */,
2319B8C81C2402BA0050ABE8 /* ReactiveError.swift in Sources */,
@ -1829,6 +1833,7 @@
2310154D1CDCCA42004486FA /* CoreDataObservable.swift in Sources */,
2333901A1C67B41400844B9D /* ReactiveStorage+Rx.swift in Sources */,
231015251CDC8EC3004486FA /* Observable.swift in Sources */,
239FE4D81CDE7FD10050502F /* ArrayExtension.swift in Sources */,
2353E6671BED4B250081E6E7 /* CoreData.swift in Sources */,
23A9167D1C173FCF005E57D8 /* Requestable.swift in Sources */,
2353E63D1BED4AC00081E6E7 /* NSManagedObjectContext.swift in Sources */,
@ -1840,7 +1845,6 @@
239C8E051BF48B680025DB9A /* ContextParent.swift in Sources */,
2353E6511BED4AC00081E6E7 /* RequestExtension.swift in Sources */,
2305F8FF1BF1EA8D00F9D379 /* Entity.swift in Sources */,
231015531CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
2353E6491BED4AC00081E6E7 /* Storage.swift in Sources */,
233390121C67B41400844B9D /* ReactiveStorage+ReactiveCocoa.swift in Sources */,
2305F8F11BF0FE4500F9D379 /* DirUtils.swift in Sources */,
@ -1866,6 +1870,7 @@
2310154E1CDCCA42004486FA /* CoreDataObservable.swift in Sources */,
2353E6681BED4B250081E6E7 /* CoreData.swift in Sources */,
231015271CDC8EC3004486FA /* Observable.swift in Sources */,
239FE4D61CDE7FD00050502F /* ArrayExtension.swift in Sources */,
239C8E061BF48B680025DB9A /* ContextParent.swift in Sources */,
2353E63E1BED4AC00081E6E7 /* NSManagedObjectContext.swift in Sources */,
2353E65A1BED4AC00081E6E7 /* NSPredicateConvertible.swift in Sources */,
@ -1877,7 +1882,6 @@
23F3F36B1C1DFA6D009A5CC1 /* ReactiveError.swift in Sources */,
23AC16271BF370BA00FAF97A /* Entity.swift in Sources */,
2353E64A1BED4AC00081E6E7 /* Storage.swift in Sources */,
231015551CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
23A9167E1C173FCF005E57D8 /* Requestable.swift in Sources */,
233390141C67B41400844B9D /* ReactiveStorage+ReactiveCocoa.swift in Sources */,
2305F8F21BF0FE4500F9D379 /* DirUtils.swift in Sources */,
@ -1904,7 +1908,6 @@
2319B86F1C2401F90050ABE8 /* Entity.swift in Sources */,
2319B8701C2401F90050ABE8 /* Requestable.swift in Sources */,
2333901E1C67B41400844B9D /* ReactiveStorage+Rx.swift in Sources */,
231015571CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
2319B85D1C2401F20050ABE8 /* CoreData.swift in Sources */,
2319B85E1C2401F20050ABE8 /* ObjectModel.swift in Sources */,
2319B85F1C2401F20050ABE8 /* Store.swift in Sources */,
@ -1916,6 +1919,7 @@
2319B8641C2401F20050ABE8 /* NSManagedObjectContext.swift in Sources */,
2310153C1CDC9C64004486FA /* Observable+Rx.swift in Sources */,
2310154F1CDCCA42004486FA /* CoreDataObservable.swift in Sources */,
239FE4D41CDE7FCF0050502F /* ArrayExtension.swift in Sources */,
2319B8651C2401F20050ABE8 /* NSManagedObjectMemoryContext.swift in Sources */,
2319B8661C2401F20050ABE8 /* CoreDataDefaultStorage.swift in Sources */,
2319B85B1C2401E80050ABE8 /* ReactiveError.swift in Sources */,
@ -1942,7 +1946,6 @@
2319B8C51C2402960050ABE8 /* NSManagedObjectMemoryContext.swift in Sources */,
2319B8C61C2402960050ABE8 /* CoreDataDefaultStorage.swift in Sources */,
233390201C67B41400844B9D /* ReactiveStorage+Rx.swift in Sources */,
231015591CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
2319B8B21C24028C0050ABE8 /* DirUtils.swift in Sources */,
2319B8B31C24028C0050ABE8 /* Request.swift in Sources */,
2319B8B41C24028C0050ABE8 /* Storage.swift in Sources */,
@ -1954,6 +1957,7 @@
2319B8B91C24028C0050ABE8 /* Context.swift in Sources */,
2310153E1CDC9C64004486FA /* Observable+Rx.swift in Sources */,
231015501CDCCA42004486FA /* CoreDataObservable.swift in Sources */,
239FE4D21CDE7FCE0050502F /* ArrayExtension.swift in Sources */,
2319B8BA1C24028C0050ABE8 /* Entity.swift in Sources */,
2319B8BB1C24028C0050ABE8 /* Requestable.swift in Sources */,
2319B8B11C2402850050ABE8 /* ReactiveError.swift in Sources */,
@ -1972,14 +1976,14 @@
23B15C521BF352AD0059008E /* Issue.swift in Sources */,
3DE3FD961BF12B8C00009071 /* DataModel.xcdatamodeld in Sources */,
3DDE7D451BF4E45E00D166C4 /* Track+CoreDataProperties.swift in Sources */,
231015511CDCCA42004486FA /* CoreDataObservable.swift in Sources */,
230CBDC21BF37F7A00C43A48 /* ResultsTests.swift in Sources */,
2305F8EE1BF0FAFC00F9D379 /* ObjectModelTests.swift in Sources */,
23A916791C173E5F005E57D8 /* CoreDataDefaultStorageTests.swift in Sources */,
239FE4CE1CDE67DE0050502F /* CoreDataObservableTests.swift in Sources */,
239FE4DA1CDE81D00050502F /* CoreDataChangeTests.swift in Sources */,
23B15C591BF353F70059008E /* Realm.swift in Sources */,
23B15C571BF3535E0059008E /* RealmTests.swift in Sources */,
3D733FD51C0C2AC400B74DA1 /* RequestTests.swift in Sources */,
2310155B1CDCCE9F004486FA /* ArrayExtension.swift in Sources */,
231C61EF1C17482100CD6532 /* CoreData.swift in Sources */,
2305F8EC1BF0F89200F9D379 /* StoreTests.swift in Sources */,
23B15C501BF352540059008E /* Repository.swift in Sources */,

View File

@ -2,8 +2,6 @@ import Foundation
internal enum CoreDataChange<T> {
// TODO - Test
case Update(T)
case Insert(T)
case Delete(T)
@ -18,7 +16,7 @@ internal enum CoreDataChange<T> {
func isDeletion() -> Bool {
switch self {
case .Insert(_): return true
case .Delete(_): return true
default: return false
}
}
@ -36,4 +34,5 @@ internal enum CoreDataChange<T> {
default: return false
}
}
}

View File

@ -1,74 +1,69 @@
//import Foundation
//import CoreData
//
//public class CoreDataObservable<T: NSManagedObject>: Observable<T>, NSFetchedResultsControllerDelegate {
//
// // MARK: - Attributes
//
// internal let fetchedResultsController: NSFetchedResultsController
// internal let fetchRequest: NSFetchRequest
// internal var observer: (ObservableChange<[T]> -> Void)?
// private var batchChanges: [CoreDataChange<T>] = []
//
//
// // MARK: - Init
//
// public init(request: Request<T>, context: NSManagedObjectContext) {
// let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: T.entityName)
// if let predicate = request.predicate {
// fetchRequest.predicate = predicate
// }
// if let sortDescriptor = request.sortDescriptor {
// fetchRequest.sortDescriptors = [sortDescriptor]
// }
// self.fetchRequest = fetchRequest
// self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
// super.init(request: request)
// self.fetchedResultsController.delegate = self
// }
//
//
// // MARK: - Observable
//
// public override func observe(closure: ObservableChange<[T]> -> Void) {
// assert(self.observer != nil, "Observable can be observed only once")
// let initial = try! self.fetchedResultsController.managedObjectContext.executeFetchRequest(self.fetchRequest) as! [T]
// closure(ObservableChange.Initial(initial))
// self.observer = closure
// }
//
// // MARK: - NSFetchedResultsControllerDelegate
//
// public func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
// switch type {
// case .Delete:
// self.batchChanges.append(.Delete(anObject as! T))
// case .Insert:
// self.batchChanges.append(.Insert(anObject as! T))
// case .Update:
// self.batchChanges.append(.Update(anObject as! T))
// default: break
// }
// }
//
// public func controllerWillChangeContent(controller: NSFetchedResultsController) {
// self.batchChanges = []
// }
//
// public func controllerDidChangeContent(controller: NSFetchedResultsController) {
// let objects = self.batchChanges.map {$0.object()}
// let deleted = self.batchChanges.filterAndMapIndex { $0.isDeletion() }
// let inserted = self.batchChanges.filterAndMapIndex { $0.isInsertion() }
// let updated = self.batchChanges.filterAndMapIndex { $0.isUpdate() }
// self.observer?(ObservableChange.Update(objects, deletions: deleted, insertions: inserted, modifications: updated))
// }
//
//
// // MARK: - Deinit
//
// deinit {
// self.fetchedResultsController.delegate = nil
// self.observer = nil
// }
//
//}
import Foundation
import CoreData
public class CoreDataObservable<T: NSManagedObject where T:Equatable>: Observable<T>, NSFetchedResultsControllerDelegate {
// MARK: - Attributes
internal let fetchRequest: NSFetchRequest
internal var observer: (ObservableChange<[T]> -> Void)?
internal let fetchedResultsController: NSFetchedResultsController
private var batchChanges: [CoreDataChange<T>] = []
// MARK: - Init
internal init(request: Request<T>, context: NSManagedObjectContext) {
let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: T.entityName)
if let predicate = request.predicate {
fetchRequest.predicate = predicate
}
if let sortDescriptor = request.sortDescriptor {
fetchRequest.sortDescriptors = [sortDescriptor]
}
fetchRequest.fetchBatchSize = 0
self.fetchRequest = fetchRequest
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
super.init(request: request)
self.fetchedResultsController.delegate = self
}
// MARK: - Observable
public override func observe(closure: ObservableChange<[T]> -> Void) {
assert(self.observer == nil, "Observable can be observed only once")
let initial = try! self.fetchedResultsController.managedObjectContext.executeFetchRequest(self.fetchRequest) as! [T]
closure(ObservableChange.Initial(initial))
self.observer = closure
_ = try? self.fetchedResultsController.performFetch()
}
// MARK: - NSFetchedResultsControllerDelegate
public func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Delete:
self.batchChanges.append(.Delete(anObject as! T))
case .Insert:
self.batchChanges.append(.Insert(anObject as! T))
case .Update:
self.batchChanges.append(.Update(anObject as! T))
default: break
}
}
public func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.batchChanges = []
}
public func controllerDidChangeContent(controller: NSFetchedResultsController) {
let deleted = self.batchChanges.filter { $0.isDeletion() }.map { $0.object() }
let inserted = self.batchChanges.filter { $0.isInsertion() }.map { $0.object() }
let updated = self.batchChanges.filter { $0.isUpdate() }.map { $0.object() }
self.observer?(ObservableChange.Update(deletions: deleted, insertions: inserted, modifications: updated))
}
}

View File

@ -11,7 +11,7 @@ public extension CoreData {
switch self {
case .Default:
var sqliteOptions: [String: String] = [String: String] ()
sqliteOptions["WAL"] = "journal_mode"
sqliteOptions["journal_mode"] = "DELETE"
var options: [NSObject: AnyObject] = [NSObject: AnyObject] ()
options[NSMigratePersistentStoresAutomaticallyOption] = NSNumber(bool: true)
options[NSInferMappingModelAutomaticallyOption] = NSNumber(bool: false)
@ -19,7 +19,7 @@ public extension CoreData {
return options
case .Migration:
var sqliteOptions: [String: String] = [String: String] ()
sqliteOptions["WAL"] = "journal_mode"
sqliteOptions["journal_mode"] = "DELETE"
var options: [NSObject: AnyObject] = [NSObject: AnyObject] ()
options[NSMigratePersistentStoresAutomaticallyOption] = NSNumber(bool: true)
options[NSInferMappingModelAutomaticallyOption] = NSNumber(bool: true)

View File

@ -73,6 +73,9 @@ public class CoreDataDefaultStorage: Storage {
public func removeStore() throws {
try NSFileManager.defaultManager().removeItemAtURL(store.path())
_ = try? NSFileManager.defaultManager().removeItemAtPath("\(store.path().absoluteString)-shm")
_ = try? NSFileManager.defaultManager().removeItemAtPath("\(store.path().absoluteString)-wal")
}
@ -90,9 +93,9 @@ public class CoreDataDefaultStorage: Storage {
// MARK: - Public
// public func observable<T: NSManagedObject>(request: Request<T>) -> Observable<T> {
// return CoreDataObservable(request: request, context: self.mainContext as! NSManagedObjectContext)
// }
public func observable<T: NSManagedObject where T:Equatable>(request: Request<T>) -> Observable<T> {
return CoreDataObservable(request: request, context: self.mainContext as! NSManagedObjectContext)
}
}

View File

@ -92,10 +92,10 @@ public class CoreDataiCloudStorage: Storage {
// MARK: - Public
// public func observable<T: NSManagedObject>(request: Request<T>) -> Observable<T> {
// return CoreDataObservable(request: request, context: self.mainContext as! NSManagedObjectContext)
// }
//
public func observable<T: NSManagedObject where T:Equatable>(request: Request<T>) -> Observable<T> {
return CoreDataObservable(request: request, context: self.mainContext as! NSManagedObjectContext)
}
// MARK: - Private

View File

@ -2,7 +2,7 @@ import Foundation
public enum ObservableChange<T> {
case Initial(T)
case Update(T, deletions: [Int], insertions: [Int], modifications: [Int])
case Update(deletions: T, insertions: T, modifications: T)
case Error(NSError)
}

View File

@ -77,7 +77,7 @@ public struct Request<T: Entity>: Equatable {
// MARK: - Equatable
public func ==<T>(lhs: Request<T>, rhs: Request<T>) -> Bool {
public func == <T>(lhs: Request<T>, rhs: Request<T>) -> Bool {
return lhs.sortDescriptor == rhs.sortDescriptor &&
lhs.predicate == rhs.predicate
}

View File

@ -5,6 +5,8 @@ public enum StorageType {
case Realm
}
typealias StorageOperation = ((context: Context, save: () -> Void) throws -> Void) throws -> Void
public protocol Storage: CustomStringConvertible, Requestable {
var type: StorageType { get }

View File

@ -1,15 +1,15 @@
import Foundation
extension Array {
// TODO: - Test
func filterAndMapIndex(include: Element -> Bool) -> [Int] {
var indexes: [Int] = []
self.enumerate().forEach { (element) in
if include(element.element) {
indexes.append(element.index)
func filter(closure: (index: Int, element: Element) -> Bool) -> [Element] {
var filtered: [Element] = []
var index: Int = 0
self.forEach { (element) in
if closure(index: index, element: element) {
filtered.append(element)
}
index = index + 1
}
return indexes
return filtered
}
}

View File

@ -5,9 +5,9 @@ public extension Observable {
public func rx_observe() -> RxSwift.Observable<ObservableChange<[T]>> {
return RxSwift.Observable.create { (observer) -> Disposable in
self.observe({ (change) in
self.observe { change in
observer.onNext(change)
})
}
return AnonymousDisposable {
self.dispose()
observer.onCompleted()

View File

@ -47,7 +47,10 @@ public class RealmObservable<T: Object>: Observable<T> {
case .Initial(let initial):
return ObservableChange.Initial(initial.toArray())
case .Update(let objects, let deletions, let insertions, let modifications):
return ObservableChange.Update(objects.toArray(), deletions: deletions, insertions: insertions, modifications: modifications)
let deletions = objects.toArray().filter { deletions.indexOf($0.0) != nil }
let insertions = objects.toArray().filter { insertions.indexOf($0.0) != nil }
let modifications = objects.toArray().filter { modifications.indexOf($0.0) != nil }
return ObservableChange.Update(deletions: deletions, insertions: insertions, modifications: modifications)
}
}

View File

@ -0,0 +1,110 @@
import Foundation
import Quick
import Nimble
@testable import SugarRecordCoreData
class CoreDataChangeTests: QuickSpec {
override func spec() {
context("Insert") {
var change: CoreDataChange<String>!
beforeSuite {
change = .Insert("insert")
}
describe("-object") {
it("should return the correct object") {
expect(change.object()) == "insert"
}
}
describe("-isDeletion") {
it("should return false") {
expect(change.isDeletion()) == false
}
}
describe("-isUpdate") {
it("should return false") {
expect(change.isUpdate()) == false
}
}
describe("-isInsertion") {
it("should return true") {
expect(change.isInsertion()) == true
}
}
}
context("Update") {
var change: CoreDataChange<String>!
beforeSuite {
change = .Update("update")
}
describe("-object") {
it("should return the correct object") {
expect(change.object()) == "update"
}
}
describe("-isDeletion") {
it("should return false") {
expect(change.isDeletion()) == false
}
}
describe("-isUpdate") {
it("should return true") {
expect(change.isUpdate()) == true
}
}
describe("-isInsertion") {
it("should return false") {
expect(change.isInsertion()) == false
}
}
}
context("Delete") {
var change: CoreDataChange<String>!
beforeSuite {
change = .Delete("delete")
}
describe("-object") {
it("should return the correct object") {
expect(change.object()) == "delete"
}
}
describe("-isDeletion") {
it("should return true") {
expect(change.isDeletion()) == true
}
}
describe("-isUpdate") {
it("should return false") {
expect(change.isUpdate()) == false
}
}
describe("-isInsertion") {
it("should return false") {
expect(change.isInsertion()) == false
}
}
}
}
}

View File

@ -0,0 +1,77 @@
import Foundation
import Quick
import Nimble
import CoreData
@testable import SugarRecordCoreData
class CoreDataObservableTests: QuickSpec {
override func spec() {
var request: Request<Track>!
var subject: CoreDataObservable<Track>!
var storage: CoreDataDefaultStorage!
beforeEach {
let store: CoreData.Store = CoreData.Store.Named("test")
let bundle = NSBundle(forClass: self.classForCoder)
let model = CoreData.ObjectModel.Merged([bundle])
storage = try! CoreDataDefaultStorage(store: store, model: model)
_ = try? storage.removeStore()
request = Request<Track>().sortedWith("name", ascending: true)
let context: NSManagedObjectContext = storage.mainContext as! NSManagedObjectContext
subject = CoreDataObservable(request: request, context: context)
context.performBlock({
let track: Track = try! context.create()
track.name = "test"
track.artist = "pedro"
try! context.save()
})
}
afterEach {
_ = try? storage.removeStore()
}
describe("-observe:") {
it("should report the initial state") {
waitUntil(action: { (done) in
subject.observe({ (change) in
switch change {
case .Initial(let values):
expect(values.first?.name) == "test"
expect(values.first?.artist) == "pedro"
done()
default:
break
}
})
})
}
it("should report updates") {
waitUntil(action: { (done) in
subject.observe({ (change) in
switch change {
case .Update(_, let insertions, _):
expect(insertions[0].name) == "test2"
expect(insertions[0].artist) == "pedro"
done()
default:
break
}
})
let context: NSManagedObjectContext = storage.mainContext as! NSManagedObjectContext
context.performBlockAndWait {
let track: Track = try! context.create()
track.name = "test2"
track.artist = "pedro"
try! context.save()
}
})
}
}
}
}

View File

@ -14,7 +14,7 @@ class OptionsTests: QuickSpec {
it("should return the right data for default options") {
let options = CoreData.Options.Default.dict()
let sqliteOptions = options[NSSQLitePragmasOption] as! [String: String]
expect(sqliteOptions["WAL"]) == "journal_mode"
expect(sqliteOptions["journal_mode"]) == "DELETE"
expect(options[NSMigratePersistentStoresAutomaticallyOption] as? NSNumber) == NSNumber(bool: true)
expect(options[NSInferMappingModelAutomaticallyOption] as? NSNumber) == NSNumber(bool: false)
}
@ -22,7 +22,7 @@ class OptionsTests: QuickSpec {
it("should return the right data for migration options") {
let options = CoreData.Options.Migration.dict()
let sqliteOptions = options[NSSQLitePragmasOption] as! [String: String]
expect(sqliteOptions["WAL"]) == "journal_mode"
expect(sqliteOptions["journal_mode"]) == "DELETE"
expect(options[NSMigratePersistentStoresAutomaticallyOption] as? NSNumber) == NSNumber(bool: true)
expect(options[NSInferMappingModelAutomaticallyOption] as? NSNumber) == NSNumber(bool: true)
}

View File

@ -10,19 +10,19 @@ class CoreDataDefaultStorageTests: QuickSpec {
describe("storage") { () -> Void in
var store: CoreData.Store?
var model: CoreData.ObjectModel?
var defaultStorage: CoreDataDefaultStorage?
var store: CoreData.Store!
var model: CoreData.ObjectModel!
var subject: CoreDataDefaultStorage!
beforeEach {
store = CoreData.Store.Named("test")
let bundle = NSBundle(forClass: self.classForCoder)
model = CoreData.ObjectModel.Merged([bundle])
defaultStorage = try! CoreDataDefaultStorage(store: store!, model: model!)
subject = try! CoreDataDefaultStorage(store: store!, model: model!)
}
afterEach {
_ = try? defaultStorage?.removeStore()
_ = try? subject?.removeStore()
}
context("initialization") {
@ -33,57 +33,57 @@ class CoreDataDefaultStorageTests: QuickSpec {
}
it("should have the right description") {
expect(defaultStorage?.description) == "CoreDataDefaultStorage"
expect(subject?.description) == "CoreDataDefaultStorage"
}
it("should have the right type") {
expect(defaultStorage?.type) == .CoreData
expect(subject?.type) == .CoreData
}
describe("root saving context") {
it("should have the persistent store coordinator as parent") {
expect(defaultStorage?.rootSavingContext.persistentStoreCoordinator) == defaultStorage?.persistentStoreCoordinator
expect(subject?.rootSavingContext.persistentStoreCoordinator) == subject?.persistentStoreCoordinator
}
it("should have private concurrency type") {
expect(defaultStorage?.rootSavingContext.concurrencyType) == .PrivateQueueConcurrencyType
expect(subject?.rootSavingContext.concurrencyType) == .PrivateQueueConcurrencyType
}
}
describe("save context") {
it("should have the root saving context as parent") {
expect((defaultStorage?.saveContext as! NSManagedObjectContext).parentContext) == defaultStorage?.rootSavingContext
expect((subject?.saveContext as! NSManagedObjectContext).parentContext) == subject?.rootSavingContext
}
it("should have private concurrency type") {
expect((defaultStorage?.saveContext as! NSManagedObjectContext).concurrencyType) == NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType
expect((subject?.saveContext as! NSManagedObjectContext).concurrencyType) == NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType
}
}
describe("memory context") {
it("should have the root saving context as parent") {
expect((defaultStorage?.memoryContext as! NSManagedObjectContext).parentContext) == defaultStorage?.rootSavingContext
expect((subject?.memoryContext as! NSManagedObjectContext).parentContext) == subject?.rootSavingContext
}
it("should have private concurrency type") {
expect((defaultStorage?.memoryContext as! NSManagedObjectContext).concurrencyType) == NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType
expect((subject?.memoryContext as! NSManagedObjectContext).concurrencyType) == NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType
}
}
describe("main context") {
it("should have the root saving context as parent") {
expect((defaultStorage?.mainContext as! NSManagedObjectContext).parentContext) == defaultStorage?.rootSavingContext
expect((subject?.mainContext as! NSManagedObjectContext).parentContext) == subject?.rootSavingContext
}
it("should have main concurrency type") {
expect((defaultStorage?.mainContext as! NSManagedObjectContext).concurrencyType) == NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType
expect((subject?.mainContext as! NSManagedObjectContext).concurrencyType) == NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType
}
}
}
context("removal") {
it("should properly remove the store") {
_ = try? defaultStorage?.removeStore()
_ = try? subject?.removeStore()
let path = store!.path().path!
expect(NSFileManager.defaultManager().fileExistsAtPath(path)) == false
}
@ -93,10 +93,10 @@ class CoreDataDefaultStorageTests: QuickSpec {
it("shouldn't persist changes if we save the memory context") {
waitUntil(action: { (done) -> Void in
let memoryContext = defaultStorage!.memoryContext as! NSManagedObjectContext!
let memoryContext = subject!.memoryContext as! NSManagedObjectContext!
let _: Track = try! memoryContext.create()
try! memoryContext.save()
_ = try? defaultStorage?.operation({ (context, save) -> Void in
_ = try? subject?.operation({ (context, save) -> Void in
let resultsCount = try! context.request(Track.self).fetch().count
expect(resultsCount) == 0
done()
@ -106,17 +106,40 @@ class CoreDataDefaultStorageTests: QuickSpec {
it("should persist the changes if it's save context") {
waitUntil(action: { (done) -> Void in
_ = try? defaultStorage?.operation({ (context, save) -> Void in
_ = try? subject?.operation({ (context, save) -> Void in
let _: Track = try! context.create()
save()
})
let tracksCount: Int? = try! defaultStorage?.mainContext.request(Track.self).fetch().count
let tracksCount: Int? = try! subject?.mainContext.request(Track.self).fetch().count
expect(tracksCount) == 1
done()
})
}
}
describe("-observable:") {
var request: Request<Track>!
var observable: CoreDataObservable<Track>!
beforeEach {
request = Request<Track>().filteredWith("name", equalTo: "test").sortedWith("name", ascending: true)
observable = subject.observable(request) as! CoreDataObservable<Track>
}
it("should have the correct request predicate") {
expect(observable.fetchRequest.predicate) == request.predicate
}
it("should have the correct request sort descriptor") {
expect(observable.fetchRequest.sortDescriptors) == [request.sortDescriptor!]
}
it("should have the correct context") {
expect(observable.fetchedResultsController.managedObjectContext) == subject.mainContext! as? NSManagedObjectContext
}
}
}
}

View File

@ -52,9 +52,8 @@ class RealmObservableTests: QuickSpec {
waitUntil(timeout: 5.0, action: { (done) in
subject.observe({ (change) in
switch change {
case .Update(let updated, _, let insertions, _):
expect(insertions.first) == 1
expect(updated[1].id) == "666"
case .Update(_, let insertions, _):
expect(insertions[0].id) == "666"
done()
default:
break