Created
September 2, 2016 06:48
-
-
Save zwaldowski/83b8e0ca1f8c0ad8bece82cd1026c021 to your computer and use it in GitHub Desktop.
NSPersistentContainer backport to iOS 7 with Swift 3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import CoreData | |
@objc(_PersistentStoreDescription) private protocol AnyPersistentStoreDescription: NSObjectProtocol, NSCopying { | |
var type: String { get set } | |
var configuration: String? { get set } | |
@objc(URL) var url: URL? { get set } | |
var options: [String : NSObject] { get } | |
func setOption(_ option: NSObject?, forKey key: String) | |
var isReadOnly: Bool { | |
get | |
@objc(setReadOnly:) set | |
} | |
var timeout: TimeInterval { get set } | |
var sqlitePragmas: [String : NSObject] { get } | |
func setValue(_ value: NSObject?, forPragmaNamed name: String) | |
var shouldAddStoreAsynchronously: Bool { get set } | |
var shouldMigrateStoreAutomatically: Bool { get set } | |
var shouldInferMappingModelAutomatically: Bool { get set } | |
} | |
private let NSAddStoreAsynchronouslyOption = "NSAddStoreAsynchronouslyOption" | |
private final class LegacyPersistentStoreDescription: NSObject, NSCopying { | |
static var defaultURL: URL { | |
return URL(fileURLWithPath: "/dev/null") | |
} | |
@objc var type = NSSQLiteStoreType | |
@objc var configuration: String? | |
@objc(URL) var url: URL? | |
@objc private(set) var options: [String : NSObject] = [ | |
NSInferMappingModelAutomaticallyOption: true as NSNumber, | |
NSMigratePersistentStoresAutomaticallyOption: true as NSNumber | |
] | |
init(url: URL) { | |
self.url = url | |
} | |
override convenience init() { | |
self.init(url: LegacyPersistentStoreDescription.defaultURL) | |
} | |
override var description: String { | |
return "\(super.description) (type: \(type), url: \(url))" | |
} | |
func copy(with zone: NSZone?) -> Any { | |
let result = type(of: self).init(url: url ?? LegacyPersistentStoreDescription.defaultURL) | |
result.configuration = configuration | |
result.type = type | |
result.options = options | |
return result | |
} | |
override var hash: Int { | |
return url.map({ $0.standardized })?.hashValue ?? 0 | |
} | |
override func isEqual(_ object: Any?) -> Bool { | |
guard let object = object as? LegacyPersistentStoreDescription else { return false } | |
if object === self { return true } | |
let selfUrl = url.map { $0.standardized } | |
let objectUrl = object.url.map { $0.standardized } | |
return selfUrl == objectUrl && type == object.type && configuration == object.configuration && options == object.options | |
} | |
@objc func setOption(_ option: NSObject?, forKey key: String) { | |
options[key] = option | |
} | |
// MARK: - Store options | |
var isReadOnly: Bool { | |
get { return (options[NSReadOnlyPersistentStoreOption] as? Bool) ?? false } | |
@objc(setReadOnly:) set { options[NSReadOnlyPersistentStoreOption] = newValue as NSNumber } | |
} | |
@objc var timeout: TimeInterval { | |
get { return (options[NSPersistentStoreTimeoutOption] as? TimeInterval) ?? 240 } | |
set { options[NSPersistentStoreTimeoutOption] = newValue as NSNumber } | |
} | |
@objc private(set) var sqlitePragmas: [String : NSObject] { | |
get { return (options[NSSQLitePragmasOption] as? [String : NSObject]) ?? [:] } | |
set { options[NSSQLitePragmasOption] = newValue as NSObject } | |
} | |
@objc func setValue(_ value: NSObject?, forPragmaNamed name: String) { | |
sqlitePragmas[name] = value | |
} | |
// MARK: - addPersistentStore-time behaviors | |
@objc var shouldAddStoreAsynchronously: Bool { | |
get { return (options[NSAddStoreAsynchronouslyOption] as? Bool) ?? false } | |
set { options[NSAddStoreAsynchronouslyOption] = newValue as NSNumber } | |
} | |
@objc var shouldMigrateStoreAutomatically: Bool { | |
get { return (options[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false } | |
set { options[NSMigratePersistentStoresAutomaticallyOption] = newValue as NSNumber } | |
} | |
@objc var shouldInferMappingModelAutomatically: Bool { | |
get { return (options[NSInferMappingModelAutomaticallyOption] as? Bool) ?? false } | |
set { options[NSInferMappingModelAutomaticallyOption] = newValue as NSNumber } | |
} | |
} | |
extension LegacyPersistentStoreDescription: AnyPersistentStoreDescription {} | |
@available(iOS 10.0, *) | |
extension NSPersistentStoreDescription: AnyPersistentStoreDescription {} | |
private final class MutableHandle<MutableType : NSObjectProtocol & NSCopying> { | |
fileprivate var pointer: MutableType | |
init(reference: MutableType) { | |
pointer = reference.copy(with: nil) as! MutableType | |
} | |
init(adoptingReference reference: MutableType) { | |
pointer = reference | |
} | |
/// Apply a closure to the reference type. | |
@inline(__always) | |
func map<ReturnType>(_ body: (MutableType) throws -> ReturnType) rethrows -> ReturnType { | |
return try body(pointer) | |
} | |
func copiedReference() -> MutableType { | |
return pointer.copy(with: nil) as! MutableType | |
} | |
func uncopiedReference() -> MutableType { | |
return pointer | |
} | |
} | |
struct PersistentStoreDescription { | |
fileprivate var handle: MutableHandle<AnyPersistentStoreDescription> | |
@inline(__always) | |
fileprivate func read<ReturnType>(_ body: (AnyPersistentStoreDescription) throws -> ReturnType) rethrows -> ReturnType { | |
return try handle.map(body) | |
} | |
@inline(__always) | |
fileprivate mutating func write<ReturnType>(_ body: (AnyPersistentStoreDescription) throws -> ReturnType) rethrows -> ReturnType { | |
// Only create a new box if we are not uniquely referenced | |
if !isKnownUniquelyReferenced(&handle) { | |
handle = MutableHandle(reference: handle.pointer) | |
} | |
return try body(handle.pointer) | |
} | |
/// The type of store this description represents. | |
var type: String { | |
get { return read { $0.type } } | |
set { write { $0.type = newValue } } | |
} | |
/// The name of the configuration used by this store. | |
var configuration: String? { | |
get { return read { $0.configuration } } | |
set { write { $0.configuration = newValue } } | |
} | |
/// The URL that the store will use for its location. | |
var url: URL? { | |
get { return read { $0.url } } | |
set { write { $0.url = newValue } } | |
} | |
/// Dictionary representation of the options set on the associated | |
/// persistent store. | |
var options: [String : NSObject] { | |
return read { $0.options } | |
} | |
/// Set an option on the store. | |
mutating func setOption(_ option: NSObject?, forKey key: String) { | |
write { $0.setOption(option, forKey: key) } | |
} | |
/// Flag that indicates if this store will be read only. | |
var isReadOnly: Bool { | |
get { return read { $0.isReadOnly } } | |
set { write { $0.isReadOnly = newValue } } | |
} | |
/// Property that specifies the connection timeout for the associated store. | |
var timeout: TimeInterval { | |
get { return read { $0.timeout } } | |
set { write { $0.timeout = newValue } } | |
} | |
/// The SQLite pragmas set for the associated persistent store. | |
var sqlitePragmas: [String : NSObject] { | |
return read { $0.sqlitePragmas } | |
} | |
/// This method allows you to set pragmas for the SQLite store. | |
mutating func setValue(_ value: NSObject?, forPragmaNamed name: String) { | |
write { $0.setValue(value, forPragmaNamed: name) } | |
} | |
/// Flag that determines if the the store will be added asynchronously. | |
var shouldAddStoreAsynchronously: Bool { | |
get { return read { $0.shouldAddStoreAsynchronously } } | |
set { write { $0.shouldAddStoreAsynchronously = newValue } } | |
} | |
/// A flag indicating if the associated persistent store should be migrated | |
/// automatically. | |
var shouldMigrateStoreAutomatically: Bool { | |
get { return read { $0.shouldMigrateStoreAutomatically } } | |
set { write { $0.shouldMigrateStoreAutomatically = newValue } } | |
} | |
/// A flag indicating if a mapping model should be created automatically. | |
var shouldInferMappingModelAutomatically: Bool { | |
get { return read { $0.shouldInferMappingModelAutomatically } } | |
set { write { $0.shouldInferMappingModelAutomatically = newValue } } | |
} | |
/// Initializes the receiver with a URL for the store. | |
init(url: URL) { | |
let result: AnyPersistentStoreDescription | |
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { | |
result = NSPersistentStoreDescription(url: url) | |
} else { | |
result = LegacyPersistentStoreDescription(url: url) | |
} | |
handle = MutableHandle(adoptingReference: result) | |
} | |
/// Initializes the reciever for an in-memory store. | |
init() { | |
let result: AnyPersistentStoreDescription | |
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { | |
result = NSPersistentStoreDescription() | |
} else { | |
result = LegacyPersistentStoreDescription() | |
} | |
handle = MutableHandle(adoptingReference: result) | |
} | |
} | |
extension PersistentStoreDescription: CustomStringConvertible, CustomDebugStringConvertible { | |
var description: String { | |
return read { String(describing: $0) } | |
} | |
var debugDescription: String { | |
return read { String(reflecting: $0) } | |
} | |
} | |
extension PersistentStoreDescription: Hashable { | |
var hashValue: Int { | |
return read { $0.hash } | |
} | |
} | |
func == (lhs: PersistentStoreDescription, rhs: PersistentStoreDescription) -> Bool { | |
return lhs.handle.uncopiedReference().isEqual(rhs.handle.uncopiedReference()) | |
} | |
extension PersistentStoreDescription: _ObjectiveCBridgeable { | |
private init(reference: AnyPersistentStoreDescription) { | |
handle = MutableHandle(reference: reference) | |
} | |
static func _getObjectiveCType() -> Any.Type { | |
return AnyPersistentStoreDescription.self | |
} | |
@_semantics("convertToObjectiveC") | |
func _bridgeToObjectiveC() -> AnyObject { | |
return handle.copiedReference() | |
} | |
public static func _forceBridgeFromObjectiveC(_ source: AnyObject, result: inout PersistentStoreDescription?) { | |
result = (source as? AnyPersistentStoreDescription).map(PersistentStoreDescription.init) | |
} | |
public static func _conditionallyBridgeFromObjectiveC(_ source: AnyObject, result: inout PersistentStoreDescription?) -> Bool { | |
guard let source = source as? AnyPersistentStoreDescription else { | |
result = nil | |
return false | |
} | |
result = PersistentStoreDescription(reference: source) | |
return true | |
} | |
public static func _unconditionallyBridgeFromObjectiveC(_ source: AnyObject?) -> PersistentStoreDescription { | |
return PersistentStoreDescription(reference: source as! AnyPersistentStoreDescription) | |
} | |
} | |
// MARK: - PersistentContainer | |
@objc(_PersistentContainer) private protocol AnyPersistentContainer: NSObjectProtocol { | |
var name: String { get } | |
var viewContext: NSManagedObjectContext { get } | |
var managedObjectModel: NSManagedObjectModel { get } | |
var persistentStoreCoordinator: NSPersistentStoreCoordinator { get } | |
var _persistentStoreDescriptions: [Any] { get set } | |
func _loadPersistentStores(completionHandler block: @escaping(AnyPersistentStoreDescription, Error?) -> Void) | |
func newBackgroundContext() -> NSManagedObjectContext | |
func performBackgroundTask(_ block: @escaping(NSManagedObjectContext) -> Void) | |
} | |
private final class LegacyPersistentContainer: NSObject { | |
@objc let name: String | |
@objc let persistentStoreCoordinator: NSPersistentStoreCoordinator | |
@objc let viewContext: NSManagedObjectContext | |
var persistentStoreDescriptions = [LegacyPersistentStoreDescription]() | |
static let defaultDirectoryURL: URL = { | |
let fm = FileManager.default | |
#if os(tvOS) | |
let directoryType = FileManager.SearchPathDirectory.cachesDirectory | |
#else | |
let directoryType = FileManager.SearchPathDirectory.applicationSupportDirectory | |
#endif | |
var appDirectory: URL | |
do { | |
appDirectory = try fm.url(for: directoryType, in: .userDomainMask, appropriateFor: nil, create: true) | |
} catch { | |
preconditionFailure("Found no possible URLs for directory") | |
} | |
#if os(OSX) | |
guard let suffix = Bundle.main.infoDictionary?[kCFBundleExecutableKey as String] as? String ?? Bundle.main.executableURL?.lastPathComponent else { | |
preconditionFailure("Could not get appliction name information from bundle \(Bundle.main)") | |
} | |
appDirectory.appendPathComponent(suffix) | |
#endif | |
do { | |
try fm.createDirectory(at: appDirectory, withIntermediateDirectories: true, attributes: nil) | |
} catch CocoaError.fileWriteFileExistsError { | |
preconditionFailure("File \(appDirectory) already exists and is not a directory!") | |
} catch { | |
preconditionFailure("Failed to create directory \(appDirectory): \(error)") | |
} | |
return appDirectory | |
}() | |
convenience init(name: String) { | |
let modelURL = Bundle.main.url(forResource: name, withExtension: "momd") ?? Bundle.main.url(forResource: name, withExtension: "mom") | |
guard let model = modelURL.flatMap(NSManagedObjectModel.init) else { | |
preconditionFailure("Failed to load model named \(name)") | |
} | |
self.init(name: name, managedObjectModel: model) | |
} | |
init(name: String, managedObjectModel: NSManagedObjectModel) { | |
self.name = name | |
self.persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) | |
self.viewContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) | |
self.viewContext.persistentStoreCoordinator = self.persistentStoreCoordinator | |
var storeURL = LegacyPersistentContainer.defaultDirectoryURL | |
storeURL.appendPathComponent(name) | |
storeURL.appendPathExtension("sqlite") | |
persistentStoreDescriptions.append(LegacyPersistentStoreDescription(url: storeURL)) | |
} | |
@objc var managedObjectModel: NSManagedObjectModel { | |
return persistentStoreCoordinator.managedObjectModel | |
} | |
@objc func loadPersistentStores(completionHandler block: @escaping(AnyPersistentStoreDescription, Error?) -> Void) { | |
let storedDescriptions = persistentStoreDescriptions.map { $0.copy(with: nil) as! AnyPersistentStoreDescription } | |
for desc in storedDescriptions where !desc.shouldAddStoreAsynchronously { autoreleasepool { | |
do { | |
try persistentStoreCoordinator.addPersistentStore(ofType: desc.type, configurationName: desc.configuration, at: desc.url, options: desc.options) | |
block(desc, nil) | |
} catch { | |
block(desc, error as NSError) | |
} | |
} } | |
let queue = DispatchQueue.global() | |
for desc in storedDescriptions where desc.shouldAddStoreAsynchronously { | |
queue.async { autoreleasepool { | |
do { | |
try self.persistentStoreCoordinator.addPersistentStore(ofType: desc.type, configurationName: desc.configuration, at: desc.url, options: desc.options) | |
DispatchQueue.main.async { | |
block(desc, nil) | |
} | |
} catch { | |
DispatchQueue.main.async { | |
block(desc, error) | |
} | |
} | |
} } | |
} | |
} | |
@objc func newBackgroundContext() -> NSManagedObjectContext { | |
let result = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) | |
if let context = viewContext.parent { | |
result.parent = context | |
} else { | |
result.persistentStoreCoordinator = persistentStoreCoordinator | |
} | |
return result | |
} | |
@objc func performBackgroundTask(_ block: @escaping(NSManagedObjectContext) -> Void) { | |
let context = newBackgroundContext() | |
context.perform { | |
block(context) | |
} | |
} | |
} | |
extension LegacyPersistentContainer: AnyPersistentContainer { | |
@objc fileprivate var _persistentStoreDescriptions: [Any] { | |
get { | |
return persistentStoreDescriptions | |
} | |
set { | |
persistentStoreDescriptions = newValue as! [LegacyPersistentStoreDescription] | |
} | |
} | |
@objc fileprivate func _loadPersistentStores(completionHandler block: @escaping(AnyPersistentStoreDescription, Error?) -> Void) { | |
loadPersistentStores(completionHandler: block) | |
} | |
} | |
@available(iOS 10.0, *) | |
extension NSPersistentContainer: AnyPersistentContainer { | |
@objc fileprivate var _persistentStoreDescriptions: [Any] { | |
get { | |
return persistentStoreDescriptions | |
} | |
set { | |
persistentStoreDescriptions = newValue as! [NSPersistentStoreDescription] | |
} | |
} | |
@objc fileprivate func _loadPersistentStores(completionHandler block: @escaping(AnyPersistentStoreDescription, Error?) -> Void) { | |
loadPersistentStores(completionHandler: block) | |
} | |
} | |
/// The persistent container is designed to encapsulate a Core Data stack in | |
/// your application and greatly simplify its creation and management. | |
struct PersistentContainer { | |
fileprivate var handle: AnyPersistentContainer | |
/// The default directory for the persistent stores on the current platform. | |
static var defaultDirectoryURL: URL { | |
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { | |
return NSPersistentContainer.defaultDirectoryURL() | |
} else { | |
return LegacyPersistentContainer.defaultDirectoryURL | |
} | |
} | |
/// Name of this persistent container. | |
var name: String { | |
return handle.name | |
} | |
/// The managed object context associated with the main queue. | |
var viewContext: NSManagedObjectContext { | |
return handle.viewContext | |
} | |
/// The model associated with this persistent container. | |
var managedObjectModel: NSManagedObjectModel { | |
return handle.managedObjectModel | |
} | |
/// The persistent store coordinator associated with this persistent | |
/// container. | |
var persistentStoreCoordinator: NSPersistentStoreCoordinator { | |
return handle.persistentStoreCoordinator | |
} | |
/// The descriptions used to create the persistent stores referenced by | |
/// this persistent container. | |
var persistentStoreDescriptions: [PersistentStoreDescription] { | |
get { | |
return handle._persistentStoreDescriptions as! [PersistentStoreDescription] | |
} | |
nonmutating set { | |
handle._persistentStoreDescriptions = newValue | |
} | |
} | |
/// Instructs the persistent container to load the persistent stores. | |
/// | |
/// Once the completion handler has fired the stack is fully initialized and | |
/// is ready for use. The completion handler will be called once for each | |
/// persistent store that is created. | |
func loadPersistentStores(completionHandler block: @escaping(PersistentStoreDescription, Error?) -> Void) { | |
return handle._loadPersistentStores { | |
block($0 as! PersistentStoreDescription, $1) | |
} | |
} | |
/// Returns a newly-created private queue managed object context. | |
func newBackgroundContext() -> NSManagedObjectContext { | |
return handle.newBackgroundContext() | |
} | |
func performBackgroundTask(_ block: @escaping(NSManagedObjectContext) -> Void) { | |
return handle.performBackgroundTask(block) | |
} | |
/// Creates a persistent container with the given `name`. | |
init(name: String) { | |
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { | |
handle = NSPersistentContainer(name: name) | |
} else { | |
handle = LegacyPersistentContainer(name: name) | |
} | |
} | |
/// Creates a persistent container with the given `name` and `model`. | |
init(name: String, managedObjectModel model: NSManagedObjectModel) { | |
if #available(OSX 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) { | |
handle = NSPersistentContainer(name: name, managedObjectModel: model) | |
} else { | |
handle = LegacyPersistentContainer(name: name, managedObjectModel: model) | |
} | |
} | |
} | |
extension PersistentContainer: CustomStringConvertible, CustomDebugStringConvertible { | |
var description: String { | |
return String(describing: handle) | |
} | |
var debugDescription: String { | |
return String(reflecting: handle) | |
} | |
} | |
extension PersistentContainer: Hashable { | |
var hashValue: Int { | |
return handle.hash | |
} | |
} | |
func == (lhs: PersistentContainer, rhs: PersistentContainer) -> Bool { | |
return lhs.handle.isEqual(rhs.handle) | |
} | |
extension PersistentContainer: _ObjectiveCBridgeable { | |
private init(reference: AnyPersistentContainer) { | |
handle = reference | |
} | |
static func _getObjectiveCType() -> Any.Type { | |
return AnyPersistentContainer.self | |
} | |
@_semantics("convertToObjectiveC") | |
func _bridgeToObjectiveC() -> AnyObject { | |
return handle | |
} | |
static func _forceBridgeFromObjectiveC(_ source: AnyObject, result: inout PersistentContainer?) { | |
result = (source as? AnyPersistentContainer).map(PersistentContainer.init) | |
} | |
static func _conditionallyBridgeFromObjectiveC(_ source: AnyObject, result: inout PersistentContainer?) -> Bool { | |
guard let source = source as? AnyPersistentContainer else { | |
result = nil | |
return false | |
} | |
result = PersistentContainer(reference: source) | |
return true | |
} | |
static func _unconditionallyBridgeFromObjectiveC(_ source: AnyObject?) -> PersistentContainer { | |
return PersistentContainer(reference: source as! AnyPersistentContainer) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment