Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save seanwoodward/ff0970996f157884120f4ef1f9c624a6 to your computer and use it in GitHub Desktop.
Save seanwoodward/ff0970996f157884120f4ef1f9c624a6 to your computer and use it in GitHub Desktop.
NSPersistentContainer backport to iOS 7 with Swift 3
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