├── .gitignore ├── .swift-version ├── .travis.yml ├── EncryptedDATAStack.podspec ├── EncryptedDATAStack ├── .DS_Store ├── Assets │ └── .gitkeep └── Classes │ ├── .DS_Store │ ├── .gitkeep │ ├── EncryptedDATAStack.h │ ├── EncryptedDATAStack.swift │ └── TestCheck.swift ├── Example ├── EncryptedDATAStack.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── EncryptedDATAStack-Example.xcscheme ├── EncryptedDATAStack.xcworkspace │ └── contents.xcworkspacedata ├── EncryptedDATAStack │ ├── AppDelegate.swift │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── DemoSwift.xcdatamodeld │ │ ├── .xccurrentversion │ │ └── DemoSwift.xcdatamodel │ │ │ └── contents │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ └── ViewController.swift ├── Podfile ├── Podfile.lock └── Tests │ ├── Info.plist │ ├── LightweightMigrationModel.xcdatamodel │ └── contents │ ├── ModelGroup.xcdatamodeld │ └── ModelGroup.xcdatamodel │ │ └── contents │ ├── SimpleModel.xcdatamodel │ └── contents │ └── Tests.swift ├── LICENSE ├── README.md └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | Icon 6 | ._* 7 | .Spotlight-V100 8 | .Trashes 9 | 10 | # Xcode 11 | # 12 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 13 | 14 | ## Build generated 15 | build/ 16 | DerivedData/ 17 | 18 | ## Various settings 19 | *.pbxuser 20 | !default.pbxuser 21 | *.mode1v3 22 | !default.mode1v3 23 | *.mode2v3 24 | !default.mode2v3 25 | *.perspectivev3 26 | !default.perspectivev3 27 | xcuserdata/ 28 | 29 | ## Other 30 | *.moved-aside 31 | *.xcuserstate 32 | 33 | ## Obj-C/Swift specific 34 | *.hmap 35 | *.ipa 36 | *.dSYM.zip 37 | *.dSYM 38 | 39 | ## Playgrounds 40 | timeline.xctimeline 41 | playground.xcworkspace 42 | 43 | # Swift Package Manager 44 | # 45 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 46 | # Packages/ 47 | .build/ 48 | 49 | # CocoaPods 50 | # 51 | # We recommend against adding the Pods directory to your .gitignore. However 52 | # you should judge for yourself, the pros and cons are mentioned at: 53 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 54 | # 55 | Pods/ 56 | 57 | # Carthage 58 | # 59 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 60 | # Carthage/Checkouts 61 | 62 | Carthage/Build 63 | 64 | # fastlane 65 | # 66 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 67 | # screenshots whenever they are needed. 68 | # For more information about the recommended setup visit: 69 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 70 | 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | 76 | Example/Tests/.DS_Store 77 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 3.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode8 2 | language: objective-c 3 | 4 | # cache: cocoapods 5 | podfile: Example/Podfile 6 | before_install: 7 | - gem install cocoapods # Since Travis is not always on latest version 8 | - pod repo update 9 | - pod install --project-directory=Example 10 | 11 | #script: 12 | #- set -o pipefail && xcodebuild test -workspace Example/EncryptedDATAStack.xcworkspace -scheme EncryptedDATAStack-Example -destination 'platform=iOS Simulator,name=iPhone 7' ONLY_ACTIVE_ARCH=NO | xcpretty 13 | #- pod lib lint 14 | 15 | script: 16 | - xcodebuild -workspace Example/EncryptedDATAStack.xcworkspace -scheme EncryptedDATAStack-Example -sdk iphonesimulator build test -destination 'platform=iOS Simulator,name=iPhone 7' | xcpretty -c && exit ${PIPESTATUS[0]} 17 | - pod lib lint --allow-warnings 18 | 19 | notifications: 20 | email: false 21 | -------------------------------------------------------------------------------- /EncryptedDATAStack.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod lib lint EncryptedDATAStack.podspec' to ensure this is a 3 | # valid spec before submitting. 4 | # 5 | # Any lines starting with a # are optional, but their use is encouraged 6 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | s.name = 'EncryptedDATAStack' 11 | s.version = '7.0.3' 12 | s.summary = 'Set up your encrypted database with only 1 line of code!' 13 | 14 | s.description = <<-DESC 15 | Build your encrypted database with only 1 line of code. Fork of DATAStack with support for Encryption. 16 | DESC 17 | 18 | s.homepage = 'https://github.com/flipacholas/EncryptedDATAStack' 19 | s.license = { :type => 'MIT', :file => 'LICENSE' } 20 | s.author = { 'Rodrigo Copetti' => 'rodrigo.copetti@outlook.com' } 21 | s.source = { :git => 'https://github.com/flipacholas/EncryptedDATAStack.git', :tag => s.version.to_s } 22 | s.social_media_url = 'https://twitter.com/flipacholas' 23 | 24 | s.ios.deployment_target = '8.0' 25 | s.source_files = 'EncryptedDATAStack/Classes/**/*' 26 | 27 | # s.resource_bundles = { 28 | # 'EncryptedDATAStack' => ['EncryptedDATAStack/Assets/*.png'] 29 | # } 30 | 31 | # s.public_header_files = 'Pod/Classes/**/*.h' 32 | s.frameworks = 'Foundation', 'CoreData' 33 | s.dependency 'EncryptedCoreData', '~> 3.1' 34 | end 35 | -------------------------------------------------------------------------------- /EncryptedDATAStack/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flipacholas/EncryptedDATAStack/55a6c2937209836226ba28449bf0adb6ccd28645/EncryptedDATAStack/.DS_Store -------------------------------------------------------------------------------- /EncryptedDATAStack/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flipacholas/EncryptedDATAStack/55a6c2937209836226ba28449bf0adb6ccd28645/EncryptedDATAStack/Assets/.gitkeep -------------------------------------------------------------------------------- /EncryptedDATAStack/Classes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flipacholas/EncryptedDATAStack/55a6c2937209836226ba28449bf0adb6ccd28645/EncryptedDATAStack/Classes/.DS_Store -------------------------------------------------------------------------------- /EncryptedDATAStack/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flipacholas/EncryptedDATAStack/55a6c2937209836226ba28449bf0adb6ccd28645/EncryptedDATAStack/Classes/.gitkeep -------------------------------------------------------------------------------- /EncryptedDATAStack/Classes/EncryptedDATAStack.h: -------------------------------------------------------------------------------- 1 | @import EncryptedCoreData; 2 | 3 | FOUNDATION_EXPORT double EncryptedDATAStackVersionNumber; 4 | FOUNDATION_EXPORT const unsigned char EncryptedDATAStackVersionString[]; 5 | -------------------------------------------------------------------------------- /EncryptedDATAStack/Classes/EncryptedDATAStack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EncryptedDATAStack.swift 3 | // 4 | // Created by Rodrigo Copetti on 21/08/2017. 5 | // 6 | // Copyright © 2017 Rodrigo Copetti. All rights reserved. 7 | // Copyright © 2015 Elvis Nuñez 8 | // Copyright © 2016 SyncDB 9 | // 10 | 11 | import Foundation 12 | import EncryptedCoreData 13 | 14 | @objc public enum EncryptedDATAStackStoreType: Int { 15 | case inMemory, sqLite, sqLiteNoEncryption 16 | 17 | var type: String { 18 | switch self { 19 | case .inMemory: 20 | return NSInMemoryStoreType 21 | case .sqLite: 22 | return EncryptedStoreType 23 | case .sqLiteNoEncryption: 24 | return NSSQLiteStoreType 25 | } 26 | } 27 | } 28 | 29 | @objc public class EncryptedDATAStack: NSObject { 30 | private var storeType = EncryptedDATAStackStoreType.sqLite 31 | 32 | private var storeName: String? 33 | 34 | private var modelName = "" 35 | 36 | private var modelBundle = Bundle.main 37 | 38 | private var model: NSManagedObjectModel 39 | 40 | private var containerURL = URL.directoryURL() 41 | 42 | private var _mainContext: NSManagedObjectContext? 43 | 44 | private var passphraseKey:String? 45 | 46 | /** 47 | The context for the main queue. Please do not use this to mutate data, use `performInNewBackgroundContext` 48 | instead. 49 | */ 50 | public lazy var mainContext: NSManagedObjectContext = { 51 | let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 52 | context.undoManager = nil 53 | context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy 54 | context.persistentStoreCoordinator = self.persistentStoreCoordinator 55 | 56 | NotificationCenter.default.addObserver(self, selector: #selector(EncryptedDATAStack.mainContextDidSave(_:)), name: .NSManagedObjectContextDidSave, object: context) 57 | 58 | return context 59 | }() 60 | 61 | /** 62 | The context for the main queue. Please do not use this to mutate data, use `performBackgroundTask` 63 | instead. 64 | */ 65 | public var viewContext: NSManagedObjectContext { 66 | return self.mainContext 67 | } 68 | 69 | private lazy var writerContext: NSManagedObjectContext = { 70 | let context = NSManagedObjectContext(concurrencyType: EncryptedDATAStack.backgroundConcurrencyType()) 71 | context.undoManager = nil 72 | context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy 73 | context.persistentStoreCoordinator = self.persistentStoreCoordinator 74 | 75 | return context 76 | }() 77 | 78 | public private(set) lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = { 79 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: self.model) 80 | try! persistentStoreCoordinator.addPersistentStore(storeType: self.storeType, bundle: self.modelBundle, modelName: self.modelName, storeName: self.storeName, containerURL: self.containerURL, passphraseKey:self.passphraseKey) 81 | 82 | return persistentStoreCoordinator 83 | }() 84 | 85 | private lazy var disposablePersistentStoreCoordinator: NSPersistentStoreCoordinator = { 86 | let model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) 87 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model) 88 | try! persistentStoreCoordinator.addPersistentStore(storeType: .inMemory, bundle: self.modelBundle, modelName: self.modelName, storeName: self.storeName, containerURL: self.containerURL) 89 | 90 | return persistentStoreCoordinator 91 | }() 92 | 93 | /** 94 | Initializes a EncryptedDATAStack using the bundle name as the model name, so if your target is called ModernApp, 95 | it will look for a ModernApp.xcdatamodeld. 96 | */ 97 | public override init() { 98 | let bundle = Bundle.main 99 | if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { 100 | self.modelName = bundleName 101 | } 102 | self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) 103 | super.init() 104 | } 105 | 106 | public convenience init(passphraseKey:String) { 107 | self.init() 108 | self.passphraseKey = passphraseKey 109 | } 110 | 111 | /** 112 | Initializes a EncryptedDATAStack using the provided model name. 113 | - parameter modelName: The name of your Core Data model (xcdatamodeld). 114 | */ 115 | public init(passphraseKey:String, modelName: String) { 116 | self.modelName = modelName 117 | self.passphraseKey = passphraseKey 118 | self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) 119 | 120 | super.init() 121 | } 122 | 123 | /** 124 | Initializes a EncryptedDATAStack using the provided model name, bundle and storeType. 125 | - parameter modelName: The name of your Core Data model (xcdatamodeld). 126 | - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory 127 | based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. 128 | */ 129 | public init(passphraseKey:String? = nil, modelName: String, storeType: EncryptedDATAStackStoreType) { 130 | self.modelName = modelName 131 | self.storeType = storeType 132 | self.passphraseKey = passphraseKey 133 | self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) 134 | 135 | super.init() 136 | } 137 | 138 | /** 139 | Initializes a EncryptedDATAStack using the provided model name, bundle and storeType. 140 | - parameter modelName: The name of your Core Data model (xcdatamodeld). 141 | - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in 142 | the main bundle but when using unit tests sometimes your Core Data model could be located where your tests 143 | are located. 144 | - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory 145 | based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. 146 | */ 147 | public init(passphraseKey:String? = nil, modelName: String, bundle: Bundle, storeType: EncryptedDATAStackStoreType) { 148 | self.modelName = modelName 149 | self.modelBundle = bundle 150 | self.storeType = storeType 151 | self.passphraseKey = passphraseKey 152 | self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) 153 | 154 | super.init() 155 | } 156 | 157 | /** 158 | Initializes a EncryptedDATAStack using the provided model name, bundle, storeType and store name. 159 | - parameter modelName: The name of your Core Data model (xcdatamodeld). 160 | - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in 161 | the main bundle but when using unit tests sometimes your Core Data model could be located where your tests 162 | are located. 163 | - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory 164 | based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. 165 | - parameter storeName: Normally your file would be named as your model name is named, so if your model 166 | name is AwesomeApp then the .sqlite file will be named AwesomeApp.sqlite, this attribute allows your to 167 | change that. 168 | */ 169 | public init(passphraseKey:String? = nil, modelName: String, bundle: Bundle, storeType: EncryptedDATAStackStoreType, storeName: String) { 170 | self.modelName = modelName 171 | self.modelBundle = bundle 172 | self.storeType = storeType 173 | self.storeName = storeName 174 | self.passphraseKey = passphraseKey 175 | self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) 176 | 177 | super.init() 178 | } 179 | 180 | /** 181 | Initializes a EncryptedDATAStack using the provided model name, bundle, storeType and store name. 182 | - parameter modelName: The name of your Core Data model (xcdatamodeld). 183 | - parameter bundle: The bundle where your Core Data model is located, normally your Core Data model is in 184 | the main bundle but when using unit tests sometimes your Core Data model could be located where your tests 185 | are located. 186 | - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory 187 | based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. 188 | - parameter storeName: Normally your file would be named as your model name is named, so if your model 189 | name is AwesomeApp then the .sqlite file will be named AwesomeApp.sqlite, this attribute allows your to 190 | change that. 191 | - parameter containerURL: The container URL for the sqlite file when a store type of SQLite is used. 192 | */ 193 | public init(passphraseKey:String? = nil, modelName: String, bundle: Bundle, storeType: EncryptedDATAStackStoreType, storeName: String, containerURL: URL) { 194 | self.modelName = modelName 195 | self.modelBundle = bundle 196 | self.storeType = storeType 197 | self.storeName = storeName 198 | self.containerURL = containerURL 199 | self.passphraseKey = passphraseKey 200 | self.model = NSManagedObjectModel(bundle: self.modelBundle, name: self.modelName) 201 | 202 | super.init() 203 | } 204 | 205 | /** 206 | Initializes a EncryptedDATAStack using the provided model name, bundle and storeType. 207 | - parameter model: The model that we'll use to set up your EncryptedDATAStack. 208 | - parameter storeType: The store type to be used, you have .InMemory and .SQLite, the first one is memory 209 | based and doesn't save to disk, while the second one creates a .sqlite file and stores things there. 210 | */ 211 | public init(passphraseKey:String? = nil, model: NSManagedObjectModel, storeType: EncryptedDATAStackStoreType) { 212 | self.model = model 213 | self.storeType = storeType 214 | self.passphraseKey = passphraseKey 215 | 216 | let bundle = Bundle.main 217 | if let bundleName = bundle.infoDictionary?["CFBundleName"] as? String { 218 | self.storeName = bundleName 219 | } 220 | 221 | super.init() 222 | } 223 | 224 | deinit { 225 | NotificationCenter.default.removeObserver(self, name: .NSManagedObjectContextWillSave, object: nil) 226 | NotificationCenter.default.removeObserver(self, name: .NSManagedObjectContextDidSave, object: nil) 227 | } 228 | 229 | /** 230 | Returns a new main context that is detached from saving to disk. 231 | */ 232 | public func newDisposableMainContext() -> NSManagedObjectContext { 233 | let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 234 | context.persistentStoreCoordinator = self.disposablePersistentStoreCoordinator 235 | context.undoManager = nil 236 | 237 | NotificationCenter.default.addObserver(self, selector: #selector(EncryptedDATAStack.newDisposableMainContextWillSave(_:)), name: NSNotification.Name.NSManagedObjectContextWillSave, object: context) 238 | 239 | return context 240 | } 241 | 242 | /** 243 | 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. 244 | 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 245 | example when using a NSFetchedResultsController use `try self.fetchedResultsController.performFetch()`. 246 | */ 247 | public func newNonMergingBackgroundContext() -> NSManagedObjectContext { 248 | let context = NSManagedObjectContext(concurrencyType: EncryptedDATAStack.backgroundConcurrencyType()) 249 | context.persistentStoreCoordinator = self.persistentStoreCoordinator 250 | context.undoManager = nil 251 | context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy 252 | 253 | return context 254 | } 255 | 256 | /** 257 | 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. 258 | */ 259 | public func newBackgroundContext() -> NSManagedObjectContext { 260 | let context = NSManagedObjectContext(concurrencyType: EncryptedDATAStack.backgroundConcurrencyType()) 261 | context.persistentStoreCoordinator = self.persistentStoreCoordinator 262 | context.undoManager = nil 263 | context.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy 264 | 265 | NotificationCenter.default.addObserver(self, selector: #selector(EncryptedDATAStack.backgroundContextDidSave(_:)), name: .NSManagedObjectContextDidSave, object: context) 266 | 267 | return context 268 | } 269 | 270 | /** 271 | Returns a background context perfect for data mutability operations. 272 | - parameter operation: The block that contains the created background context. 273 | */ 274 | public func performInNewBackgroundContext(_ operation: @escaping (_ backgroundContext: NSManagedObjectContext) -> Void) { 275 | let context = self.newBackgroundContext() 276 | let contextBlock: @convention(block) () -> Void = { 277 | operation(context) 278 | } 279 | let blockObject: AnyObject = unsafeBitCast(contextBlock, to: AnyObject.self) 280 | context.perform(EncryptedDATAStack.performSelectorForBackgroundContext(), with: blockObject) 281 | } 282 | 283 | /** 284 | Returns a background context perfect for data mutability operations. 285 | - parameter operation: The block that contains the created background context. 286 | */ 287 | public func performBackgroundTask(operation: @escaping (_ backgroundContext: NSManagedObjectContext) -> Void) { 288 | self.performInNewBackgroundContext(operation) 289 | } 290 | 291 | func saveMainThread(completion: ((_ error: NSError?) -> Void)?) { 292 | var writerContextError: NSError? 293 | let writerContextBlock: @convention(block) () -> Void = { 294 | do { 295 | try self.writerContext.save() 296 | if TestCheck.isTesting { 297 | completion?(nil) 298 | } 299 | } catch let parentError as NSError { 300 | writerContextError = parentError 301 | } 302 | } 303 | let writerContextBlockObject: AnyObject = unsafeBitCast(writerContextBlock, to: AnyObject.self) 304 | 305 | let mainContextBlock: @convention(block) () -> Void = { 306 | self.writerContext.perform(EncryptedDATAStack.performSelectorForBackgroundContext(), with: writerContextBlockObject) 307 | DispatchQueue.main.async { 308 | completion?(writerContextError) 309 | } 310 | } 311 | let mainContextBlockObject: AnyObject = unsafeBitCast(mainContextBlock, to: AnyObject.self) 312 | self.mainContext.perform(EncryptedDATAStack.performSelectorForBackgroundContext(), with: mainContextBlockObject) 313 | } 314 | 315 | /** 316 | Drops the database. 317 | */ 318 | public func drop(completion: ((_ error: NSError?) -> Void)? = nil) { 319 | self.writerContext.performAndWait { 320 | self.writerContext.reset() 321 | 322 | self.mainContext.performAndWait { 323 | self.mainContext.reset() 324 | 325 | self.persistentStoreCoordinator.performAndWait { 326 | for store in self.persistentStoreCoordinator.persistentStores { 327 | guard let storeURL = store.url else { continue } 328 | 329 | do { 330 | if #available(iOS 9.0, *) { 331 | try self.persistentStoreCoordinator.destroyPersistentStore(at: storeURL, ofType: self.storeType.type, options: store.options) 332 | } else { 333 | try self.persistentStoreCoordinator.remove(self.persistentStoreCoordinator.persistentStores.last!) 334 | try FileManager.default.removeItem(at: storeURL) 335 | } 336 | try! self.persistentStoreCoordinator.addPersistentStore(storeType: self.storeType, bundle: self.modelBundle, modelName: self.modelName, storeName: self.storeName, containerURL: self.containerURL,passphraseKey:self.passphraseKey) 337 | 338 | DispatchQueue.main.async { 339 | completion?(nil) 340 | } 341 | } catch let error as NSError { 342 | DispatchQueue.main.async { 343 | completion?(NSError(info: "Failed dropping the data stack.", previousError: error)) 344 | } 345 | } 346 | } 347 | } 348 | } 349 | } 350 | } 351 | 352 | /// Sends a request to all the persistent stores associated with the receiver. 353 | /// 354 | /// - Parameters: 355 | /// - request: A fetch, save or delete request. 356 | /// - context: The context against which request should be executed. 357 | /// - 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. 358 | /// - Throws: If an error occurs, upon return contains an NSError object that describes the problem. 359 | public func execute(_ request: NSPersistentStoreRequest, with context: NSManagedObjectContext) throws -> Any { 360 | return try self.persistentStoreCoordinator.execute(request, with: context) 361 | } 362 | 363 | // Can't be private, has to be internal in order to be used as a selector. 364 | func mainContextDidSave(_ notification: Notification) { 365 | self.saveMainThread { error in 366 | if let error = error { 367 | fatalError("Failed to save objects in main thread: \(error)") 368 | } 369 | } 370 | } 371 | 372 | // Can't be private, has to be internal in order to be used as a selector. 373 | func newDisposableMainContextWillSave(_ notification: Notification) { 374 | if let context = notification.object as? NSManagedObjectContext { 375 | context.reset() 376 | } 377 | } 378 | 379 | // Can't be private, has to be internal in order to be used as a selector. 380 | func backgroundContextDidSave(_ notification: Notification) throws { 381 | if Thread.isMainThread && TestCheck.isTesting == false { 382 | throw NSError(info: "Background context saved in the main thread. Use context's `performBlock`", previousError: nil) 383 | } else { 384 | let contextBlock: @convention(block) () -> Void = { 385 | self.mainContext.mergeChanges(fromContextDidSave: notification) 386 | } 387 | let blockObject: AnyObject = unsafeBitCast(contextBlock, to: AnyObject.self) 388 | self.mainContext.perform(EncryptedDATAStack.performSelectorForBackgroundContext(), with: blockObject) 389 | } 390 | } 391 | 392 | private static func backgroundConcurrencyType() -> NSManagedObjectContextConcurrencyType { 393 | return TestCheck.isTesting ? .mainQueueConcurrencyType : .privateQueueConcurrencyType 394 | } 395 | 396 | private static func performSelectorForBackgroundContext() -> Selector { 397 | return TestCheck.isTesting ? NSSelectorFromString("performBlockAndWait:") : NSSelectorFromString("performBlock:") 398 | } 399 | } 400 | 401 | extension NSPersistentStoreCoordinator { 402 | func addPersistentStore(storeType: EncryptedDATAStackStoreType, bundle: Bundle, modelName: String, storeName: String?, containerURL: URL,passphraseKey:String? = nil) throws { 403 | let filePath = (storeName ?? modelName) + ".sqlite" 404 | switch storeType { 405 | case .inMemory: 406 | do { 407 | try self.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil) 408 | } catch let error as NSError { 409 | throw NSError(info: "There was an error creating the persistentStoreCoordinator for in memory store", previousError: error) 410 | } 411 | 412 | break 413 | case .sqLite, .sqLiteNoEncryption: 414 | if storeType == .sqLite{ 415 | assert(passphraseKey != nil, "Set a Passphrase Key while using encrypted sqlite!") 416 | } 417 | let storeURL = containerURL.appendingPathComponent(filePath) 418 | let storePath = storeURL.path 419 | 420 | let shouldPreloadDatabase = !FileManager.default.fileExists(atPath: storePath) 421 | if shouldPreloadDatabase { 422 | if let preloadedPath = bundle.path(forResource: modelName, ofType: "sqlite") { 423 | let preloadURL = URL(fileURLWithPath: preloadedPath) 424 | 425 | do { 426 | try FileManager.default.copyItem(at: preloadURL, to: storeURL) 427 | } catch let error as NSError { 428 | throw NSError(info: "Oops, could not copy preloaded data", previousError: error) 429 | } 430 | } 431 | } 432 | 433 | var options:[String : Any] = [NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true] 434 | if storeType == .sqLite { 435 | options[EncryptedStorePassphraseKey] = passphraseKey 436 | } 437 | do { 438 | try self.addPersistentStore(ofType: storeType.type, configurationName: nil, at: storeURL, options: options) 439 | } catch { 440 | do { 441 | try FileManager.default.removeItem(atPath: storePath) 442 | do { 443 | try self.addPersistentStore(ofType: storeType.type, configurationName: nil, at: storeURL, options: options) 444 | } catch let addPersistentError as NSError { 445 | throw NSError(info: "There was an error creating the persistentStoreCoordinator", previousError: addPersistentError) 446 | } 447 | } catch let removingError as NSError { 448 | throw NSError(info: "There was an error removing the persistentStoreCoordinator", previousError: removingError) 449 | } 450 | } 451 | 452 | let shouldExcludeSQLiteFromBackup = (storeType == .sqLite || storeType == .sqLiteNoEncryption) && TestCheck.isTesting == false 453 | if shouldExcludeSQLiteFromBackup { 454 | do { 455 | try (storeURL as NSURL).setResourceValue(true, forKey: .isExcludedFromBackupKey) 456 | } catch let excludingError as NSError { 457 | throw NSError(info: "Excluding SQLite file from backup caused an error", previousError: excludingError) 458 | } 459 | } 460 | 461 | break 462 | } 463 | } 464 | } 465 | 466 | public extension NSManagedObjectModel { 467 | public convenience init(bundle: Bundle, name: String) { 468 | if let momdModelURL = bundle.url(forResource: name, withExtension: "momd") { 469 | self.init(contentsOf: momdModelURL)! 470 | } else if let momModelURL = bundle.url(forResource: name, withExtension: "mom") { 471 | self.init(contentsOf: momModelURL)! 472 | } else { 473 | self.init() 474 | } 475 | } 476 | } 477 | 478 | extension NSError { 479 | convenience init(info: String, previousError: NSError?) { 480 | if let previousError = previousError { 481 | var userInfo = previousError.userInfo 482 | if let _ = userInfo[NSLocalizedFailureReasonErrorKey] { 483 | userInfo["Additional reason"] = info 484 | } else { 485 | userInfo[NSLocalizedFailureReasonErrorKey] = info 486 | } 487 | 488 | self.init(domain: previousError.domain, code: previousError.code, userInfo: userInfo) 489 | } else { 490 | var userInfo = [String: String]() 491 | userInfo[NSLocalizedDescriptionKey] = info 492 | self.init(domain: "com.SyncDB.EncryptedDATAStack", code: 9999, userInfo: userInfo) 493 | } 494 | } 495 | } 496 | 497 | extension URL { 498 | fileprivate static func directoryURL() -> URL { 499 | #if os(tvOS) 500 | return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).last! 501 | #else 502 | return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! 503 | #endif 504 | } 505 | } 506 | -------------------------------------------------------------------------------- /EncryptedDATAStack/Classes/TestCheck.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct TestCheck { 4 | /** 5 | Checks wheter your on testing mode or not. 6 | - returns: A Bool, `true` if you're on testing mode, `false` if you're not. 7 | */ 8 | static let isTesting: Bool = { 9 | let enviroment = ProcessInfo.processInfo.environment 10 | let serviceName = enviroment["XPC_SERVICE_NAME"] 11 | let injectBundle = enviroment["XCInjectBundle"] 12 | var isRunning = (enviroment["TRAVIS"] != nil || enviroment["XCTestConfigurationFilePath"] != nil) 13 | 14 | if !isRunning { 15 | if let serviceName = serviceName { 16 | isRunning = (serviceName as NSString).pathExtension == "xctest" 17 | } 18 | } 19 | 20 | if !isRunning { 21 | if let injectBundle = injectBundle { 22 | isRunning = (injectBundle as NSString).pathExtension == "xctest" 23 | } 24 | } 25 | 26 | return isRunning 27 | }() 28 | } 29 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5B04181C1DBAC6A40042CB03 /* DemoSwift.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 5B04181A1DBAC6A40042CB03 /* DemoSwift.xcdatamodeld */; }; 11 | 5B680FF41DC2141D009C9780 /* LightweightMigrationModel.xcdatamodel in Sources */ = {isa = PBXBuildFile; fileRef = 5B680FF01DC2141D009C9780 /* LightweightMigrationModel.xcdatamodel */; }; 12 | 5B680FF51DC2141D009C9780 /* ModelGroup.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 5B680FF11DC2141D009C9780 /* ModelGroup.xcdatamodeld */; }; 13 | 5B680FF61DC2141D009C9780 /* SimpleModel.xcdatamodel in Sources */ = {isa = PBXBuildFile; fileRef = 5B680FF31DC2141D009C9780 /* SimpleModel.xcdatamodel */; }; 14 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; }; 15 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; }; 16 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; }; 17 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; }; 18 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; }; 19 | D4E879116ACF8D10D36429E3 /* Pods_EncryptedDATAStack_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 576DB806B6E46B3E9F404B4D /* Pods_EncryptedDATAStack_Example.framework */; }; 20 | FE601D2B65705734A78A2823 /* Pods_EncryptedDATAStack_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058DE430F84E05D54406892C /* Pods_EncryptedDATAStack_Tests.framework */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 5B04181E1DBACCF30042CB03 /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 607FACC81AFB9204008FA782 /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 607FACCF1AFB9204008FA782; 29 | remoteInfo = EncryptedDATAStack_Example; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 058DE430F84E05D54406892C /* Pods_EncryptedDATAStack_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_EncryptedDATAStack_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 10B9588F8E42F393D02D713F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; 36 | 576DB806B6E46B3E9F404B4D /* Pods_EncryptedDATAStack_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_EncryptedDATAStack_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 37 | 5B04181B1DBAC6A40042CB03 /* DemoSwift.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DemoSwift.xcdatamodel; sourceTree = ""; }; 38 | 5B680FF01DC2141D009C9780 /* LightweightMigrationModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LightweightMigrationModel.xcdatamodel; sourceTree = ""; }; 39 | 5B680FF21DC2141D009C9780 /* ModelGroup.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ModelGroup.xcdatamodel; sourceTree = ""; }; 40 | 5B680FF31DC2141D009C9780 /* SimpleModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = SimpleModel.xcdatamodel; sourceTree = ""; }; 41 | 5BBEB6F61DBB6917001B559B /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; name = .travis.yml; path = ../.travis.yml; sourceTree = ""; }; 42 | 5BBEB6F71DBB692C001B559B /* .gitignore */ = {isa = PBXFileReference; lastKnownFileType = text; name = .gitignore; path = ../.gitignore; sourceTree = ""; }; 43 | 607FACD01AFB9204008FA782 /* EncryptedDATAStack_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = EncryptedDATAStack_Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 46 | 607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 47 | 607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 48 | 607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 49 | 607FACE51AFB9204008FA782 /* EncryptedDATAStack_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EncryptedDATAStack_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 51 | 607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = ""; }; 52 | 634E6D12EDE1A4BC613486EF /* EncryptedDATAStack.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = EncryptedDATAStack.podspec; path = ../EncryptedDATAStack.podspec; sourceTree = ""; }; 53 | 84ABCD5D8B03D2935C3A658E /* Pods-EncryptedDATAStack_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EncryptedDATAStack_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-EncryptedDATAStack_Tests/Pods-EncryptedDATAStack_Tests.debug.xcconfig"; sourceTree = ""; }; 54 | 8D58A2F0C5337A96061BF02D /* Pods-EncryptedDATAStack_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EncryptedDATAStack_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-EncryptedDATAStack_Example/Pods-EncryptedDATAStack_Example.debug.xcconfig"; sourceTree = ""; }; 55 | B6BFDD279D32D9ABFA1C9E90 /* Pods-EncryptedDATAStack_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EncryptedDATAStack_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-EncryptedDATAStack_Tests/Pods-EncryptedDATAStack_Tests.release.xcconfig"; sourceTree = ""; }; 56 | B9462BC3DF1C280412EFDCBB /* Pods-EncryptedDATAStack_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-EncryptedDATAStack_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-EncryptedDATAStack_Example/Pods-EncryptedDATAStack_Example.release.xcconfig"; sourceTree = ""; }; 57 | DCE18FCEF1D47987C1C13AEC /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = ""; }; 58 | /* End PBXFileReference section */ 59 | 60 | /* Begin PBXFrameworksBuildPhase section */ 61 | 607FACCD1AFB9204008FA782 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | D4E879116ACF8D10D36429E3 /* Pods_EncryptedDATAStack_Example.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | 607FACE21AFB9204008FA782 /* Frameworks */ = { 70 | isa = PBXFrameworksBuildPhase; 71 | buildActionMask = 2147483647; 72 | files = ( 73 | FE601D2B65705734A78A2823 /* Pods_EncryptedDATAStack_Tests.framework in Frameworks */, 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | /* End PBXFrameworksBuildPhase section */ 78 | 79 | /* Begin PBXGroup section */ 80 | 607FACC71AFB9204008FA782 = { 81 | isa = PBXGroup; 82 | children = ( 83 | 607FACF51AFB993E008FA782 /* Podspec Metadata */, 84 | 607FACD21AFB9204008FA782 /* Example for EncryptedDATAStack */, 85 | 607FACE81AFB9204008FA782 /* Tests */, 86 | 607FACD11AFB9204008FA782 /* Products */, 87 | 8C50ED3FECF86E02D47C1EBD /* Pods */, 88 | F70570465C0B891D9E284791 /* Frameworks */, 89 | ); 90 | sourceTree = ""; 91 | }; 92 | 607FACD11AFB9204008FA782 /* Products */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 607FACD01AFB9204008FA782 /* EncryptedDATAStack_Example.app */, 96 | 607FACE51AFB9204008FA782 /* EncryptedDATAStack_Tests.xctest */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 607FACD21AFB9204008FA782 /* Example for EncryptedDATAStack */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 5B04181A1DBAC6A40042CB03 /* DemoSwift.xcdatamodeld */, 105 | 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 106 | 607FACD71AFB9204008FA782 /* ViewController.swift */, 107 | 607FACDC1AFB9204008FA782 /* Images.xcassets */, 108 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */, 109 | 607FACD31AFB9204008FA782 /* Supporting Files */, 110 | ); 111 | name = "Example for EncryptedDATAStack"; 112 | path = EncryptedDATAStack; 113 | sourceTree = ""; 114 | }; 115 | 607FACD31AFB9204008FA782 /* Supporting Files */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 607FACD41AFB9204008FA782 /* Info.plist */, 119 | ); 120 | name = "Supporting Files"; 121 | sourceTree = ""; 122 | }; 123 | 607FACE81AFB9204008FA782 /* Tests */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 5B680FF01DC2141D009C9780 /* LightweightMigrationModel.xcdatamodel */, 127 | 5B680FF11DC2141D009C9780 /* ModelGroup.xcdatamodeld */, 128 | 5B680FF31DC2141D009C9780 /* SimpleModel.xcdatamodel */, 129 | 607FACEB1AFB9204008FA782 /* Tests.swift */, 130 | 607FACE91AFB9204008FA782 /* Supporting Files */, 131 | ); 132 | path = Tests; 133 | sourceTree = ""; 134 | }; 135 | 607FACE91AFB9204008FA782 /* Supporting Files */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 607FACEA1AFB9204008FA782 /* Info.plist */, 139 | ); 140 | name = "Supporting Files"; 141 | sourceTree = ""; 142 | }; 143 | 607FACF51AFB993E008FA782 /* Podspec Metadata */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 5BBEB6F71DBB692C001B559B /* .gitignore */, 147 | 5BBEB6F61DBB6917001B559B /* .travis.yml */, 148 | 634E6D12EDE1A4BC613486EF /* EncryptedDATAStack.podspec */, 149 | DCE18FCEF1D47987C1C13AEC /* README.md */, 150 | 10B9588F8E42F393D02D713F /* LICENSE */, 151 | ); 152 | name = "Podspec Metadata"; 153 | sourceTree = ""; 154 | }; 155 | 8C50ED3FECF86E02D47C1EBD /* Pods */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | 8D58A2F0C5337A96061BF02D /* Pods-EncryptedDATAStack_Example.debug.xcconfig */, 159 | B9462BC3DF1C280412EFDCBB /* Pods-EncryptedDATAStack_Example.release.xcconfig */, 160 | 84ABCD5D8B03D2935C3A658E /* Pods-EncryptedDATAStack_Tests.debug.xcconfig */, 161 | B6BFDD279D32D9ABFA1C9E90 /* Pods-EncryptedDATAStack_Tests.release.xcconfig */, 162 | ); 163 | name = Pods; 164 | sourceTree = ""; 165 | }; 166 | F70570465C0B891D9E284791 /* Frameworks */ = { 167 | isa = PBXGroup; 168 | children = ( 169 | 576DB806B6E46B3E9F404B4D /* Pods_EncryptedDATAStack_Example.framework */, 170 | 058DE430F84E05D54406892C /* Pods_EncryptedDATAStack_Tests.framework */, 171 | ); 172 | name = Frameworks; 173 | sourceTree = ""; 174 | }; 175 | /* End PBXGroup section */ 176 | 177 | /* Begin PBXNativeTarget section */ 178 | 607FACCF1AFB9204008FA782 /* EncryptedDATAStack_Example */ = { 179 | isa = PBXNativeTarget; 180 | buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "EncryptedDATAStack_Example" */; 181 | buildPhases = ( 182 | 410BCFB47A2D8BF45400231B /* [CP] Check Pods Manifest.lock */, 183 | 607FACCC1AFB9204008FA782 /* Sources */, 184 | 607FACCD1AFB9204008FA782 /* Frameworks */, 185 | 607FACCE1AFB9204008FA782 /* Resources */, 186 | 012DB5DB326FD7B5FDC019E2 /* [CP] Embed Pods Frameworks */, 187 | 353F099BEFD860267289AC1C /* [CP] Copy Pods Resources */, 188 | ); 189 | buildRules = ( 190 | ); 191 | dependencies = ( 192 | ); 193 | name = EncryptedDATAStack_Example; 194 | productName = EncryptedDATAStack; 195 | productReference = 607FACD01AFB9204008FA782 /* EncryptedDATAStack_Example.app */; 196 | productType = "com.apple.product-type.application"; 197 | }; 198 | 607FACE41AFB9204008FA782 /* EncryptedDATAStack_Tests */ = { 199 | isa = PBXNativeTarget; 200 | buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "EncryptedDATAStack_Tests" */; 201 | buildPhases = ( 202 | DDB2CA4D8D16A1178B9C88EB /* [CP] Check Pods Manifest.lock */, 203 | 607FACE11AFB9204008FA782 /* Sources */, 204 | 607FACE21AFB9204008FA782 /* Frameworks */, 205 | 607FACE31AFB9204008FA782 /* Resources */, 206 | E27E22FDEAD1DF50B59E4C46 /* [CP] Embed Pods Frameworks */, 207 | 295C60BE668923D80E87F0E3 /* [CP] Copy Pods Resources */, 208 | ); 209 | buildRules = ( 210 | ); 211 | dependencies = ( 212 | 5B04181F1DBACCF30042CB03 /* PBXTargetDependency */, 213 | ); 214 | name = EncryptedDATAStack_Tests; 215 | productName = Tests; 216 | productReference = 607FACE51AFB9204008FA782 /* EncryptedDATAStack_Tests.xctest */; 217 | productType = "com.apple.product-type.bundle.unit-test"; 218 | }; 219 | /* End PBXNativeTarget section */ 220 | 221 | /* Begin PBXProject section */ 222 | 607FACC81AFB9204008FA782 /* Project object */ = { 223 | isa = PBXProject; 224 | attributes = { 225 | LastSwiftUpdateCheck = 0720; 226 | LastUpgradeCheck = 0800; 227 | ORGANIZATIONNAME = CocoaPods; 228 | TargetAttributes = { 229 | 607FACCF1AFB9204008FA782 = { 230 | CreatedOnToolsVersion = 6.3.1; 231 | DevelopmentTeam = MT49Z4H3J6; 232 | LastSwiftMigration = 0800; 233 | }; 234 | 607FACE41AFB9204008FA782 = { 235 | CreatedOnToolsVersion = 6.3.1; 236 | LastSwiftMigration = 0800; 237 | TestTargetID = 607FACCF1AFB9204008FA782; 238 | }; 239 | }; 240 | }; 241 | buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "EncryptedDATAStack" */; 242 | compatibilityVersion = "Xcode 3.2"; 243 | developmentRegion = English; 244 | hasScannedForEncodings = 0; 245 | knownRegions = ( 246 | en, 247 | Base, 248 | ); 249 | mainGroup = 607FACC71AFB9204008FA782; 250 | productRefGroup = 607FACD11AFB9204008FA782 /* Products */; 251 | projectDirPath = ""; 252 | projectRoot = ""; 253 | targets = ( 254 | 607FACCF1AFB9204008FA782 /* EncryptedDATAStack_Example */, 255 | 607FACE41AFB9204008FA782 /* EncryptedDATAStack_Tests */, 256 | ); 257 | }; 258 | /* End PBXProject section */ 259 | 260 | /* Begin PBXResourcesBuildPhase section */ 261 | 607FACCE1AFB9204008FA782 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | 607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */, 266 | 607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */, 267 | ); 268 | runOnlyForDeploymentPostprocessing = 0; 269 | }; 270 | 607FACE31AFB9204008FA782 /* Resources */ = { 271 | isa = PBXResourcesBuildPhase; 272 | buildActionMask = 2147483647; 273 | files = ( 274 | ); 275 | runOnlyForDeploymentPostprocessing = 0; 276 | }; 277 | /* End PBXResourcesBuildPhase section */ 278 | 279 | /* Begin PBXShellScriptBuildPhase section */ 280 | 012DB5DB326FD7B5FDC019E2 /* [CP] Embed Pods Frameworks */ = { 281 | isa = PBXShellScriptBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | ); 285 | inputPaths = ( 286 | "${SRCROOT}/Pods/Target Support Files/Pods-EncryptedDATAStack_Example/Pods-EncryptedDATAStack_Example-frameworks.sh", 287 | "${BUILT_PRODUCTS_DIR}/DATASource/DATASource.framework", 288 | "${BUILT_PRODUCTS_DIR}/EncryptedCoreData/EncryptedCoreData.framework", 289 | "${BUILT_PRODUCTS_DIR}/EncryptedDATAStack/EncryptedDATAStack.framework", 290 | "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", 291 | ); 292 | name = "[CP] Embed Pods Frameworks"; 293 | outputPaths = ( 294 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DATASource.framework", 295 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EncryptedCoreData.framework", 296 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EncryptedDATAStack.framework", 297 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | shellPath = /bin/sh; 301 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-EncryptedDATAStack_Example/Pods-EncryptedDATAStack_Example-frameworks.sh\"\n"; 302 | showEnvVarsInLog = 0; 303 | }; 304 | 295C60BE668923D80E87F0E3 /* [CP] Copy Pods Resources */ = { 305 | isa = PBXShellScriptBuildPhase; 306 | buildActionMask = 2147483647; 307 | files = ( 308 | ); 309 | inputPaths = ( 310 | ); 311 | name = "[CP] Copy Pods Resources"; 312 | outputPaths = ( 313 | ); 314 | runOnlyForDeploymentPostprocessing = 0; 315 | shellPath = /bin/sh; 316 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-EncryptedDATAStack_Tests/Pods-EncryptedDATAStack_Tests-resources.sh\"\n"; 317 | showEnvVarsInLog = 0; 318 | }; 319 | 353F099BEFD860267289AC1C /* [CP] Copy Pods Resources */ = { 320 | isa = PBXShellScriptBuildPhase; 321 | buildActionMask = 2147483647; 322 | files = ( 323 | ); 324 | inputPaths = ( 325 | ); 326 | name = "[CP] Copy Pods Resources"; 327 | outputPaths = ( 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | shellPath = /bin/sh; 331 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-EncryptedDATAStack_Example/Pods-EncryptedDATAStack_Example-resources.sh\"\n"; 332 | showEnvVarsInLog = 0; 333 | }; 334 | 410BCFB47A2D8BF45400231B /* [CP] Check Pods Manifest.lock */ = { 335 | isa = PBXShellScriptBuildPhase; 336 | buildActionMask = 2147483647; 337 | files = ( 338 | ); 339 | inputPaths = ( 340 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 341 | "${PODS_ROOT}/Manifest.lock", 342 | ); 343 | name = "[CP] Check Pods Manifest.lock"; 344 | outputPaths = ( 345 | "$(DERIVED_FILE_DIR)/Pods-EncryptedDATAStack_Example-checkManifestLockResult.txt", 346 | ); 347 | runOnlyForDeploymentPostprocessing = 0; 348 | shellPath = /bin/sh; 349 | 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"; 350 | showEnvVarsInLog = 0; 351 | }; 352 | DDB2CA4D8D16A1178B9C88EB /* [CP] Check Pods Manifest.lock */ = { 353 | isa = PBXShellScriptBuildPhase; 354 | buildActionMask = 2147483647; 355 | files = ( 356 | ); 357 | inputPaths = ( 358 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 359 | "${PODS_ROOT}/Manifest.lock", 360 | ); 361 | name = "[CP] Check Pods Manifest.lock"; 362 | outputPaths = ( 363 | "$(DERIVED_FILE_DIR)/Pods-EncryptedDATAStack_Tests-checkManifestLockResult.txt", 364 | ); 365 | runOnlyForDeploymentPostprocessing = 0; 366 | shellPath = /bin/sh; 367 | 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"; 368 | showEnvVarsInLog = 0; 369 | }; 370 | E27E22FDEAD1DF50B59E4C46 /* [CP] Embed Pods Frameworks */ = { 371 | isa = PBXShellScriptBuildPhase; 372 | buildActionMask = 2147483647; 373 | files = ( 374 | ); 375 | inputPaths = ( 376 | ); 377 | name = "[CP] Embed Pods Frameworks"; 378 | outputPaths = ( 379 | ); 380 | runOnlyForDeploymentPostprocessing = 0; 381 | shellPath = /bin/sh; 382 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-EncryptedDATAStack_Tests/Pods-EncryptedDATAStack_Tests-frameworks.sh\"\n"; 383 | showEnvVarsInLog = 0; 384 | }; 385 | /* End PBXShellScriptBuildPhase section */ 386 | 387 | /* Begin PBXSourcesBuildPhase section */ 388 | 607FACCC1AFB9204008FA782 /* Sources */ = { 389 | isa = PBXSourcesBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, 393 | 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, 394 | 5B04181C1DBAC6A40042CB03 /* DemoSwift.xcdatamodeld in Sources */, 395 | ); 396 | runOnlyForDeploymentPostprocessing = 0; 397 | }; 398 | 607FACE11AFB9204008FA782 /* Sources */ = { 399 | isa = PBXSourcesBuildPhase; 400 | buildActionMask = 2147483647; 401 | files = ( 402 | 5B680FF41DC2141D009C9780 /* LightweightMigrationModel.xcdatamodel in Sources */, 403 | 607FACEC1AFB9204008FA782 /* Tests.swift in Sources */, 404 | 5B680FF61DC2141D009C9780 /* SimpleModel.xcdatamodel in Sources */, 405 | 5B680FF51DC2141D009C9780 /* ModelGroup.xcdatamodeld in Sources */, 406 | ); 407 | runOnlyForDeploymentPostprocessing = 0; 408 | }; 409 | /* End PBXSourcesBuildPhase section */ 410 | 411 | /* Begin PBXTargetDependency section */ 412 | 5B04181F1DBACCF30042CB03 /* PBXTargetDependency */ = { 413 | isa = PBXTargetDependency; 414 | target = 607FACCF1AFB9204008FA782 /* EncryptedDATAStack_Example */; 415 | targetProxy = 5B04181E1DBACCF30042CB03 /* PBXContainerItemProxy */; 416 | }; 417 | /* End PBXTargetDependency section */ 418 | 419 | /* Begin PBXVariantGroup section */ 420 | 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = { 421 | isa = PBXVariantGroup; 422 | children = ( 423 | 607FACDF1AFB9204008FA782 /* Base */, 424 | ); 425 | name = LaunchScreen.xib; 426 | sourceTree = ""; 427 | }; 428 | /* End PBXVariantGroup section */ 429 | 430 | /* Begin XCBuildConfiguration section */ 431 | 607FACED1AFB9204008FA782 /* Debug */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | ALWAYS_SEARCH_USER_PATHS = NO; 435 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 436 | CLANG_CXX_LIBRARY = "libc++"; 437 | CLANG_ENABLE_MODULES = YES; 438 | CLANG_ENABLE_OBJC_ARC = YES; 439 | CLANG_WARN_BOOL_CONVERSION = YES; 440 | CLANG_WARN_CONSTANT_CONVERSION = YES; 441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INFINITE_RECURSION = YES; 445 | CLANG_WARN_INT_CONVERSION = YES; 446 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 447 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 448 | CLANG_WARN_UNREACHABLE_CODE = YES; 449 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 450 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 451 | COPY_PHASE_STRIP = NO; 452 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 453 | ENABLE_STRICT_OBJC_MSGSEND = YES; 454 | ENABLE_TESTABILITY = YES; 455 | GCC_C_LANGUAGE_STANDARD = gnu99; 456 | GCC_DYNAMIC_NO_PIC = NO; 457 | GCC_NO_COMMON_BLOCKS = YES; 458 | GCC_OPTIMIZATION_LEVEL = 0; 459 | GCC_PREPROCESSOR_DEFINITIONS = ( 460 | "DEBUG=1", 461 | "$(inherited)", 462 | ); 463 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 464 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 465 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 466 | GCC_WARN_UNDECLARED_SELECTOR = YES; 467 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 468 | GCC_WARN_UNUSED_FUNCTION = YES; 469 | GCC_WARN_UNUSED_VARIABLE = YES; 470 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 471 | MTL_ENABLE_DEBUG_INFO = YES; 472 | ONLY_ACTIVE_ARCH = YES; 473 | SDKROOT = iphoneos; 474 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 475 | }; 476 | name = Debug; 477 | }; 478 | 607FACEE1AFB9204008FA782 /* Release */ = { 479 | isa = XCBuildConfiguration; 480 | buildSettings = { 481 | ALWAYS_SEARCH_USER_PATHS = NO; 482 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 483 | CLANG_CXX_LIBRARY = "libc++"; 484 | CLANG_ENABLE_MODULES = YES; 485 | CLANG_ENABLE_OBJC_ARC = YES; 486 | CLANG_WARN_BOOL_CONVERSION = YES; 487 | CLANG_WARN_CONSTANT_CONVERSION = YES; 488 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 489 | CLANG_WARN_EMPTY_BODY = YES; 490 | CLANG_WARN_ENUM_CONVERSION = YES; 491 | CLANG_WARN_INFINITE_RECURSION = YES; 492 | CLANG_WARN_INT_CONVERSION = YES; 493 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 494 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 495 | CLANG_WARN_UNREACHABLE_CODE = YES; 496 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 497 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 498 | COPY_PHASE_STRIP = NO; 499 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 500 | ENABLE_NS_ASSERTIONS = NO; 501 | ENABLE_STRICT_OBJC_MSGSEND = YES; 502 | GCC_C_LANGUAGE_STANDARD = gnu99; 503 | GCC_NO_COMMON_BLOCKS = YES; 504 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 505 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 506 | GCC_WARN_UNDECLARED_SELECTOR = YES; 507 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 508 | GCC_WARN_UNUSED_FUNCTION = YES; 509 | GCC_WARN_UNUSED_VARIABLE = YES; 510 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 511 | MTL_ENABLE_DEBUG_INFO = NO; 512 | SDKROOT = iphoneos; 513 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 514 | VALIDATE_PRODUCT = YES; 515 | }; 516 | name = Release; 517 | }; 518 | 607FACF01AFB9204008FA782 /* Debug */ = { 519 | isa = XCBuildConfiguration; 520 | baseConfigurationReference = 8D58A2F0C5337A96061BF02D /* Pods-EncryptedDATAStack_Example.debug.xcconfig */; 521 | buildSettings = { 522 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 523 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 524 | DEVELOPMENT_TEAM = MT49Z4H3J6; 525 | INFOPLIST_FILE = EncryptedDATAStack/Info.plist; 526 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 527 | MODULE_NAME = ExampleApp; 528 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.EncryptedDATAStack-Example2"; 529 | PRODUCT_NAME = "$(TARGET_NAME)"; 530 | SWIFT_VERSION = 3.0; 531 | }; 532 | name = Debug; 533 | }; 534 | 607FACF11AFB9204008FA782 /* Release */ = { 535 | isa = XCBuildConfiguration; 536 | baseConfigurationReference = B9462BC3DF1C280412EFDCBB /* Pods-EncryptedDATAStack_Example.release.xcconfig */; 537 | buildSettings = { 538 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 539 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 540 | DEVELOPMENT_TEAM = MT49Z4H3J6; 541 | INFOPLIST_FILE = EncryptedDATAStack/Info.plist; 542 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 543 | MODULE_NAME = ExampleApp; 544 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.EncryptedDATAStack-Example2"; 545 | PRODUCT_NAME = "$(TARGET_NAME)"; 546 | SWIFT_VERSION = 3.0; 547 | }; 548 | name = Release; 549 | }; 550 | 607FACF31AFB9204008FA782 /* Debug */ = { 551 | isa = XCBuildConfiguration; 552 | baseConfigurationReference = 84ABCD5D8B03D2935C3A658E /* Pods-EncryptedDATAStack_Tests.debug.xcconfig */; 553 | buildSettings = { 554 | FRAMEWORK_SEARCH_PATHS = ( 555 | "$(SDKROOT)/Developer/Library/Frameworks", 556 | "$(inherited)", 557 | ); 558 | GCC_PREPROCESSOR_DEFINITIONS = ( 559 | "DEBUG=1", 560 | "$(inherited)", 561 | ); 562 | INFOPLIST_FILE = Tests/Info.plist; 563 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 564 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 565 | PRODUCT_NAME = "$(TARGET_NAME)"; 566 | SWIFT_VERSION = 3.0; 567 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/EncryptedDATAStack_Example.app/EncryptedDATAStack_Example"; 568 | }; 569 | name = Debug; 570 | }; 571 | 607FACF41AFB9204008FA782 /* Release */ = { 572 | isa = XCBuildConfiguration; 573 | baseConfigurationReference = B6BFDD279D32D9ABFA1C9E90 /* Pods-EncryptedDATAStack_Tests.release.xcconfig */; 574 | buildSettings = { 575 | FRAMEWORK_SEARCH_PATHS = ( 576 | "$(SDKROOT)/Developer/Library/Frameworks", 577 | "$(inherited)", 578 | ); 579 | INFOPLIST_FILE = Tests/Info.plist; 580 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 581 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)"; 582 | PRODUCT_NAME = "$(TARGET_NAME)"; 583 | SWIFT_VERSION = 3.0; 584 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/EncryptedDATAStack_Example.app/EncryptedDATAStack_Example"; 585 | }; 586 | name = Release; 587 | }; 588 | /* End XCBuildConfiguration section */ 589 | 590 | /* Begin XCConfigurationList section */ 591 | 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "EncryptedDATAStack" */ = { 592 | isa = XCConfigurationList; 593 | buildConfigurations = ( 594 | 607FACED1AFB9204008FA782 /* Debug */, 595 | 607FACEE1AFB9204008FA782 /* Release */, 596 | ); 597 | defaultConfigurationIsVisible = 0; 598 | defaultConfigurationName = Release; 599 | }; 600 | 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "EncryptedDATAStack_Example" */ = { 601 | isa = XCConfigurationList; 602 | buildConfigurations = ( 603 | 607FACF01AFB9204008FA782 /* Debug */, 604 | 607FACF11AFB9204008FA782 /* Release */, 605 | ); 606 | defaultConfigurationIsVisible = 0; 607 | defaultConfigurationName = Release; 608 | }; 609 | 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "EncryptedDATAStack_Tests" */ = { 610 | isa = XCConfigurationList; 611 | buildConfigurations = ( 612 | 607FACF31AFB9204008FA782 /* Debug */, 613 | 607FACF41AFB9204008FA782 /* Release */, 614 | ); 615 | defaultConfigurationIsVisible = 0; 616 | defaultConfigurationName = Release; 617 | }; 618 | /* End XCConfigurationList section */ 619 | 620 | /* Begin XCVersionGroup section */ 621 | 5B04181A1DBAC6A40042CB03 /* DemoSwift.xcdatamodeld */ = { 622 | isa = XCVersionGroup; 623 | children = ( 624 | 5B04181B1DBAC6A40042CB03 /* DemoSwift.xcdatamodel */, 625 | ); 626 | currentVersion = 5B04181B1DBAC6A40042CB03 /* DemoSwift.xcdatamodel */; 627 | path = DemoSwift.xcdatamodeld; 628 | sourceTree = ""; 629 | versionGroupType = wrapper.xcdatamodel; 630 | }; 631 | 5B680FF11DC2141D009C9780 /* ModelGroup.xcdatamodeld */ = { 632 | isa = XCVersionGroup; 633 | children = ( 634 | 5B680FF21DC2141D009C9780 /* ModelGroup.xcdatamodel */, 635 | ); 636 | currentVersion = 5B680FF21DC2141D009C9780 /* ModelGroup.xcdatamodel */; 637 | path = ModelGroup.xcdatamodeld; 638 | sourceTree = ""; 639 | versionGroupType = wrapper.xcdatamodel; 640 | }; 641 | /* End XCVersionGroup section */ 642 | }; 643 | rootObject = 607FACC81AFB9204008FA782 /* Project object */; 644 | } 645 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack.xcodeproj/xcshareddata/xcschemes/EncryptedDATAStack-Example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 54 | 55 | 56 | 57 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 80 | 82 | 88 | 89 | 90 | 91 | 92 | 93 | 99 | 101 | 107 | 108 | 109 | 110 | 112 | 113 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import EncryptedDATAStack 3 | 4 | @UIApplicationMain 5 | class AppDelegate: UIResponder, UIApplicationDelegate { 6 | 7 | var window: UIWindow? = { 8 | let window = UIWindow(frame: UIScreen.main.bounds) 9 | 10 | return window 11 | }() 12 | 13 | var dataStack: EncryptedDATAStack = { 14 | let dataStack = EncryptedDATAStack(passphraseKey:"randomPassj", modelName: "DemoSwift") 15 | 16 | return dataStack 17 | }() 18 | 19 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 20 | if let window = self.window { 21 | let viewController = ViewController(dataStack: self.dataStack) 22 | window.rootViewController = UINavigationController(rootViewController: viewController) 23 | window.makeKeyAndVisible() 24 | } 25 | 26 | return true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack/DemoSwift.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | DemoSwift.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack/DemoSwift.xcdatamodeld/DemoSwift.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Example/EncryptedDATAStack/ViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import EncryptedDATAStack 3 | import DATASource 4 | 5 | class ViewController: UITableViewController { 6 | 7 | var dataStack: EncryptedDATAStack 8 | 9 | lazy var dataSource: DATASource = { 10 | let request: NSFetchRequest = NSFetchRequest(entityName: "User") 11 | request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)] 12 | 13 | let dataSource = DATASource(tableView: self.tableView, cellIdentifier: "Cell", fetchRequest: request, mainContext: self.dataStack.mainContext, configuration: { cell, item, indexPath in 14 | if let name = item.value(forKey: "name") as? String, let createdDate = item.value(forKey: "createdDate") as? NSDate { 15 | cell.textLabel?.text = name + " - " + createdDate.description 16 | } 17 | }) 18 | 19 | return dataSource 20 | }() 21 | 22 | init(dataStack: EncryptedDATAStack) { 23 | self.dataStack = dataStack 24 | 25 | super.init(style: .plain) 26 | } 27 | 28 | required init?(coder aDecoder: NSCoder) { 29 | fatalError("init(coder:) has not been implemented") 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell") 36 | self.tableView.dataSource = self.dataSource 37 | 38 | let backgroundButton = UIBarButtonItem(title: "Background", style: .done, target: self, action: #selector(ViewController.createBackground)) 39 | self.navigationItem.rightBarButtonItem = backgroundButton 40 | 41 | let mainButton = UIBarButtonItem(title: "Main", style: .done, target: self, action: #selector(ViewController.createMain)) 42 | self.navigationItem.leftBarButtonItem = mainButton 43 | } 44 | 45 | func createBackground() { 46 | self.dataStack.performInNewBackgroundContext { backgroundContext in 47 | let entity = NSEntityDescription.entity(forEntityName: "User", in: backgroundContext)! 48 | let object = NSManagedObject(entity: entity, insertInto: backgroundContext) 49 | object.setValue("Background", forKey: "name") 50 | object.setValue(NSDate(), forKey: "createdDate") 51 | try! backgroundContext.save() 52 | } 53 | } 54 | 55 | func createMain() { 56 | let entity = NSEntityDescription.entity(forEntityName: "User", in: self.dataStack.mainContext)! 57 | let object = NSManagedObject(entity: entity, insertInto: self.dataStack.mainContext) 58 | object.setValue("Main", forKey: "name") 59 | object.setValue(Date(), forKey: "createdDate") 60 | try! self.dataStack.mainContext.save() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'EncryptedDATAStack_Example' do 4 | pod 'EncryptedDATAStack', :path => '../' 5 | pod 'DATASource’, '~> 6.1.5' 6 | pod 'EncryptedCoreData', :git => 'https://github.com/project-imas/encrypted-core-data.git' 7 | 8 | target 'EncryptedDATAStack_Tests' do 9 | inherit! :search_paths 10 | 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - DATASource (6.1.5) 3 | - EncryptedCoreData (3.1): 4 | - SQLCipher (~> 3.4.0) 5 | - EncryptedDATAStack (7.0.3): 6 | - EncryptedCoreData (~> 3.1) 7 | - SQLCipher (3.4.0): 8 | - SQLCipher/standard (= 3.4.0) 9 | - SQLCipher/common (3.4.0) 10 | - SQLCipher/standard (3.4.0): 11 | - SQLCipher/common 12 | 13 | DEPENDENCIES: 14 | - DATASource (~> 6.1.5) 15 | - EncryptedCoreData (from `https://github.com/project-imas/encrypted-core-data.git`) 16 | - EncryptedDATAStack (from `../`) 17 | 18 | EXTERNAL SOURCES: 19 | EncryptedCoreData: 20 | :git: https://github.com/project-imas/encrypted-core-data.git 21 | EncryptedDATAStack: 22 | :path: ../ 23 | 24 | CHECKOUT OPTIONS: 25 | EncryptedCoreData: 26 | :commit: b97ffaf2f19dad4d1558bc9b0668cc2e09d17347 27 | :git: https://github.com/project-imas/encrypted-core-data.git 28 | 29 | SPEC CHECKSUMS: 30 | DATASource: 52b86cfff6c7ac3db45192c01f7839c7473576c3 31 | EncryptedCoreData: f6762fb05f88a52f36c5648659abc91b57f244b4 32 | EncryptedDATAStack: 84897982fa1826a44a0306af5890cd786c43b4b1 33 | SQLCipher: 4c768761421736a247ed6cf412d9045615d53dff 34 | 35 | PODFILE CHECKSUM: eb0fd5215d30babaff403c4078e4c632c9c0447c 36 | 37 | COCOAPODS: 1.3.1 38 | -------------------------------------------------------------------------------- /Example/Tests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Example/Tests/LightweightMigrationModel.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Example/Tests/ModelGroup.xcdatamodeld/ModelGroup.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Tests/SimpleModel.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Example/Tests/Tests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import EncryptedDATAStack 3 | 4 | extension XCTestCase { 5 | func createDataStack(_ storeType: EncryptedDATAStackStoreType = .inMemory) -> EncryptedDATAStack { 6 | let dataStack = EncryptedDATAStack(passphraseKey:"randomPass", modelName: "ModelGroup", bundle: Bundle(for: Tests.self), storeType: storeType) 7 | 8 | return dataStack 9 | } 10 | 11 | @discardableResult 12 | func insertUser(in context: NSManagedObjectContext) -> NSManagedObject { 13 | let user = NSEntityDescription.insertNewObject(forEntityName: "User", into: context) 14 | user.setValue(NSNumber(value: 1), forKey: "remoteID") 15 | user.setValue("Joshua Ivanof", forKey: "name") 16 | try! context.save() 17 | 18 | return user 19 | } 20 | 21 | func fetch(in context: NSManagedObjectContext) -> [NSManagedObject] { 22 | let request = NSFetchRequest(entityName: "User") 23 | let objects = try! context.fetch(request) 24 | 25 | return objects 26 | } 27 | } 28 | 29 | class InitializerTests: XCTestCase { 30 | func testInitializeUsingXCDataModel() { 31 | let dataStack = EncryptedDATAStack(passphraseKey:"randomPass", modelName: "SimpleModel", bundle: Bundle(for: Tests.self), storeType: .inMemory) 32 | 33 | self.insertUser(in: dataStack.mainContext) 34 | let objects = self.fetch(in: dataStack.mainContext) 35 | XCTAssertEqual(objects.count, 1) 36 | } 37 | 38 | // xcdatamodeld is a container for .xcdatamodel files. It's used for versioning and migration. 39 | // When moving from v1 of the model to v2, you add a new xcdatamodel to it that has v2 along with the mapping model. 40 | func testInitializeUsingXCDataModeld() { 41 | let dataStack = self.createDataStack() 42 | 43 | self.insertUser(in: dataStack.mainContext) 44 | let objects = self.fetch(in: dataStack.mainContext) 45 | XCTAssertEqual(objects.count, 1) 46 | } 47 | 48 | func testInitializingUsingNSManagedObjectModel() { 49 | let model = NSManagedObjectModel(bundle: Bundle(for: Tests.self), name: "ModelGroup") 50 | let dataStack = EncryptedDATAStack(passphraseKey:"randomPass", model: model, storeType: .inMemory) 51 | 52 | self.insertUser(in: dataStack.mainContext) 53 | let objects = self.fetch(in: dataStack.mainContext) 54 | XCTAssertEqual(objects.count, 1) 55 | } 56 | } 57 | 58 | class Tests: XCTestCase { 59 | func testSynchronousBackgroundContext() { 60 | let dataStack = self.createDataStack() 61 | 62 | var synchronous = false 63 | dataStack.performInNewBackgroundContext { _ in 64 | synchronous = true 65 | } 66 | 67 | XCTAssertTrue(synchronous) 68 | } 69 | 70 | func testBackgroundContextSave() { 71 | let dataStack = self.createDataStack() 72 | 73 | dataStack.performInNewBackgroundContext { backgroundContext in 74 | self.insertUser(in: backgroundContext) 75 | 76 | let objects = self.fetch(in: backgroundContext) 77 | XCTAssertEqual(objects.count, 1) 78 | } 79 | 80 | let objects = self.fetch(in: dataStack.mainContext) 81 | XCTAssertEqual(objects.count, 1) 82 | } 83 | 84 | func testNewBackgroundContextSave() { 85 | var synchronous = false 86 | let dataStack = self.createDataStack() 87 | let backgroundContext = dataStack.newBackgroundContext() 88 | backgroundContext.performAndWait { 89 | synchronous = true 90 | self.insertUser(in: backgroundContext) 91 | let objects = self.fetch(in: backgroundContext) 92 | XCTAssertEqual(objects.count, 1) 93 | } 94 | 95 | let objects = self.fetch(in: dataStack.mainContext) 96 | XCTAssertEqual(objects.count, 1) 97 | 98 | XCTAssertTrue(synchronous) 99 | } 100 | 101 | func testRequestWithDictionaryResultType() { 102 | let dataStack = self.createDataStack() 103 | self.insertUser(in: dataStack.mainContext) 104 | 105 | let request = NSFetchRequest(entityName: "User") 106 | let objects = try! dataStack.mainContext.fetch(request) 107 | XCTAssertEqual(objects.count, 1) 108 | 109 | let expression = NSExpressionDescription() 110 | expression.name = "objectID" 111 | expression.expression = NSExpression.expressionForEvaluatedObject() 112 | expression.expressionResultType = .objectIDAttributeType 113 | 114 | let dictionaryRequest = NSFetchRequest(entityName: "User") 115 | dictionaryRequest.resultType = .dictionaryResultType 116 | dictionaryRequest.propertiesToFetch = [expression, "remoteID"] 117 | 118 | let dictionaryObjects = try! dataStack.mainContext.fetch(dictionaryRequest) 119 | XCTAssertEqual(dictionaryObjects.count, 1) 120 | } 121 | 122 | func testDisposableContextSave() { 123 | let dataStack = self.createDataStack() 124 | 125 | let disposableContext = dataStack.newDisposableMainContext() 126 | self.insertUser(in: disposableContext) 127 | let objects = self.fetch(in: disposableContext) 128 | XCTAssertEqual(objects.count, 0) 129 | } 130 | 131 | func testDrop() { 132 | let dataStackArray = [self.createDataStack(.sqLite), self.createDataStack(.sqLiteNoEncryption)] 133 | 134 | for dataStack in dataStackArray{ 135 | 136 | dataStack.performInNewBackgroundContext { backgroundContext in 137 | self.insertUser(in: backgroundContext) 138 | } 139 | 140 | let objectsA = self.fetch(in: dataStack.mainContext) 141 | XCTAssertEqual(objectsA.count, 1) 142 | 143 | dataStack.drop() 144 | 145 | let objects = self.fetch(in: dataStack.mainContext) 146 | XCTAssertEqual(objects.count, 0) 147 | 148 | dataStack.performInNewBackgroundContext { backgroundContext in 149 | self.insertUser(in: backgroundContext) 150 | } 151 | 152 | let objectsB = self.fetch(in: dataStack.mainContext) 153 | XCTAssertEqual(objectsB.count, 1) 154 | 155 | dataStack.drop() 156 | } 157 | } 158 | 159 | 160 | 161 | func testAutomaticMigration() { 162 | let storeTests:[EncryptedDATAStackStoreType] = [.sqLite, .sqLiteNoEncryption] 163 | 164 | for storeType in storeTests{ 165 | let firstDataStack = EncryptedDATAStack(passphraseKey:"randomPass", modelName: "SimpleModel", bundle: Bundle(for: Tests.self), storeType: storeType, storeName: "Shared") 166 | self.insertUser(in: firstDataStack.mainContext) 167 | let objects = self.fetch(in: firstDataStack.mainContext) 168 | XCTAssertEqual(objects.count, 1) 169 | 170 | // LightweightMigrationModel is a copy of DataModel with the main difference that adds the updatedDate attribute. 171 | let secondDataStack = EncryptedDATAStack(passphraseKey:"randomPass", modelName: "LightweightMigrationModel", bundle: Bundle(for: Tests.self), storeType: storeType, storeName: "Shared") 172 | let fetchRequest = NSFetchRequest(entityName: "User") 173 | fetchRequest.predicate = NSPredicate(format: "remoteID = %@", NSNumber(value: 1)) 174 | let user = try! secondDataStack.mainContext.fetch(fetchRequest).first 175 | XCTAssertNotNil(user) 176 | XCTAssertEqual(user?.value(forKey: "name") as? String, "Joshua Ivanof") 177 | user?.setValue(Date().addingTimeInterval(16000), forKey: "updatedDate") 178 | try! secondDataStack.mainContext.save() 179 | 180 | firstDataStack.drop() 181 | secondDataStack.drop() 182 | } 183 | 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 Rodrigo Copetti, Elvis Nuñez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EncryptedDATAStack 2 | [![](http://img.shields.io/badge/iOS-8.0%2B-blue.svg)]() 3 | [![](http://img.shields.io/badge/Swift-3-red.svg)]() 4 | [![CI Status](http://img.shields.io/travis/flipacholas/EncryptedDATAStack.svg?style=flat)](https://travis-ci.org/flipacholas/EncryptedDATAStack) 5 | [![Version](https://img.shields.io/cocoapods/v/EncryptedDATAStack.svg?style=flat)](http://cocoapods.org/pods/EncryptedDATAStack) 6 | [![License](https://img.shields.io/cocoapods/l/EncryptedDATAStack.svg?style=flat)](http://cocoapods.org/pods/EncryptedDATAStack) 7 | [![Platform](https://img.shields.io/cocoapods/p/EncryptedDATAStack.svg?style=flat)](http://cocoapods.org/pods/EncryptedDATAStack) 8 | 9 | Set up an encrypted Database with only 1 line of code! 10 | 11 | **EncryptedDATAStack** is a 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. 12 | All in all, this allows you to set up a database (encrypted and/or unencrypted) with only one line of code! 13 | 14 | Version tags are set to match the version of DATAStack used. 15 | 16 | 17 | ## Table of Contents 18 | 19 | * [Running the demos](#running-the-demo) 20 | * [Initialization](#initialization) 21 | * [Installation](#installation) 22 | * [Main Thread NSManagedObjectContext](#main-thread-nsmanagedobjectcontext) 23 | * [Background Thread NSManagedObjectContext](#background-thread-nsmanagedobjectcontext) 24 | * [Clean up](#clean-up) 25 | * [Testing](#testing) 26 | * [Migrations](#migrations) 27 | * [Improvements](#improvements) 28 | * [Author](#author) 29 | * [License](#license) 30 | 31 | ## Running the demo 32 | Before being able to run the demo you have to install the demo dependencies using [CocoaPods](https://cocoapods.org/). 33 | 34 | - Install CocoaPods 35 | - Type `pod install` 36 | - Run Example workspace 37 | 38 | ## Initialization 39 | 40 | You can easily initialize a new instance of **EncryptedDATAStack** with just your Core Data Model name (xcdatamodel). 41 | 42 | **Swift** 43 | ``` swift 44 | let encryptedDataStack = EncryptedDATAStack(passphraseKey:"YOUR_PASSWORD", modelName:"MyAppModel") 45 | ``` 46 | 47 | **Objective-C** 48 | ``` objc 49 | EncryptedDATAStack *encryptedDataStack = [[EncryptedDATAStack alloc] initWithPassphraseKey:@"YOUR_PASSWORD" modelName:@"MyAppModel"]; 50 | ``` 51 | 52 | There are plenty of other ways to intialize an EncryptedDATAStack: 53 | 54 | - Using a custom store type. 55 | 56 | ``` swift 57 | //For Memory Storage 58 | let encryptedDataStack = EncryptedDATAStack(modelName:"MyAppModel", storeType: .InMemory) 59 | ``` 60 | 61 | ``` swift 62 | //For Regular SQLite 63 | let encryptedDataStack = EncryptedDATAStack(modelName:"MyAppModel", storeType: .sqLiteNoEncryption) 64 | ``` 65 | 66 | - Using another bundle and a store type, let's say your test bundle and .InMemory store type, perfect for running unit tests. 67 | 68 | ``` swift 69 | let encryptedDataStack = EncryptedDATAStack(modelName: "Model", bundle: NSBundle(forClass: Tests.self), storeType: .InMemory) 70 | ``` 71 | 72 | - Using a different name for your .sqlite file than your model name, like `CustomStoreName.sqlite`. 73 | 74 | ``` swift 75 | let encryptedDataStack = EncryptedDATAStack(passphraseKey:"YOUR_PASSWORD", modelName: "Model", bundle: NSBundle.mainBundle(), storeType: .sqLite, storeName: "CustomStoreName") 76 | ``` 77 | 78 | - 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. 79 | 80 | ``` swift 81 | let encryptedDataStack = EncryptedDATAStack(passphraseKey:"YOUR_PASSWORD", modelName: "Model", bundle: NSBundle.mainBundle(), storeType: .sqLite, storeName: "CustomStoreName", containerURL: sharedURL) 82 | ``` 83 | 84 | ## Main Thread NSManagedObjectContext 85 | 86 | Getting access to the NSManagedObjectContext attached to the main thread is as simple as using the `mainContext` property. 87 | 88 | ```swift 89 | self.encryptedDataStack.mainContext 90 | ``` 91 | 92 | or 93 | 94 | ```swift 95 | self.encryptedDataStack.viewContext 96 | ``` 97 | 98 | ## Background Thread NSManagedObjectContext 99 | 100 | You can easily create a new background NSManagedObjectContext for data processing. This block is completely asynchronous and will be run on a background thread. 101 | 102 | To be compatible with NSPersistentContainer you can also use `performBackgroundTask` instead of `performInNewBackgroundContext`. 103 | 104 | **Swift** 105 | ```swift 106 | func createUser() { 107 | self.encryptedDataStack.performInNewBackgroundContext { backgroundContext in 108 | let entity = NSEntityDescription.entityForName("User", inManagedObjectContext: backgroundContext)! 109 | let object = NSManagedObject(entity: entity, insertIntoManagedObjectContext: backgroundContext) 110 | object.setValue("Background", forKey: "name") 111 | object.setValue(NSDate(), forKey: "createdDate") 112 | try! backgroundContext.save() 113 | } 114 | } 115 | ``` 116 | 117 | **Objective-C** 118 | ```objc 119 | - (void)createUser { 120 | [self.encryptedDataStack performInNewBackgroundContext:^(NSManagedObjectContext * _Nonnull backgroundContext) { 121 | NSEntityDescription *entity = [NSEntityDescription entityForName:@"User" inManagedObjectContext:backgroundContext]; 122 | NSManagedObject *object = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:backgroundContext]; 123 | [object setValue:@"Background" forKey:@"name"]; 124 | [object setValue:[NSDate date] forKey:@"createdDate"]; 125 | [backgroundContext save:nil]; 126 | }]; 127 | } 128 | ``` 129 | 130 | When using Xcode's Objective-C autocompletion the `backgroundContext` parameter name doesn't get included. Make sure to add it. 131 | 132 | ## Clean up 133 | 134 | Deleting the `.sqlite` file and resetting the state of your **EncryptedDATAStack** is as simple as just calling `drop`. 135 | 136 | **Swift** 137 | ```swift 138 | self.encryptedDataStack.drop() 139 | ``` 140 | 141 | **Objective-C** 142 | ```objc 143 | [self.encryptedDataStack forceDrop]; 144 | ``` 145 | 146 | ## Testing 147 | 148 | **EncryptedDATAStack** is optimized for unit testing and it runs synchronously in testing enviroments. Hopefully you'll have to use less XCTestExpectations now. 149 | 150 | You can create a stack that uses in memory store like this if your Core Data model is located in your app bundle: 151 | 152 | **Swift** 153 | ```swift 154 | let encryptedDataStack = EncryptedDATAStack(modelName: "MyAppModel", bundle: NSBundle.mainBundle(), storeType: .InMemory) 155 | ``` 156 | 157 | **Objective-C** 158 | ```objc 159 | EncryptedDATAStack *encryptedDataStack = [[EncryptedDATAStack alloc] initWithModelName:@"MyAppModel" 160 | bundle:[NSBundle mainBundle] 161 | storeType:EncryptedDATAStackStoreTypeInMemory]; 162 | ``` 163 | 164 | If your Core Data model is located in your test bundle: 165 | 166 | **Swift** 167 | ```swift 168 | let encryptedDataStack = EncryptedDATAStack(modelName: "MyAppModel", bundle: NSBundle(forClass: Tests.self), storeType: .InMemory) 169 | ``` 170 | 171 | **Objective-C** 172 | ```objc 173 | EncryptedDATAStack *encryptedDataStack = [[EncryptedDATAStack alloc] initWithModelName:@"MyAppModel" 174 | bundle:[NSBundle bundleForClass:[self class]] 175 | storeType:EncryptedDATAStackStoreTypeInMemory]; 176 | ``` 177 | 178 | ## Migrations 179 | 180 | 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. 181 | 182 | 183 | ## Installation 184 | 185 | **EncryptedDATAStack** is available through [CocoaPods](http://cocoapods.org). To install it, simply add the following line to your Podfile: 186 | 187 | ```ruby 188 | use_frameworks! 189 | 190 | pod 'EncryptedDATAStack', :git => 'https://github.com/flipacholas/EncryptedDATAStack.git' 191 | pod 'EncryptedCoreData', :git => 'https://github.com/project-imas/encrypted-core-data' 192 | ``` 193 | 194 | ## Possible bugs 195 | 196 | The latest version might not be suitable for some particular tasks, if you are having problems try the 2.0 branch: 197 | 198 | ```ruby 199 | use_frameworks! 200 | 201 | pod 'EncryptedDATAStack', '~> 2.0.0' 202 | ``` 203 | 204 | ## Improvements 205 | 206 | You can open issues, pull requests... I will be happy to help 207 | 208 | ## Author 209 | 210 | Rodrigo Copetti, [@flipacholas](https://twitter.com/flipacholas) 211 | 212 | Special thanks to: 213 | 214 | Elvis Nuñez, [@3lvis](https://twitter.com/3lvis) and Project iMAS, [project-imas](https://github.com/project-imas) 215 | 216 | ## License 217 | 218 | **EncryptedDATAStack** is available under the Affero GPL v3 Licence. 219 | 220 | ...Just kidding, MIT license for you. See the LICENSE file for more info. 221 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------