smart way to load entityNames from the model file

This commit is contained in:
John Rommel Estropia
2015-02-22 22:01:23 +09:00
parent f6ced13577
commit 8f77818015
8 changed files with 88 additions and 44 deletions

View File

@@ -38,14 +38,14 @@ private let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey
/**
The DataStack encapsulates the data model for the Core Data stack. Each DataStack can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own NSPersistentStoreCoordinator, a root NSManagedObjectContext for disk saves, and a shared NSManagedObjectContext acting as a model interface for NSManagedObjects.
*/
public class DataStack: NSObject {
public class DataStack {
// MARK: Public
/**
Initializes a DataStack from merged model in the app bundle.
*/
public convenience override init() {
public convenience init() {
self.init(managedObjectModel: NSManagedObjectModel.mergedModelFromBundles(NSBundle.allBundles())!)
}
@@ -74,8 +74,18 @@ public class DataStack: NSObject {
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
self.transactionQueue = .createSerial("com.hardcoredata.datastack.transactionqueue")
self.entityNameMapping = (managedObjectModel.entities as! [NSEntityDescription]).reduce([EntityClassNameType: EntityNameType]()) { (var mapping, entityDescription) in
if let entityName = entityDescription.name {
mapping[entityDescription.managedObjectClassName] = entityName
}
return mapping
}
super.init()
println(self.entityNameMapping)
self.rootSavingContext.parentStack = self
}
/**
@@ -133,10 +143,14 @@ public class DataStack: NSObject {
public func addSQLiteStore(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult {
return self.addSQLiteStore(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(fileName, isDirectory: false),
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
automigrating: automigrating,
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure)
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
)
}
/**
@@ -257,9 +271,18 @@ public class DataStack: NSObject {
internal let mainContext: NSManagedObjectContext
internal let transactionQueue: GCDQueue;
internal func entityNameForEntityClass(entityClass: NSManagedObject.Type) -> String? {
return self.entityNameMapping[NSStringFromClass(entityClass)]
}
// MARK: Private
private typealias EntityClassNameType = String
private typealias EntityNameType = String
private let coordinator: NSPersistentStoreCoordinator
private let rootSavingContext: NSManagedObjectContext
private let entityNameMapping: [EntityClassNameType: EntityNameType]
}

View File

@@ -105,13 +105,14 @@ public final class DataTransaction {
HardcoreData.assert(!self.isCommitted, "Attempted to commit a DataTransaction more than once.")
self.isCommitted = true
let result = self.context.saveSynchronously()
self.result = result
GCDQueue.Main.async {
let semaphore = GCDSemaphore(0)
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
self.result = result
completion(result: result)
semaphore.signal()
}
semaphore.wait()
}
/**

View File

@@ -30,15 +30,17 @@ import GCDKit
/**
Okay, okay. This one's shorter.
*/
typealias HCD = HardcoreData
public typealias HCD = HardcoreData
// MARK: HardcoreData
// MARK: - HardcoreData
/**
The HardcoreData struct is the main entry point for all other APIs.
HardcoreData is the main entry point for all other APIs.
*/
public struct HardcoreData {
public enum HardcoreData {
// MARK: Public
/**
The default DataStack instance to be used. If defaultStack is not set before the first time accessed, a default-configured DataStack will be created.
@@ -110,6 +112,8 @@ public struct HardcoreData {
}
// MARK: Private
private static let defaultStackBarrierQueue = GCDQueue.createConcurrent("com.hardcoreData.defaultStackBarrierQueue")
private static var defaultStackInstance: DataStack?

View File

@@ -31,21 +31,14 @@ import CoreData
extension NSManagedObject {
// MARK: - Entity Utilities
public class var entityName: String {
// TODO: map from model file
return NSStringFromClass(self).componentsSeparatedByString(".").last!
}
// MARK: - Internal
internal class func createInContext(context: NSManagedObjectContext) -> Self {
return self(entity: NSEntityDescription.entityForName(self.entityName, inManagedObjectContext: context)!,
insertIntoManagedObjectContext: context)
return self(
entity: context.entityDescriptionForEntityClass(self)!,
insertIntoManagedObjectContext: context
)
}
internal func inContext(context: NSManagedObjectContext) -> Self? {

View File

@@ -28,11 +28,11 @@ import CoreData
import GCDKit
// MARK: - NSManagedObjectContext+HardcoreData
// MARK: - NSManagedObjectContext
public extension NSManagedObjectContext {
// MARK: - Public
// MARK: NSObject
// MARK: Transactions
@@ -40,10 +40,10 @@ public extension NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
context.parentContext = self
context.setupForHardcoreDataWithContextName("com.hardcoredata.temporarycontext")
context.shouldCascadeSavesToParent = true
context.parentStack = self.parentStack
context.parentTransaction = self.parentTransaction
context.setupForHardcoreDataWithContextName("com.hardcoredata.temporarycontext")
context.shouldCascadeSavesToParent = true
return context
}
@@ -55,10 +55,19 @@ public extension NSManagedObjectContext {
get {
if let parentContext = self.parentContext {
return parentContext.parentStack
}
return self.getAssociatedObjectForKey(&PropertyKeys.parentStack)
}
set {
if let parentContext = self.parentContext {
return
}
self.setAssociatedWeakObject(
newValue,
forKey: &PropertyKeys.parentStack)
@@ -79,10 +88,23 @@ public extension NSManagedObjectContext {
}
}
internal func entityDescriptionForEntityClass(entity: NSManagedObject.Type) -> NSEntityDescription? {
if let entityName = self.parentStack?.entityNameForEntityClass(entity) {
return NSEntityDescription.entityForName(
entityName,
inManagedObjectContext: self
)
}
return nil
}
internal func temporaryContextInTransaction(transaction: DataTransaction?) -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
context.parentContext = self
context.parentStack = self.parentStack
context.setupForHardcoreDataWithContextName("com.hardcoredata.temporarycontext")
context.shouldCascadeSavesToParent = true
@@ -160,7 +182,14 @@ public extension NSManagedObjectContext {
if let parentContext = self.parentContext {
parentContext.saveAsynchronouslyWithCompletion(completion)
let result = parentContext.saveSynchronously()
if let completion = completion {
GCDQueue.Main.async {
completion(result: result)
}
}
return
}
}

View File

@@ -41,9 +41,7 @@ extension NSManagedObjectContext {
public func fetchOne<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> T? {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName(
entity.entityName,
inManagedObjectContext: self)
fetchRequest.entity = self.entityDescriptionForEntityClass(entity)
fetchRequest.fetchLimit = 1
fetchRequest.resultType = .ManagedObjectResultType
@@ -75,9 +73,7 @@ extension NSManagedObjectContext {
public func fetchAll<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> [T]? {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName(
entity.entityName,
inManagedObjectContext: self)
fetchRequest.entity = self.entityDescriptionForEntityClass(entity)
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectResultType
@@ -109,9 +105,7 @@ extension NSManagedObjectContext {
public func queryCount<T: NSManagedObject>(entity: T.Type, _ queryClauses: [FetchClause]) -> Int {
let fetchRequest = NSFetchRequest()
fetchRequest.entity = NSEntityDescription.entityForName(
entity.entityName,
inManagedObjectContext: self)
fetchRequest.entity = self.entityDescriptionForEntityClass(entity)
for clause in queryClauses {

View File

@@ -43,7 +43,7 @@ class HardcoreDataTests: XCTestCase {
let stack = DataStack()
HardcoreData.defaultStack = stack
XCTAssertEqual(HardcoreData.defaultStack, stack, "HardcoreData.defaultStack == stack")
XCTAssert(HardcoreData.defaultStack === stack, "HardcoreData.defaultStack === stack")
switch stack.addSQLiteStore("Config1Store.sqlite", configuration: "Config1", resetStoreOnMigrationFailure: true){
@@ -99,7 +99,7 @@ class HardcoreDataTests: XCTestCase {
}
let queryExpectation = self.expectationWithDescription("Query creation")
HardcoreData.performTransaction{ (transaction) -> Void in
HardcoreData.performTransaction { (transaction) -> Void in
let obj1 = transaction.fetchOne(TestEntity1)
XCTAssertNotNil(obj1, "obj1 != nil")
@@ -130,6 +130,6 @@ class HardcoreDataTests: XCTestCase {
}
}
self.waitForExpectationsWithTimeout(10, handler: nil)
self.waitForExpectationsWithTimeout(100, handler: nil)
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7517.1" systemVersion="14C109" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
<entity name="TestEntity1" representedClassName="HardcoreDataTests.TestEntity1" syncable="YES">
<entity name="TestEntity1AAA" representedClassName="HardcoreDataTests.TestEntity1" syncable="YES">
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="testEntityID" attributeType="Integer 64" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
@@ -13,13 +13,13 @@
<attribute name="testString" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<configuration name="Config1">
<memberEntity name="TestEntity1"/>
<memberEntity name="TestEntity1AAA"/>
</configuration>
<configuration name="Config2">
<memberEntity name="TestEntity2"/>
</configuration>
<elements>
<element name="TestEntity1" positionX="-63" positionY="-18" width="128" height="105"/>
<element name="TestEntity1AAA" positionX="-63" positionY="-18" width="128" height="105"/>
<element name="TestEntity2" positionX="-63" positionY="9" width="128" height="103"/>
</elements>
</model>