From a39fd72ceeebc3998eea91d60556d66c5791c77a Mon Sep 17 00:00:00 2001 From: Rodrigo Copetti Date: Sun, 27 Aug 2017 11:08:56 +0100 Subject: [PATCH] Rebased to 7.0.3 --- .travis.yml | 4 +- EncryptedDATAStack.podspec | 8 +- EncryptedDATAStack/Classes/DATAStack.swift | 489 ----------------- .../Classes/EncryptedDATAStack.h | 4 +- .../Classes/EncryptedDATAStack.swift | 510 ++++++++++++++++-- Example/.DS_Store | Bin 6148 -> 0 bytes .../project.pbxproj | 26 +- .../EncryptedDATAStack-Example.xcscheme | 2 + Example/EncryptedDATAStack/.DS_Store | Bin 6148 -> 0 bytes Example/EncryptedDATAStack/AppDelegate.swift | 9 +- .../DemoSwift.xcdatamodeld/.xccurrentversion | 0 .../DemoSwift.xcdatamodel/contents | 0 .../EncryptedDATAStack/ViewController.swift | 28 +- Example/Podfile | 5 +- Example/Podfile.lock | 24 +- Example/Tests/.DS_Store | Bin 6148 -> 0 bytes Example/Tests/Tests.swift | 101 ++-- README.md | 132 ++++- 18 files changed, 739 insertions(+), 603 deletions(-) delete mode 100755 EncryptedDATAStack/Classes/DATAStack.swift mode change 100644 => 100755 EncryptedDATAStack/Classes/EncryptedDATAStack.swift delete mode 100644 Example/.DS_Store delete mode 100644 Example/EncryptedDATAStack/.DS_Store mode change 100644 => 100755 Example/EncryptedDATAStack/DemoSwift.xcdatamodeld/.xccurrentversion mode change 100644 => 100755 Example/EncryptedDATAStack/DemoSwift.xcdatamodeld/DemoSwift.xcdatamodel/contents mode change 100644 => 100755 Example/EncryptedDATAStack/ViewController.swift delete mode 100644 Example/Tests/.DS_Store diff --git a/.travis.yml b/.travis.yml index 280da01..5dc7f1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,11 @@ podfile: Example/Podfile # - pod install --project-directory=Example #script: -#- set -o pipefail && xcodebuild test -workspace Example/EncryptedDATAStack.xcworkspace -scheme EncryptedDATAStack-Example -destination 'platform=iOS Simulator,name=iPhone 6' ONLY_ACTIVE_ARCH=NO | xcpretty +#- set -o pipefail && xcodebuild test -workspace Example/EncryptedDATAStack.xcworkspace -scheme EncryptedDATAStack-Example -destination 'platform=iOS Simulator,name=iPhone 7' ONLY_ACTIVE_ARCH=NO | xcpretty #- pod lib lint script: -- xcodebuild -workspace Example/EncryptedDATAStack.xcworkspace -scheme EncryptedDATAStack-Example -sdk iphonesimulator build test -destination 'platform=iOS Simulator,name=iPhone 6' | xcpretty -c && exit ${PIPESTATUS[0]} +- xcodebuild -workspace Example/EncryptedDATAStack.xcworkspace -scheme EncryptedDATAStack-Example -sdk iphonesimulator build test -destination 'platform=iOS Simulator,name=iPhone 7' | xcpretty -c && exit ${PIPESTATUS[0]} - pod lib lint --allow-warnings notifications: diff --git a/EncryptedDATAStack.podspec b/EncryptedDATAStack.podspec index 709b27c..ea5bf15 100644 --- a/EncryptedDATAStack.podspec +++ b/EncryptedDATAStack.podspec @@ -8,22 +8,22 @@ Pod::Spec.new do |s| s.name = 'EncryptedDATAStack' - s.version = '2.0.0' + s.version = '7.0.3' s.summary = 'Set up your encrypted database with only 1 line of code!' s.description = <<-DESC -Build your encrypted database with only 1 line of code. An extension of DATAStack with support of Encryption. +Build your encrypted database with only 1 line of code. Fork of DATAStack with support for Encryption. DESC s.homepage = 'https://github.com/flipacholas/EncryptedDATAStack' s.license = { :type => 'MIT', :file => 'LICENSE' } - s.author = { 'Rodrigo Copetti' => 'flipacholas@gmail.com' } + s.author = { 'Rodrigo Copetti' => 'rodrigo.copetti@outlook.com' } s.source = { :git => 'https://github.com/flipacholas/EncryptedDATAStack.git', :tag => s.version.to_s } s.social_media_url = 'https://twitter.com/flipacholas' s.ios.deployment_target = '8.0' s.source_files = 'EncryptedDATAStack/Classes/**/*' - + # s.resource_bundles = { # 'EncryptedDATAStack' => ['EncryptedDATAStack/Assets/*.png'] # } diff --git a/EncryptedDATAStack/Classes/DATAStack.swift b/EncryptedDATAStack/Classes/DATAStack.swift deleted file mode 100755 index fc23eb4..0000000 --- a/EncryptedDATAStack/Classes/DATAStack.swift +++ /dev/null @@ -1,489 +0,0 @@ -import Foundation -import EncryptedCoreData - -@objc public enum DATAStackStoreType: Int { - case inMemory, sqLite -} - -@objc public class DATAStack: NSObject { - private var storeType = DATAStackStoreType.sqLite - - internal var storeName: String? - - internal var modelName = "" - - internal var modelBundle = Bundle.main - - private var model: NSManagedObjectModel - - internal var containerURL = URL.directoryURL() - - internal var _mainContext: NSManagedObjectContext? - - /** - The context for the main queue. Please do not use this to mutate data, use `performInNewBackgroundContext` - instead. - */ - public var mainContext: NSManagedObjectContext { - get { - if _mainContext == nil { - let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) - context.undoManager = nil - context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy - context.persistentStoreCoordinator = self.persistentStoreCoordinator - - NotificationCenter.default.addObserver(self, selector: #selector(DATAStack.mainContextDidSave(_:)), name: NSNotification.Name.NSManagedObjectContextDidSave, object: context) - - _mainContext = context - } - - return _mainContext! - } - } - - /** - The context for the main queue. Please do not use this to mutate data, use `performBackgroundTask` - instead. - */ - public var viewContext: NSManagedObjectContext { - return self.mainContext - } - - private var _writerContext: NSManagedObjectContext? - - private var writerContext: NSManagedObjectContext { - get { - if _writerContext == nil { - let context = NSManagedObjectContext(concurrencyType: DATAStack.backgroundConcurrencyType()) - context.undoManager = nil - context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy - context.persistentStoreCoordinator = self.persistentStoreCoordinator - - _writerContext = context - } - - return _writerContext! - } - } - - internal var _persistentStoreCoordinator: NSPersistentStoreCoordinator? - - internal var persistentStoreCoordinator: NSPersistentStoreCoordinator { - get { - if _persistentStoreCoordinator == nil { - let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.model) - try! persistentStoreCoordinator.addPersistentStore(storeType: self.storeType, bundle: self.modelBundle, modelName: self.modelName, storeName: self.storeName, containerURL: self.containerURL) - _persistentStoreCoordinator = persistentStoreCoordinator - } - - return _persistentStoreCoordinator! - } - } - - private lazy var disposablePersistentStoreCoordinator: NSPersistentStoreCoordinator = { - let model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) - try! persistentStoreCoordinator.addPersistentStore(storeType: .inMemory, bundle: self.modelBundle, modelName: self.modelName, storeName: self.storeName, containerURL: self.containerURL) - - return persistentStoreCoordinator - }() - - /** - Initializes a DATAStack using the bundle name as the model name, so if your target is called ModernApp, - it will look for a ModernApp.xcdatamodeld. - */ - public override init() { - let bundle = Bundle.main - if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { - self.modelName = bundleName - } - self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - - super.init() - } - - /** - Initializes a DATAStack using the provided model name. - - parameter modelName: The name of your Core Data model (xcdatamodeld). - */ - public init(modelName: String) { - self.modelName = modelName - self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - - super.init() - } - - /** - Initializes a DATAStack using the provided model name, bundle and storeType. - - parameter modelName: The name of your Core Data model (xcdatamodeld). - - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory - based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. - */ - public init(modelName: String, storeType: DATAStackStoreType) { - self.modelName = modelName - self.storeType = storeType - self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - - super.init() - } - - /** - Initializes a DATAStack using the provided model name, bundle and storeType. - - parameter modelName: The name of your Core Data model (xcdatamodeld). - - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in - the main bundle but when using unit tests sometimes your Core Data model could be located where your tests - are located. - - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory - based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. - */ - public init(modelName: String, bundle: Bundle, storeType: DATAStackStoreType) { - self.modelName = modelName - self.modelBundle = bundle - self.storeType = storeType - self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - - super.init() - } - - /** - Initializes a DATAStack using the provided model name, bundle, storeType and store name. - - parameter modelName: The name of your Core Data model (xcdatamodeld). - - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in - the main bundle but when using unit tests sometimes your Core Data model could be located where your tests - are located. - - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory - based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. - - parameter storeName: Normally your file would be named as your model name is named, so if your model - name is AwesomeApp then the .sqlite file will be named AwesomeApp.sqlite, this attribute allows your to - change that. - */ - public init(modelName: String, bundle: Bundle, storeType: DATAStackStoreType, storeName: String) { - self.modelName = modelName - self.modelBundle = bundle - self.storeType = storeType - self.storeName = storeName - self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - - super.init() - } - - /** - Initializes a DATAStack using the provided model name, bundle, storeType and store name. - - parameter modelName: The name of your Core Data model (xcdatamodeld). - - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in - the main bundle but when using unit tests sometimes your Core Data model could be located where your tests - are located. - - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory - based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. - - parameter storeName: Normally your file would be named as your model name is named, so if your model - name is AwesomeApp then the .sqlite file will be named AwesomeApp.sqlite, this attribute allows your to - change that. - - parameter containerURL: The container URL for the sqlite file when a store type of SQLite is used. - */ - public init(modelName: String, bundle: Bundle, storeType: DATAStackStoreType, storeName: String, containerURL: URL) { - self.modelName = modelName - self.modelBundle = bundle - self.storeType = storeType - self.storeName = storeName - self.containerURL = containerURL - self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - - super.init() - } - - /** - Initializes a DATAStack using the provided model name, bundle and storeType. - - parameter model: The model that we'll use to set up your DATAStack. - - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory - based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. - */ - public init(model: NSManagedObjectModel, storeType: DATAStackStoreType) { - self.model = model - self.storeType = storeType - - let bundle = Bundle.main - if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { - self.storeName = bundleName - } - - super.init() - } - - deinit { - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSManagedObjectContextWillSave, object: nil) - NotificationCenter.default.removeObserver(self, name: NSNotification.Name.NSManagedObjectContextDidSave, object: nil) - } - - /** - Returns a new main context that is detached from saving to disk. - */ - public func newDisposableMainContext() -> NSManagedObjectContext { - let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) - context.persistentStoreCoordinator = self.disposablePersistentStoreCoordinator - context.undoManager = nil - - NotificationCenter.default.addObserver(self, selector: #selector(DATAStack.newDisposableMainContextWillSave(_:)), name: NSNotification.Name.NSManagedObjectContextWillSave, object: context) - - return context - } - - /** - Returns a background context perfect for data mutability operations. Make sure to never use it on the main thread. Use `performBlock` or `performBlockAndWait` to use it. - Saving to this context doesn't merge with the main thread. This context is specially useful to run operations that don't block the main thread. To refresh your main thread objects for - example when using a NSFetchedResultsController use `try self.fetchedResultsController.performFetch()`. - */ - public func newNonMergingBackgroundContext() -> NSManagedObjectContext { - let context = NSManagedObjectContext(concurrencyType: DATAStack.backgroundConcurrencyType()) - context.persistentStoreCoordinator = self.persistentStoreCoordinator - context.undoManager = nil - context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy - - return context - } - - /** - Returns a background context perfect for data mutability operations. Make sure to never use it on the main thread. Use `performBlock` or `performBlockAndWait` to use it. - */ - public func newBackgroundContext() -> NSManagedObjectContext { - let context = NSManagedObjectContext(concurrencyType: DATAStack.backgroundConcurrencyType()) - context.persistentStoreCoordinator = self.persistentStoreCoordinator - context.undoManager = nil - context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy - - NotificationCenter.default.addObserver(self, selector: #selector(DATAStack.backgroundContextDidSave(_:)), name: NSNotification.Name.NSManagedObjectContextDidSave, object: context) - - return context - } - - /** - Returns a background context perfect for data mutability operations. - - parameter operation: The block that contains the created background context. - */ - public func performInNewBackgroundContext(_ operation: @escaping (_ backgroundContext: NSManagedObjectContext) -> Void) { - let context = self.newBackgroundContext() - let contextBlock: @convention(block) () -> Void = { - operation(context) - } - let blockObject : AnyObject = unsafeBitCast(contextBlock, to: AnyObject.self) - context.perform(DATAStack.performSelectorForBackgroundContext(), with: blockObject) - } - - /** - Returns a background context perfect for data mutability operations. - - parameter operation: The block that contains the created background context. - */ - public func performBackgroundTask(operation: @escaping (_ backgroundContext: NSManagedObjectContext) -> Void) { - self.performInNewBackgroundContext(operation) - } - - func saveMainThread(completion: ((_ error: NSError?) -> Void)?) { - var writerContextError: NSError? - let writerContextBlock: @convention(block) (Void) -> Void = { - do { - try self.writerContext.save() - if TestCheck.isTesting { - completion?(nil) - } - } catch let parentError as NSError { - writerContextError = parentError - } - } - let writerContextBlockObject : AnyObject = unsafeBitCast(writerContextBlock, to: AnyObject.self) - - let mainContextBlock: @convention(block) (Void) -> Void = { - self.writerContext.perform(DATAStack.performSelectorForBackgroundContext(), with: writerContextBlockObject) - DispatchQueue.main.async { - completion?(writerContextError) - } - } - let mainContextBlockObject : AnyObject = unsafeBitCast(mainContextBlock, to: AnyObject.self) - self.mainContext.perform(DATAStack.performSelectorForBackgroundContext(), with: mainContextBlockObject) - } - - /** - Drops the database. Useful for ObjC compatibility, since it doesn't allow `throws` Use `drop` in Swift. - */ - public func forceDrop() { - try! drop() - } - - /** - Drops the database. - */ - public func drop() throws { - for store in self.persistentStoreCoordinator.persistentStores { - guard let storeURL = store.url else { throw NSError(info: "Persistent store url not found", previousError: nil) } - - let storePath = storeURL.path - let sqliteFile = (storePath as NSString).deletingPathExtension - let fileManager = FileManager.default - - self._writerContext = nil - self._mainContext = nil - self._persistentStoreCoordinator = nil - - let shm = sqliteFile + ".sqlite-shm" - if fileManager.fileExists(atPath: shm) { - do { - try fileManager.removeItem(at: NSURL.fileURL(withPath: shm)) - } catch let error as NSError { - throw NSError(info: "Could not delete persistent store shm", previousError: error) - } - } - - let wal = sqliteFile + ".sqlite-wal" - if fileManager.fileExists(atPath: wal) { - do { - try fileManager.removeItem(at: NSURL.fileURL(withPath: wal)) - } catch let error as NSError { - throw NSError(info: "Could not delete persistent store wal", previousError: error) - } - } - - if fileManager.fileExists(atPath: storePath) { - do { - try fileManager.removeItem(at: storeURL) - } catch let error as NSError { - throw NSError(info: "Could not delete sqlite file", previousError: error) - } - } - } - } - - // Can't be private, has to be internal in order to be used as a selector. - func mainContextDidSave(_ notification: Notification) { - self.saveMainThread { error in - if let error = error { - fatalError("Failed to save objects in main thread: \(error)") - } - } - } - - // Can't be private, has to be internal in order to be used as a selector. - func newDisposableMainContextWillSave(_ notification: Notification) { - if let context = notification.object as? NSManagedObjectContext { - context.reset() - } - } - - // Can't be private, has to be internal in order to be used as a selector. - func backgroundContextDidSave(_ notification: Notification) throws { - if Thread.isMainThread && TestCheck.isTesting == false { - throw NSError(info: "Background context saved in the main thread. Use context's `performBlock`", previousError: nil) - } else { - let contextBlock: @convention(block) () -> Void = { - self.mainContext.mergeChanges(fromContextDidSave: notification) - } - let blockObject : AnyObject = unsafeBitCast(contextBlock, to: AnyObject.self) - self.mainContext.perform(DATAStack.performSelectorForBackgroundContext(), with: blockObject) - } - } - - private static func backgroundConcurrencyType() -> NSManagedObjectContextConcurrencyType { - return TestCheck.isTesting ? .mainQueueConcurrencyType : .privateQueueConcurrencyType - } - - private static func performSelectorForBackgroundContext() -> Selector { - return TestCheck.isTesting ? NSSelectorFromString("performBlockAndWait:") : NSSelectorFromString("performBlock:") - } -} - -extension NSPersistentStoreCoordinator { - func addPersistentStore(storeType: DATAStackStoreType, bundle: Bundle, modelName: String, storeName: String?, containerURL: URL) throws { - let filePath = (storeName ?? modelName) + ".sqlite" - switch storeType { - case .inMemory: - do { - try self.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil) - } catch let error as NSError { - throw NSError(info: "There was an error creating the persistentStoreCoordinator for in memory store", previousError: error) - } - - break - case .sqLite: - let storeURL = containerURL.appendingPathComponent(filePath) - let storePath = storeURL.path - - let shouldPreloadDatabase = !FileManager.default.fileExists(atPath: storePath) - if shouldPreloadDatabase { - if let preloadedPath = bundle.path(forResource: modelName, ofType: "sqlite") { - let preloadURL = URL(fileURLWithPath: preloadedPath) - - do { - try FileManager.default.copyItem(at: preloadURL, to: storeURL) - } catch let error as NSError { - throw NSError(info: "Oops, could not copy preloaded data", previousError: error) - } - } - } - - let options = [NSMigratePersistentStoresAutomaticallyOption : true, NSInferMappingModelAutomaticallyOption : true] - do { - try self.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options) - } catch { - do { - try FileManager.default.removeItem(atPath: storePath) - do { - try self.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options) - } catch let addPersistentError as NSError { - throw NSError(info: "There was an error creating the persistentStoreCoordinator", previousError: addPersistentError) - } - } catch let removingError as NSError { - throw NSError(info: "There was an error removing the persistentStoreCoordinator", previousError: removingError) - } - } - - let shouldExcludeSQLiteFromBackup = storeType == .sqLite && TestCheck.isTesting == false - if shouldExcludeSQLiteFromBackup { - do { - try (storeURL as NSURL).setResourceValue(true, forKey: URLResourceKey.isExcludedFromBackupKey) - } catch let excludingError as NSError { - throw NSError(info: "Excluding SQLite file from backup caused an error", previousError: excludingError) - } - } - - break - } - } -} - -extension NSManagedObjectModel { - convenience init(bundle: Bundle, name: String) { - if let momdModelURL = bundle.url(forResource: name, withExtension: "momd") { - self.init(contentsOf: momdModelURL)! - } else if let momModelURL = bundle.url(forResource:name, withExtension: "mom") { - self.init(contentsOf: momModelURL)! - } else { - self.init() - } - } -} - -extension NSError { - convenience init(info: String, previousError: NSError?) { - if let previousError = previousError { - var userInfo = previousError.userInfo - if let _ = userInfo[NSLocalizedFailureReasonErrorKey] { - userInfo["Additional reason"] = info - } else { - userInfo[NSLocalizedFailureReasonErrorKey] = info - } - - self.init(domain: previousError.domain, code: previousError.code, userInfo: userInfo) - } else { - var userInfo = [String : String]() - userInfo[NSLocalizedDescriptionKey] = info - self.init(domain: "com.SyncDB.DATAStack", code: 9999, userInfo: userInfo) - } - } -} - -extension URL { - fileprivate static func directoryURL() -> URL { - #if os(tvOS) - return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).last! - #else - return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! - #endif - } -} diff --git a/EncryptedDATAStack/Classes/EncryptedDATAStack.h b/EncryptedDATAStack/Classes/EncryptedDATAStack.h index 3f2c3be..7d5f547 100644 --- a/EncryptedDATAStack/Classes/EncryptedDATAStack.h +++ b/EncryptedDATAStack/Classes/EncryptedDATAStack.h @@ -1,4 +1,4 @@ @import EncryptedCoreData; -FOUNDATION_EXPORT double DATAStackVersionNumber; -FOUNDATION_EXPORT const unsigned char DATAStackVersionString[]; +FOUNDATION_EXPORT double EncryptedDATAStackVersionNumber; +FOUNDATION_EXPORT const unsigned char EncryptedDATAStackVersionString[]; diff --git a/EncryptedDATAStack/Classes/EncryptedDATAStack.swift b/EncryptedDATAStack/Classes/EncryptedDATAStack.swift old mode 100644 new mode 100755 index 6e3aeee..156ffc8 --- a/EncryptedDATAStack/Classes/EncryptedDATAStack.swift +++ b/EncryptedDATAStack/Classes/EncryptedDATAStack.swift @@ -1,61 +1,505 @@ // // EncryptedDATAStack.swift // +// Created by Rodrigo Copetti on 21/08/2017. // -// Created by Rodrigo Copetti on 21/10/2016. -// +// Copyright © 2017 Rodrigo Copetti. All rights reserved. +// Copyright © 2015 Elvis Nuñez +// Copyright © 2016 SyncDB // +import Foundation import EncryptedCoreData -public class EncryptedDATAStack: DATAStack { - fileprivate var hashKey: String +@objc public enum EncryptedDATAStackStoreType: Int { + case inMemory, sqLite, sqLiteNoEncryption + + var type: String { + switch self { + case .inMemory: + return NSInMemoryStoreType + case .sqLite: + return EncryptedStoreType + case .sqLiteNoEncryption: + return NSSQLiteStoreType + } + } +} + +@objc public class EncryptedDATAStack: NSObject { + private var storeType = EncryptedDATAStackStoreType.sqLite + + private var storeName: String? + + private var modelName = "" + + private var modelBundle = Bundle.main + + private var model: NSManagedObjectModel + + private var containerURL = URL.directoryURL() + + private var _mainContext: NSManagedObjectContext? + private var passphraseKey:String? - override internal var persistentStoreCoordinator: NSPersistentStoreCoordinator { - get { - if _persistentStoreCoordinator == nil { - let filePath = (storeName ?? modelName) + ".sqlite" - let storeURL = containerURL.appendingPathComponent(filePath) - let model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - let options = [EncryptedStorePassphraseKey: self.hashKey, EncryptedStoreDatabaseLocation: storeURL] as [String : Any] - let persistentStoreCoordinator = EncryptedStore.make(options: options, managedObjectModel: model) - - _persistentStoreCoordinator = persistentStoreCoordinator - } + /** + The context for the main queue. Please do not use this to mutate data, use `performInNewBackgroundContext` + instead. + */ + public lazy var mainContext: NSManagedObjectContext = { + let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + context.undoManager = nil + context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy + context.persistentStoreCoordinator = self.persistentStoreCoordinator - return _persistentStoreCoordinator! - } + NotificationCenter.default.addObserver(self, selector: #selector(EncryptedDATAStack.mainContextDidSave(_:)), name: .NSManagedObjectContextDidSave, object: context) + + return context + }() + + /** + The context for the main queue. Please do not use this to mutate data, use `performBackgroundTask` + instead. + */ + public var viewContext: NSManagedObjectContext { + return self.mainContext } - public init(modelName: String, hashKey: String) { - self.hashKey = hashKey + private lazy var writerContext: NSManagedObjectContext = { + let context = NSManagedObjectContext(concurrencyType: EncryptedDATAStack.backgroundConcurrencyType()) + context.undoManager = nil + context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy + context.persistentStoreCoordinator = self.persistentStoreCoordinator + + return context + }() + + public private(set) lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { + let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.model) + try! persistentStoreCoordinator.addPersistentStore(storeType: self.storeType, bundle: self.modelBundle, modelName: self.modelName, storeName: self.storeName, containerURL: self.containerURL, passphraseKey:self.passphraseKey) - super.init(modelName: modelName) + return persistentStoreCoordinator + }() + + private lazy var disposablePersistentStoreCoordinator: NSPersistentStoreCoordinator = { + let model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) + let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) + try! persistentStoreCoordinator.addPersistentStore(storeType: .inMemory, bundle: self.modelBundle, modelName: self.modelName, storeName: self.storeName, containerURL: self.containerURL) + return persistentStoreCoordinator + }() + + /** + Initializes a EncryptedDATAStack using the bundle name as the model name, so if your target is called ModernApp, + it will look for a ModernApp.xcdatamodeld. + */ + public override init() { + let bundle = Bundle.main + if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { + self.modelName = bundleName + } + self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) + super.init() + } + + public convenience init(passphraseKey:String) { + self.init() + self.passphraseKey = passphraseKey } - - - public init(modelName: String, hashKey: String, bundle: Bundle) { - self.hashKey = hashKey + /** + Initializes a EncryptedDATAStack using the provided model name. + - parameter modelName: The name of your Core Data model (xcdatamodeld). + */ + public init(passphraseKey:String, modelName: String) { + self.modelName = modelName + self.passphraseKey = passphraseKey + self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - super.init(modelName: modelName, bundle: bundle, storeType: .sqLite) + super.init() } - - public init(modelName: String, hashKey: String, bundle: Bundle, storeName: String) { - self.hashKey = hashKey + /** + Initializes a EncryptedDATAStack using the provided model name, bundle and storeType. + - parameter modelName: The name of your Core Data model (xcdatamodeld). + - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory + based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. + */ + public init(passphraseKey:String? = nil, modelName: String, storeType: EncryptedDATAStackStoreType) { + self.modelName = modelName + self.storeType = storeType + self.passphraseKey = passphraseKey + self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) - super.init(modelName: modelName, bundle: bundle, storeType: .sqLite, storeName: storeName) + super.init() } - - public init(modelName: String, hashKey: String, bundle: Bundle, storeName: String, containerURL: URL) { + /** + Initializes a EncryptedDATAStack using the provided model name, bundle and storeType. + - parameter modelName: The name of your Core Data model (xcdatamodeld). + - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in + the main bundle but when using unit tests sometimes your Core Data model could be located where your tests + are located. + - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory + based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. + */ + public init(passphraseKey:String? = nil, modelName: String, bundle: Bundle, storeType: EncryptedDATAStackStoreType) { + self.modelName = modelName + self.modelBundle = bundle + self.storeType = storeType + self.passphraseKey = passphraseKey + self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) + + super.init() + } + + /** + Initializes a EncryptedDATAStack using the provided model name, bundle, storeType and store name. + - parameter modelName: The name of your Core Data model (xcdatamodeld). + - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in + the main bundle but when using unit tests sometimes your Core Data model could be located where your tests + are located. + - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory + based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. + - parameter storeName: Normally your file would be named as your model name is named, so if your model + name is AwesomeApp then the .sqlite file will be named AwesomeApp.sqlite, this attribute allows your to + change that. + */ + public init(passphraseKey:String? = nil, modelName: String, bundle: Bundle, storeType: EncryptedDATAStackStoreType, storeName: String) { + self.modelName = modelName + self.modelBundle = bundle + self.storeType = storeType + self.storeName = storeName + self.passphraseKey = passphraseKey + self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) + + super.init() + } + + /** + Initializes a EncryptedDATAStack using the provided model name, bundle, storeType and store name. + - parameter modelName: The name of your Core Data model (xcdatamodeld). + - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in + the main bundle but when using unit tests sometimes your Core Data model could be located where your tests + are located. + - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory + based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. + - parameter storeName: Normally your file would be named as your model name is named, so if your model + name is AwesomeApp then the .sqlite file will be named AwesomeApp.sqlite, this attribute allows your to + change that. + - parameter containerURL: The container URL for the sqlite file when a store type of SQLite is used. + */ + public init(passphraseKey:String? = nil, modelName: String, bundle: Bundle, storeType: EncryptedDATAStackStoreType, storeName: String, containerURL: URL) { + self.modelName = modelName + self.modelBundle = bundle + self.storeType = storeType + self.storeName = storeName + self.containerURL = containerURL + self.passphraseKey = passphraseKey + self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) + + super.init() + } + + /** + Initializes a EncryptedDATAStack using the provided model name, bundle and storeType. + - parameter model: The model that we'll use to set up your EncryptedDATAStack. + - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory + based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. + */ + public init(passphraseKey:String? = nil, model: NSManagedObjectModel, storeType: EncryptedDATAStackStoreType) { + self.model = model + self.storeType = storeType + self.passphraseKey = passphraseKey - self.hashKey = hashKey + let bundle = Bundle.main + if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { + self.storeName = bundleName + } + + super.init() + } + + deinit { + NotificationCenter.default.removeObserver(self, name: .NSManagedObjectContextWillSave, object: nil) + NotificationCenter.default.removeObserver(self, name: .NSManagedObjectContextDidSave, object: nil) + } + + /** + Returns a new main context that is detached from saving to disk. + */ + public func newDisposableMainContext() -> NSManagedObjectContext { + let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) + context.persistentStoreCoordinator = self.disposablePersistentStoreCoordinator + context.undoManager = nil + + NotificationCenter.default.addObserver(self, selector: #selector(EncryptedDATAStack.newDisposableMainContextWillSave(_:)), name: NSNotification.Name.NSManagedObjectContextWillSave, object: context) - super.init(modelName: modelName, bundle: bundle, storeType: .sqLite, storeName: storeName, containerURL: containerURL) + return context } + /** + Returns a background context perfect for data mutability operations. Make sure to never use it on the main thread. Use `performBlock` or `performBlockAndWait` to use it. + Saving to this context doesn't merge with the main thread. This context is specially useful to run operations that don't block the main thread. To refresh your main thread objects for + example when using a NSFetchedResultsController use `try self.fetchedResultsController.performFetch()`. + */ + public func newNonMergingBackgroundContext() -> NSManagedObjectContext { + let context = NSManagedObjectContext(concurrencyType: EncryptedDATAStack.backgroundConcurrencyType()) + context.persistentStoreCoordinator = self.persistentStoreCoordinator + context.undoManager = nil + context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy + + return context + } + + /** + Returns a background context perfect for data mutability operations. Make sure to never use it on the main thread. Use `performBlock` or `performBlockAndWait` to use it. + */ + public func newBackgroundContext() -> NSManagedObjectContext { + let context = NSManagedObjectContext(concurrencyType: EncryptedDATAStack.backgroundConcurrencyType()) + context.persistentStoreCoordinator = self.persistentStoreCoordinator + context.undoManager = nil + context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy + + NotificationCenter.default.addObserver(self, selector: #selector(EncryptedDATAStack.backgroundContextDidSave(_:)), name: .NSManagedObjectContextDidSave, object: context) + + return context + } + + /** + Returns a background context perfect for data mutability operations. + - parameter operation: The block that contains the created background context. + */ + public func performInNewBackgroundContext(_ operation: @escaping (_ backgroundContext: NSManagedObjectContext) -> Void) { + let context = self.newBackgroundContext() + let contextBlock: @convention(block) () -> Void = { + operation(context) + } + let blockObject: AnyObject = unsafeBitCast(contextBlock, to: AnyObject.self) + context.perform(EncryptedDATAStack.performSelectorForBackgroundContext(), with: blockObject) + } + + /** + Returns a background context perfect for data mutability operations. + - parameter operation: The block that contains the created background context. + */ + public func performBackgroundTask(operation: @escaping (_ backgroundContext: NSManagedObjectContext) -> Void) { + self.performInNewBackgroundContext(operation) + } + + func saveMainThread(completion: ((_ error: NSError?) -> Void)?) { + var writerContextError: NSError? + let writerContextBlock: @convention(block) () -> Void = { + do { + try self.writerContext.save() + if TestCheck.isTesting { + completion?(nil) + } + } catch let parentError as NSError { + writerContextError = parentError + } + } + let writerContextBlockObject: AnyObject = unsafeBitCast(writerContextBlock, to: AnyObject.self) + + let mainContextBlock: @convention(block) () -> Void = { + self.writerContext.perform(EncryptedDATAStack.performSelectorForBackgroundContext(), with: writerContextBlockObject) + DispatchQueue.main.async { + completion?(writerContextError) + } + } + let mainContextBlockObject: AnyObject = unsafeBitCast(mainContextBlock, to: AnyObject.self) + self.mainContext.perform(EncryptedDATAStack.performSelectorForBackgroundContext(), with: mainContextBlockObject) + } + + /** + Drops the database. + */ + public func drop(completion: ((_ error: NSError?) -> Void)? = nil) { + self.writerContext.performAndWait { + self.writerContext.reset() + + self.mainContext.performAndWait { + self.mainContext.reset() + + self.persistentStoreCoordinator.performAndWait { + for store in self.persistentStoreCoordinator.persistentStores { + guard let storeURL = store.url else { continue } + + do { + if #available(iOS 9.0, *) { + try self.persistentStoreCoordinator.destroyPersistentStore(at: storeURL, ofType: self.storeType.type, options: store.options) + } else { + try self.persistentStoreCoordinator.remove(self.persistentStoreCoordinator.persistentStores.last!) + try FileManager.default.removeItem(at: storeURL) + } + try! self.persistentStoreCoordinator.addPersistentStore(storeType: self.storeType, bundle: self.modelBundle, modelName: self.modelName, storeName: self.storeName, containerURL: self.containerURL,passphraseKey:self.passphraseKey) + + DispatchQueue.main.async { + completion?(nil) + } + } catch let error as NSError { + DispatchQueue.main.async { + completion?(NSError(info: "Failed dropping the data stack.", previousError: error)) + } + } + } + } + } + } + } + + /// Sends a request to all the persistent stores associated with the receiver. + /// + /// - Parameters: + /// - request: A fetch, save or delete request. + /// - context: The context against which request should be executed. + /// - Returns: An array containing managed objects, managed object IDs, or dictionaries as appropriate for a fetch request; an empty array if request is a save request, or nil if an error occurred. + /// - Throws: If an error occurs, upon return contains an NSError object that describes the problem. + public func execute(_ request: NSPersistentStoreRequest, with context: NSManagedObjectContext) throws -> Any { + return try self.persistentStoreCoordinator.execute(request, with: context) + } + + // Can't be private, has to be internal in order to be used as a selector. + func mainContextDidSave(_ notification: Notification) { + self.saveMainThread { error in + if let error = error { + fatalError("Failed to save objects in main thread: \(error)") + } + } + } + + // Can't be private, has to be internal in order to be used as a selector. + func newDisposableMainContextWillSave(_ notification: Notification) { + if let context = notification.object as? NSManagedObjectContext { + context.reset() + } + } + + // Can't be private, has to be internal in order to be used as a selector. + func backgroundContextDidSave(_ notification: Notification) throws { + if Thread.isMainThread && TestCheck.isTesting == false { + throw NSError(info: "Background context saved in the main thread. Use context's `performBlock`", previousError: nil) + } else { + let contextBlock: @convention(block) () -> Void = { + self.mainContext.mergeChanges(fromContextDidSave: notification) + } + let blockObject: AnyObject = unsafeBitCast(contextBlock, to: AnyObject.self) + self.mainContext.perform(EncryptedDATAStack.performSelectorForBackgroundContext(), with: blockObject) + } + } + + private static func backgroundConcurrencyType() -> NSManagedObjectContextConcurrencyType { + return TestCheck.isTesting ? .mainQueueConcurrencyType : .privateQueueConcurrencyType + } + + private static func performSelectorForBackgroundContext() -> Selector { + return TestCheck.isTesting ? NSSelectorFromString("performBlockAndWait:") : NSSelectorFromString("performBlock:") + } +} + +extension NSPersistentStoreCoordinator { + func addPersistentStore(storeType: EncryptedDATAStackStoreType, bundle: Bundle, modelName: String, storeName: String?, containerURL: URL,passphraseKey:String? = nil) throws { + let filePath = (storeName ?? modelName) + ".sqlite" + switch storeType { + case .inMemory: + do { + try self.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil) + } catch let error as NSError { + throw NSError(info: "There was an error creating the persistentStoreCoordinator for in memory store", previousError: error) + } + + break + case .sqLite, .sqLiteNoEncryption: + if storeType == .sqLite{ + assert(passphraseKey != nil, "Set a Passphrase Key while using encrypted sqlite!") + } + let storeURL = containerURL.appendingPathComponent(filePath) + let storePath = storeURL.path + + let shouldPreloadDatabase = !FileManager.default.fileExists(atPath: storePath) + if shouldPreloadDatabase { + if let preloadedPath = bundle.path(forResource: modelName, ofType: "sqlite") { + let preloadURL = URL(fileURLWithPath: preloadedPath) + + do { + try FileManager.default.copyItem(at: preloadURL, to: storeURL) + } catch let error as NSError { + throw NSError(info: "Oops, could not copy preloaded data", previousError: error) + } + } + } + + var options:[String : Any] = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true] + if storeType == .sqLite { + options[EncryptedStorePassphraseKey] = passphraseKey + } + do { + try self.addPersistentStore(ofType: storeType.type, configurationName: nil, at: storeURL, options: options) + } catch { + do { + try FileManager.default.removeItem(atPath: storePath) + do { + try self.addPersistentStore(ofType: storeType.type, configurationName: nil, at: storeURL, options: options) + } catch let addPersistentError as NSError { + throw NSError(info: "There was an error creating the persistentStoreCoordinator", previousError: addPersistentError) + } + } catch let removingError as NSError { + throw NSError(info: "There was an error removing the persistentStoreCoordinator", previousError: removingError) + } + } + + let shouldExcludeSQLiteFromBackup = (storeType == .sqLite || storeType == .sqLiteNoEncryption) && TestCheck.isTesting == false + if shouldExcludeSQLiteFromBackup { + do { + try (storeURL as NSURL).setResourceValue(true, forKey: .isExcludedFromBackupKey) + } catch let excludingError as NSError { + throw NSError(info: "Excluding SQLite file from backup caused an error", previousError: excludingError) + } + } + + break + } + } +} + +public extension NSManagedObjectModel { + public convenience init(bundle: Bundle, name: String) { + if let momdModelURL = bundle.url(forResource: name, withExtension: "momd") { + self.init(contentsOf: momdModelURL)! + } else if let momModelURL = bundle.url(forResource: name, withExtension: "mom") { + self.init(contentsOf: momModelURL)! + } else { + self.init() + } + } +} + +extension NSError { + convenience init(info: String, previousError: NSError?) { + if let previousError = previousError { + var userInfo = previousError.userInfo + if let _ = userInfo[NSLocalizedFailureReasonErrorKey] { + userInfo["Additional reason"] = info + } else { + userInfo[NSLocalizedFailureReasonErrorKey] = info + } + + self.init(domain: previousError.domain, code: previousError.code, userInfo: userInfo) + } else { + var userInfo = [String: String]() + userInfo[NSLocalizedDescriptionKey] = info + self.init(domain: "com.SyncDB.EncryptedDATAStack", code: 9999, userInfo: userInfo) + } + } +} + +extension URL { + fileprivate static func directoryURL() -> URL { + #if os(tvOS) + return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).last! + #else + return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! + #endif + } } diff --git a/Example/.DS_Store b/Example/.DS_Store deleted file mode 100644 index 195f37af5526b47c62af83ab9ea8befb1472ceb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~O-{ow5QU$iNEK`93Lw&;rn85^d}=`@p1K(uVE$8u2qW#slV9B1M8| zqqkNl0VVL)2*}z!!~io4k)ynRdpJiAG1v1I+s3zXKAU8-5&Ltk~e5HN{`UJIvo>tJTD0MrJnt+6b33GoTMhF%M&Y@r$FN_DQrUonhx zXMN!Ng-_H1f?G6(<)m#ZEfhvJr_t=v2fAIPJzv`qT> I1b&sk8=g>@t^fc4 diff --git a/Example/EncryptedDATAStack.xcodeproj/project.pbxproj b/Example/EncryptedDATAStack.xcodeproj/project.pbxproj index 228af09..f4055f9 100644 --- a/Example/EncryptedDATAStack.xcodeproj/project.pbxproj +++ b/Example/EncryptedDATAStack.xcodeproj/project.pbxproj @@ -228,6 +228,7 @@ TargetAttributes = { 607FACCF1AFB9204008FA782 = { CreatedOnToolsVersion = 6.3.1; + DevelopmentTeam = MT49Z4H3J6; LastSwiftMigration = 0800; }; 607FACE41AFB9204008FA782 = { @@ -282,9 +283,18 @@ files = ( ); inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-EncryptedDATAStack_Example/Pods-EncryptedDATAStack_Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/DATASource/DATASource.framework", + "${BUILT_PRODUCTS_DIR}/EncryptedCoreData/EncryptedCoreData.framework", + "${BUILT_PRODUCTS_DIR}/EncryptedDATAStack/EncryptedDATAStack.framework", + "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DATASource.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EncryptedCoreData.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EncryptedDATAStack.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -327,13 +337,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-EncryptedDATAStack_Example-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; DDB2CA4D8D16A1178B9C88EB /* [CP] Check Pods Manifest.lock */ = { @@ -342,13 +355,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-EncryptedDATAStack_Tests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; E27E22FDEAD1DF50B59E4C46 /* [CP] Embed Pods Frameworks */ = { @@ -505,10 +521,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = MT49Z4H3J6; INFOPLIST_FILE = EncryptedDATAStack/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.EncryptedDATAStack-Example2"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; @@ -520,10 +537,11 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEVELOPMENT_TEAM = MT49Z4H3J6; INFOPLIST_FILE = EncryptedDATAStack/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; MODULE_NAME = ExampleApp; - PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.EncryptedDATAStack-Example2"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; diff --git a/Example/EncryptedDATAStack.xcodeproj/xcshareddata/xcschemes/EncryptedDATAStack-Example.xcscheme b/Example/EncryptedDATAStack.xcodeproj/xcshareddata/xcschemes/EncryptedDATAStack-Example.xcscheme index 6a0335f..e1d01fb 100644 --- a/Example/EncryptedDATAStack.xcodeproj/xcshareddata/xcschemes/EncryptedDATAStack-Example.xcscheme +++ b/Example/EncryptedDATAStack.xcodeproj/xcshareddata/xcschemes/EncryptedDATAStack-Example.xcscheme @@ -40,6 +40,7 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + language = "" shouldUseLaunchSchemeArgsEnv = "YES"> 6&Hhu)PhyhPQ`XUquXy~< zKW+}Y?SA)^cb_LKD+Q#06p#W^Knna?0q?!E`65wK3P=Gd@TGu%9~#}U7mkVX>0pQv zfH-41jO&;sh|LqkUN|N)L$jn3lWH|$SkjqqRo4s0#H7P&_^^7i)r4a4bl%^h9M%&R zrGOMTRp2tWOYi?{`VaH}DM>piAO-%F0ybN1Rs&zDdh6unyw^7R9o=g_=x$sGg(2E8 kG1@UV-j1)MDC?T9dEN`h#Go@DbfSI+To;)X_-h3|0hgl{7XSbN diff --git a/Example/EncryptedDATAStack/AppDelegate.swift b/Example/EncryptedDATAStack/AppDelegate.swift index a6c4c65..13fec0d 100755 --- a/Example/EncryptedDATAStack/AppDelegate.swift +++ b/Example/EncryptedDATAStack/AppDelegate.swift @@ -1,5 +1,4 @@ import UIKit -import CoreData import EncryptedDATAStack @UIApplicationMain @@ -9,13 +8,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let window = UIWindow(frame: UIScreen.main.bounds) return window - }() + }() var dataStack: EncryptedDATAStack = { - let dataStack = EncryptedDATAStack(modelName: "DemoSwift", hashKey: "grampaPass") + let dataStack = EncryptedDATAStack(passphraseKey:"randomPassj", modelName: "DemoSwift") return dataStack - }() + }() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { if let window = self.window { @@ -23,7 +22,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window.rootViewController = UINavigationController(rootViewController: viewController) window.makeKeyAndVisible() } - + return true } } diff --git a/Example/EncryptedDATAStack/DemoSwift.xcdatamodeld/.xccurrentversion b/Example/EncryptedDATAStack/DemoSwift.xcdatamodeld/.xccurrentversion old mode 100644 new mode 100755 diff --git a/Example/EncryptedDATAStack/DemoSwift.xcdatamodeld/DemoSwift.xcdatamodel/contents b/Example/EncryptedDATAStack/DemoSwift.xcdatamodeld/DemoSwift.xcdatamodel/contents old mode 100644 new mode 100755 diff --git a/Example/EncryptedDATAStack/ViewController.swift b/Example/EncryptedDATAStack/ViewController.swift old mode 100644 new mode 100755 index a599608..be33080 --- a/Example/EncryptedDATAStack/ViewController.swift +++ b/Example/EncryptedDATAStack/ViewController.swift @@ -1,47 +1,47 @@ import UIKit -import DATASource import EncryptedDATAStack +import DATASource class ViewController: UITableViewController { var dataStack: EncryptedDATAStack - + lazy var dataSource: DATASource = { let request: NSFetchRequest = NSFetchRequest(entityName: "User") request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] - + let dataSource = DATASource(tableView: self.tableView, cellIdentifier: "Cell", fetchRequest: request, mainContext: self.dataStack.mainContext, configuration: { cell, item, indexPath in if let name = item.value(forKey: "name") as? String, let createdDate = item.value(forKey: "createdDate") as? NSDate { - cell.textLabel?.text = name + " - " + createdDate.description + cell.textLabel?.text = name + " - " + createdDate.description } }) - + return dataSource }() - + init(dataStack: EncryptedDATAStack) { self.dataStack = dataStack - + super.init(style: .plain) } - + required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func viewDidLoad() { super.viewDidLoad() - + self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") self.tableView.dataSource = self.dataSource - + let backgroundButton = UIBarButtonItem(title: "Background", style: .done, target: self, action: #selector(ViewController.createBackground)) self.navigationItem.rightBarButtonItem = backgroundButton - + let mainButton = UIBarButtonItem(title: "Main", style: .done, target: self, action: #selector(ViewController.createMain)) self.navigationItem.leftBarButtonItem = mainButton } - + func createBackground() { self.dataStack.performInNewBackgroundContext { backgroundContext in let entity = NSEntityDescription.entity(forEntityName: "User", in: backgroundContext)! @@ -51,7 +51,7 @@ class ViewController: UITableViewController { try! backgroundContext.save() } } - + func createMain() { let entity = NSEntityDescription.entity(forEntityName: "User", in: self.dataStack.mainContext)! let object = NSManagedObject(entity: entity, insertInto: self.dataStack.mainContext) diff --git a/Example/Podfile b/Example/Podfile index 45915e2..0ff85ed 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -2,10 +2,11 @@ use_frameworks! target 'EncryptedDATAStack_Example' do pod 'EncryptedDATAStack', :path => '../' - pod 'DATASource’, '~> 6.1.1' + pod 'DATASource’, '~> 6.1.5' + pod 'EncryptedCoreData', :git => 'https://github.com/project-imas/encrypted-core-data.git' target 'EncryptedDATAStack_Tests' do inherit! :search_paths end -end \ No newline at end of file +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock index bf3c953..4d5234f 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,8 +1,8 @@ PODS: - - DATASource (6.1.1) + - DATASource (6.1.5) - EncryptedCoreData (3.1): - SQLCipher (~> 3.4.0) - - EncryptedDATAStack (2.0.0): + - EncryptedDATAStack (7.0.3): - EncryptedCoreData (~> 3.1) - SQLCipher (3.4.0): - SQLCipher/standard (= 3.4.0) @@ -11,19 +11,27 @@ PODS: - SQLCipher/common DEPENDENCIES: - - DATASource (~> 6.1.1) + - DATASource (~> 6.1.5) + - EncryptedCoreData (from `https://github.com/project-imas/encrypted-core-data.git`) - EncryptedDATAStack (from `../`) EXTERNAL SOURCES: + EncryptedCoreData: + :git: https://github.com/project-imas/encrypted-core-data.git EncryptedDATAStack: - :path: "../" + :path: ../ + +CHECKOUT OPTIONS: + EncryptedCoreData: + :commit: b97ffaf2f19dad4d1558bc9b0668cc2e09d17347 + :git: https://github.com/project-imas/encrypted-core-data.git SPEC CHECKSUMS: - DATASource: fa895931841523f62c1378cbc53ef2de87e5cf30 + DATASource: 52b86cfff6c7ac3db45192c01f7839c7473576c3 EncryptedCoreData: f6762fb05f88a52f36c5648659abc91b57f244b4 - EncryptedDATAStack: f1cf65d0122252f3ecfab981b15e28441cb27d40 + EncryptedDATAStack: 84897982fa1826a44a0306af5890cd786c43b4b1 SQLCipher: 4c768761421736a247ed6cf412d9045615d53dff -PODFILE CHECKSUM: 4300aa5ad2cff52737da731a243b640058555319 +PODFILE CHECKSUM: eb0fd5215d30babaff403c4078e4c632c9c0447c -COCOAPODS: 1.1.1 +COCOAPODS: 1.3.1 diff --git a/Example/Tests/.DS_Store b/Example/Tests/.DS_Store deleted file mode 100644 index 97bad80ae9061e2221cb409df14d8773ea487714..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~!Ab)$5QhJ>JrwoOTRrZ@t5Ba{DJx#|)EBUH#R^@wlr4IaeGs3xaveN)?<8?j)1_1gjqP-(lEhf*UG^`UmCsd~~Dm>v154gv? z(c2tGzzF;`0&;fGa2?L<=J~B~quv#!sMsp;NKc8JCwzf(JY&YTJe#8U_Al;7lUZ3T z{W80+`;A_dS(dM>8SsAb@}_g_@!g9u|HbIb`6e0p-eS1H^9n06Tw}S5Eb?u(ozM6P zE&L+ip@mUogjNm{?yrJtUvO_l?y=5?a)^r1${{jBW3LPm6`_TL?1@7fbGBIj*sIk> zzz7(D9|H1yNLWPEptV>1bg EncryptedDATAStack { - let dataStack = EncryptedDATAStack(modelName: "ModelGroup", hashKey: "grampaPass", bundle: Bundle(for: Tests.self),storeName: "Test.sqlite") - let _ = try? dataStack.drop() + func createDataStack(_ storeType: EncryptedDATAStackStoreType = .inMemory) -> EncryptedDATAStack { + let dataStack = EncryptedDATAStack(passphraseKey:"randomPass", modelName: "ModelGroup", bundle: Bundle(for: Tests.self), storeType: storeType) return dataStack } @@ -15,41 +14,48 @@ extension XCTestCase { user.setValue(NSNumber(value: 1), forKey: "remoteID") user.setValue("Joshua Ivanof", forKey: "name") try! context.save() - + return user } - + func fetch(in context: NSManagedObjectContext) -> [NSManagedObject] { let request = NSFetchRequest(entityName: "User") let objects = try! context.fetch(request) - + return objects } } class InitializerTests: XCTestCase { func testInitializeUsingXCDataModel() { - let dataStack = EncryptedDATAStack(modelName: "SimpleModel",hashKey: "grampaPass", bundle: Bundle(for: Tests.self),storeName: "Test2.sqlite") - let _ = try? dataStack.drop() + let dataStack = EncryptedDATAStack(passphraseKey:"randomPass", modelName: "SimpleModel", bundle: Bundle(for: Tests.self), storeType: .inMemory) + self.insertUser(in: dataStack.mainContext) let objects = self.fetch(in: dataStack.mainContext) XCTAssertEqual(objects.count, 1) } - + // xcdatamodeld is a container for .xcdatamodel files. It's used for versioning and migration. // When moving from v1 of the model to v2, you add a new xcdatamodel to it that has v2 along with the mapping model. func testInitializeUsingXCDataModeld() { let dataStack = self.createDataStack() - + + self.insertUser(in: dataStack.mainContext) + let objects = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objects.count, 1) + } + + func testInitializingUsingNSManagedObjectModel() { + let model = NSManagedObjectModel(bundle: Bundle(for: Tests.self), name: "ModelGroup") + let dataStack = EncryptedDATAStack(passphraseKey:"randomPass", model: model, storeType: .inMemory) + self.insertUser(in: dataStack.mainContext) let objects = self.fetch(in: dataStack.mainContext) XCTAssertEqual(objects.count, 1) } - } class Tests: XCTestCase { - func testSynchronousBackgroundContext() { let dataStack = self.createDataStack() @@ -61,6 +67,20 @@ class Tests: XCTestCase { XCTAssertTrue(synchronous) } + func testBackgroundContextSave() { + let dataStack = self.createDataStack() + + dataStack.performInNewBackgroundContext { backgroundContext in + self.insertUser(in: backgroundContext) + + let objects = self.fetch(in: backgroundContext) + XCTAssertEqual(objects.count, 1) + } + + let objects = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objects.count, 1) + } + func testNewBackgroundContextSave() { var synchronous = false let dataStack = self.createDataStack() @@ -71,67 +91,84 @@ class Tests: XCTestCase { let objects = self.fetch(in: backgroundContext) XCTAssertEqual(objects.count, 1) } - + let objects = self.fetch(in: dataStack.mainContext) XCTAssertEqual(objects.count, 1) - + XCTAssertTrue(synchronous) } func testRequestWithDictionaryResultType() { let dataStack = self.createDataStack() self.insertUser(in: dataStack.mainContext) - + let request = NSFetchRequest(entityName: "User") let objects = try! dataStack.mainContext.fetch(request) XCTAssertEqual(objects.count, 1) - + let expression = NSExpressionDescription() expression.name = "objectID" expression.expression = NSExpression.expressionForEvaluatedObject() expression.expressionResultType = .objectIDAttributeType - + let dictionaryRequest = NSFetchRequest(entityName: "User") dictionaryRequest.resultType = .dictionaryResultType dictionaryRequest.propertiesToFetch = [expression, "remoteID"] - + let dictionaryObjects = try! dataStack.mainContext.fetch(dictionaryRequest) XCTAssertEqual(dictionaryObjects.count, 1) } - + func testDisposableContextSave() { let dataStack = self.createDataStack() - + let disposableContext = dataStack.newDisposableMainContext() self.insertUser(in: disposableContext) let objects = self.fetch(in: disposableContext) XCTAssertEqual(objects.count, 0) } - + func testDrop() { - let dataStack = self.createDataStack() + let dataStackArray = [self.createDataStack(.sqLite), self.createDataStack(.sqLiteNoEncryption)] + for dataStack in dataStackArray{ + dataStack.performInNewBackgroundContext { backgroundContext in self.insertUser(in: backgroundContext) } - + let objectsA = self.fetch(in: dataStack.mainContext) XCTAssertEqual(objectsA.count, 1) - - let _ = try? dataStack.drop() - + + dataStack.drop() + let objects = self.fetch(in: dataStack.mainContext) XCTAssertEqual(objects.count, 0) + + dataStack.performInNewBackgroundContext { backgroundContext in + self.insertUser(in: backgroundContext) + } + + let objectsB = self.fetch(in: dataStack.mainContext) + XCTAssertEqual(objectsB.count, 1) + + dataStack.drop() + } } + + func testAutomaticMigration() { - let firstDataStack = DATAStack(modelName: "SimpleModel", bundle: Bundle(for: Tests.self), storeType: .sqLite, storeName: "Shared") + let storeTests:[EncryptedDATAStackStoreType] = [.sqLite, .sqLiteNoEncryption] + + for storeType in storeTests{ + let firstDataStack = EncryptedDATAStack(passphraseKey:"randomPass", modelName: "SimpleModel", bundle: Bundle(for: Tests.self), storeType: storeType, storeName: "Shared") self.insertUser(in: firstDataStack.mainContext) let objects = self.fetch(in: firstDataStack.mainContext) XCTAssertEqual(objects.count, 1) - + // LightweightMigrationModel is a copy of DataModel with the main difference that adds the updatedDate attribute. - let secondDataStack = DATAStack(modelName: "LightweightMigrationModel", bundle: Bundle(for: Tests.self), storeType: .sqLite, storeName: "Shared") + let secondDataStack = EncryptedDATAStack(passphraseKey:"randomPass", modelName: "LightweightMigrationModel", bundle: Bundle(for: Tests.self), storeType: storeType, storeName: "Shared") let fetchRequest = NSFetchRequest(entityName: "User") fetchRequest.predicate = NSPredicate(format: "remoteID = %@", NSNumber(value: 1)) let user = try! secondDataStack.mainContext.fetch(fetchRequest).first @@ -139,8 +176,10 @@ class Tests: XCTestCase { XCTAssertEqual(user?.value(forKey: "name") as? String, "Joshua Ivanof") user?.setValue(Date().addingTimeInterval(16000), forKey: "updatedDate") try! secondDataStack.mainContext.save() + + firstDataStack.drop() + secondDataStack.drop() + } - try! firstDataStack.drop() - try! secondDataStack.drop() } } diff --git a/README.md b/README.md index aaddd1a..233b368 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,10 @@ Set up an encrypted Database with only 1 line of code! -**EncryptedDATAStack** is an extension of [DATAStack](https://github.com/SyncDB/DATAStack) made to work exclusively with [Encrypted Core Data](https://github.com/project-imas/encrypted-core-data) (Core Data + SQLCipher). It acts as a superset, which retains compatibility with the original DATAStack. +**EncryptedDATAStack** is an fork of [DATAStack](https://github.com/SyncDB/DATAStack) with added support of [Encrypted Core Data](https://github.com/project-imas/encrypted-core-data) (Core Data + SQLCipher) and extra legacy support for iOS 8. +All in all, this allows you to set up a database (encrypted and/or unencrypted) with only one line of code! + +Version tags are set to match the version of DATAStack used. ## Table of Contents @@ -33,35 +36,146 @@ You can easily initialize a new instance of **EncryptedDATAStack** with just you **Swift** ``` swift -let encryptedStack = EncryptedDATAStack(modelName:"MyAppModel", key:"yourHashKey") +let encryptedDataStack = EncryptedDATAStack(passphraseKey:"YOUR_PASSWORD", modelName:"MyAppModel") ``` **Objective-C** ``` objc -DATAStack *dataStack = [[EncryptedDATAStack alloc] initWithModelName:@"MyAppModel" key:@"yourHashKey"]; +EncryptedDATAStack *encryptedDataStack = [[EncryptedDATAStack alloc] initWithPassphraseKey:@"YOUR_PASSWORD" modelName:@"MyAppModel"]; +``` + +There are plenty of other ways to intialize an EncryptedDATAStack: + +- Using a custom store type. + +``` swift +//For Memory Storage +let encryptedDataStack = EncryptedDATAStack(modelName:"MyAppModel", storeType: .InMemory) +``` + +``` swift +//For Regular SQLite +let encryptedDataStack = EncryptedDATAStack(modelName:"MyAppModel", storeType: .sqLiteNoEncryption) ``` -- Using a custom bundle. +- Using another bundle and a store type, let's say your test bundle and .InMemory store type, perfect for running unit tests. ``` swift -let encryptedStack = EncryptedDATAStack(modelName:"MyAppModel", key:"yourHashKey", bundle: NSBundle(forClass: Tests.self)) +let encryptedDataStack = EncryptedDATAStack(modelName: "Model", bundle: NSBundle(forClass: Tests.self), storeType: .InMemory) ``` - Using a different name for your .sqlite file than your model name, like `CustomStoreName.sqlite`. ``` swift -let encryptedStack = DATAStackmodelName:(modelName:"MyAppModel", key:"yourHashKey", bundle: NSBundle.mainBundle(), storeName: "CustomStoreName") +let encryptedDataStack = EncryptedDATAStack(passphraseKey:"YOUR_PASSWORD", modelName: "Model", bundle: NSBundle.mainBundle(), storeType: .sqLite, storeName: "CustomStoreName") ``` - Providing a diferent container url, by default we'll use the documents folder, most apps do this, but if you want to share your sqlite file between your main app and your app extension you'll want this. ``` swift -let encryptedStack = DATAStack(modelName:"MyAppModel", key:"yourHashKey", bundle: NSBundle.mainBundle(), storeName: "CustomStoreName", containerURL: sharedURL) +let encryptedDataStack = EncryptedDATAStack(passphraseKey:"YOUR_PASSWORD", modelName: "Model", bundle: NSBundle.mainBundle(), storeType: .sqLite, storeName: "CustomStoreName", containerURL: sharedURL) ``` -## Installation +## Main Thread NSManagedObjectContext + +Getting access to the NSManagedObjectContext attached to the main thread is as simple as using the `mainContext` property. + +```swift +self.encryptedDataStack.mainContext +``` + +or + +```swift +self.encryptedDataStack.viewContext +``` + +## Background Thread NSManagedObjectContext + +You can easily create a new background NSManagedObjectContext for data processing. This block is completely asynchronous and will be run on a background thread. + +To be compatible with NSPersistentContainer you can also use `performBackgroundTask` instead of `performInNewBackgroundContext`. + +**Swift** +```swift +func createUser() { +self.encryptedDataStack.performInNewBackgroundContext { backgroundContext in +let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: backgroundContext)! +let object = NSManagedObject(entity: entity, insertIntoManagedObjectContext: backgroundContext) +object.setValue("Background", forKey: "name") +object.setValue(NSDate(), forKey: "createdDate") +try! backgroundContext.save() +} +} +``` + +**Objective-C** +```objc +- (void)createUser { +[self.encryptedDataStack performInNewBackgroundContext:^(NSManagedObjectContext * _Nonnull backgroundContext) { +NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:backgroundContext]; +NSManagedObject *object = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:backgroundContext]; +[object setValue:@"Background" forKey:@"name"]; +[object setValue:[NSDate date] forKey:@"createdDate"]; +[backgroundContext save:nil]; +}]; +} +``` + +When using Xcode's Objective-C autocompletion the `backgroundContext` parameter name doesn't get included. Make sure to add it. + +## Clean up -Attention: A copy of DATAStack.swift is already included in this pod. +Deleting the `.sqlite` file and resetting the state of your **EncryptedDATAStack** is as simple as just calling `drop`. + +**Swift** +```swift +self.encryptedDataStack.drop() +``` + +**Objective-C** +```objc +[self.encryptedDataStack forceDrop]; +``` + +## Testing + +**EncryptedDATAStack** is optimized for unit testing and it runs synchronously in testing enviroments. Hopefully you'll have to use less XCTestExpectations now. + +You can create a stack that uses in memory store like this if your Core Data model is located in your app bundle: + +**Swift** +```swift +let encryptedDataStack = EncryptedDATAStack(modelName: "MyAppModel", bundle: NSBundle.mainBundle(), storeType: .InMemory) +``` + +**Objective-C** +```objc +EncryptedDATAStack *encryptedDataStack = [[EncryptedDATAStack alloc] initWithModelName:@"MyAppModel" +bundle:[NSBundle mainBundle] +storeType:EncryptedDATAStackStoreTypeInMemory]; +``` + +If your Core Data model is located in your test bundle: + +**Swift** +```swift +let encryptedDataStack = EncryptedDATAStack(modelName: "MyAppModel", bundle: NSBundle(forClass: Tests.self), storeType: .InMemory) +``` + +**Objective-C** +```objc +EncryptedDATAStack *encryptedDataStack = [[EncryptedDATAStack alloc] initWithModelName:@"MyAppModel" +bundle:[NSBundle bundleForClass:[self class]] +storeType:EncryptedDATAStackStoreTypeInMemory]; +``` + +## Migrations + +If `EncryptedDATAStack` has troubles creating your persistent coordinator because a migration wasn't properly handled or the passphrase was incorrect it will destroy your data and create a new sqlite file. The normal Core Data behaviour for this is making your app crash on start. This is not fun. + + +## Installation **EncryptedDATAStack** is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile: