├── .gitignore ├── .gitmodules ├── .travis.yml ├── EncryptedCoreData.podspec ├── Incremental Store ├── EncryptedStore.h └── EncryptedStore.m ├── LICENSE ├── README.md ├── diagram.jpg ├── exampleProjects ├── FailedBank │ ├── FailedBankCD.xcodeproj │ │ └── project.pbxproj │ ├── FailedBankCD │ │ ├── FBCDAppDelegate.h │ │ ├── FBCDAppDelegate.m │ │ ├── FBCDMasterViewController.h │ │ ├── FBCDMasterViewController.m │ │ ├── FailedBankCD-Info.plist │ │ ├── FailedBankCD-Prefix.pch │ │ ├── FailedBankCD.xcdatamodeld │ │ │ ├── .xccurrentversion │ │ │ └── FailedBankCD.xcdatamodel │ │ │ │ └── contents │ │ ├── FailedBankDetails.h │ │ ├── FailedBankDetails.m │ │ ├── FailedBankInfo.h │ │ ├── FailedBankInfo.m │ │ ├── SMBankDetailViewController.h │ │ ├── SMBankDetailViewController.m │ │ ├── SMBankDetailViewController.xib │ │ ├── SMSearchViewControllerViewController.h │ │ ├── SMSearchViewControllerViewController.m │ │ ├── SMSearchViewControllerViewController.xib │ │ ├── SMTagListViewController.h │ │ ├── SMTagListViewController.m │ │ ├── SMTagListViewController.xib │ │ ├── Tag.h │ │ ├── Tag.m │ │ ├── en.lproj │ │ │ ├── InfoPlist.strings │ │ │ └── MainStoryboard.storyboard │ │ └── main.m │ └── README └── IncrementalStore │ ├── ClassModel.xcdatamodeld │ └── ClassModel.xcdatamodel │ │ └── contents │ ├── ClassModels │ ├── ISDChildA.h │ ├── ISDChildA.m │ ├── ISDChildB.h │ ├── ISDChildB.m │ ├── ISDModelCategories.h │ ├── ISDModelCategories.m │ ├── ISDParent.h │ ├── ISDParent.m │ ├── ISDRoot.h │ └── ISDRoot.m │ ├── Incremental Store Demo │ ├── ISDAppDelegate.h │ ├── ISDAppDelegate.m │ ├── ISDEditPostViewController.h │ ├── ISDEditPostViewController.m │ ├── ISDPostListViewController.h │ ├── ISDPostListViewController.m │ ├── ISDUserListViewController.h │ ├── ISDUserListViewController.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Incremental Store Demo-Info.plist │ ├── Incremental Store Demo-Prefix.pch │ ├── en.lproj │ │ ├── InfoPlist.strings │ │ └── MainStoryboard.storyboard │ └── main.m │ ├── Incremental Store.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── Incremental Store Demo.xcscheme │ ├── Migration.xcdatamodeld │ ├── .xccurrentversion │ ├── Migration.xcdatamodel │ │ └── contents │ └── New.xcdatamodel │ │ └── contents │ ├── Model.xcdatamodeld │ └── Model.xcdatamodel │ │ └── contents │ ├── SubEntitiesModel.xcdatamodeld │ └── SubEntitiesModel.xcdatamodel │ │ └── contents │ └── Tests │ ├── ISDMigrationTests.m │ ├── IncrementalStoreTests.m │ ├── PasswordTests.m │ ├── RelationTests.m │ ├── SubEntityTests.m │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ └── en.lproj │ └── InfoPlist.strings └── stringOutput.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | build/* 3 | *.pbxuser 4 | !default.pbxuser 5 | *.mode1v3 6 | !default.mode1v3 7 | *.mode2v3 8 | !default.mode2v3 9 | *.perspectivev3 10 | !default.perspectivev3 11 | *.xcworkspace 12 | !default.xcworkspace 13 | xcuserdata 14 | profile 15 | *.moved-aside 16 | 17 | # Finder 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "vendor/sqlcipher"] 2 | path = vendor/sqlcipher 3 | url = https://github.com/sqlcipher/sqlcipher.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode8.1 3 | 4 | before_install: 5 | - gpg2 --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 6 | - rvm get head 7 | # - brew update 8 | # - if brew outdated | grep -qx xctool; then brew upgrade xctool; fi 9 | 10 | script: 11 | - pushd vendor/sqlcipher 12 | - git submodule init 13 | - git submodule update 14 | - ./configure 15 | - make sqlite3.c 16 | - popd 17 | - pushd vendor/sqlcipher/sqlcipher.xcodeproj 18 | - cp project.pbxproj proj.tmp 19 | - sed 's/ CFLAGS=../ CFLAGS=\\"-miphoneos-version-min=6.0 /' proj.tmp > project.pbxproj 20 | - popd 21 | - cd exampleProjects/IncrementalStore 22 | # 32-bit tests 23 | - set -o pipefail && xcodebuild -sdk iphonesimulator -scheme "Incremental Store Demo" build test -destination "platform=iOS Simulator,name=iPhone 5" | xcpretty 24 | # 64-bit tests 25 | - set -o pipefail && xcodebuild -sdk iphonesimulator -scheme "Incremental Store Demo" build test -destination "platform=iOS Simulator,name=iPhone 5s" | xcpretty 26 | - sleep 20 27 | 28 | -------------------------------------------------------------------------------- /EncryptedCoreData.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'EncryptedCoreData' 3 | s.version = '3.1' 4 | s.license = 'Apache-2.0' 5 | 6 | s.summary = 'iOS Core Data encrypted SQLite store using SQLCipher' 7 | s.description = %[ 8 | Provides a Core Data store that encrypts all data that is persisted. Besides the initial setup, the usage is exactly the same as Core Data and can be used in existing projects that use Core Data. 9 | ] 10 | s.homepage = 'https://github.com/project-imas/encrypted-core-data/' 11 | s.authors = { 12 | 'MITRE' => 'imas-proj-list@lists.mitre.org' 13 | } 14 | 15 | s.source = { :git => 'https://github.com/project-imas/encrypted-core-data.git', :tag => '3.1' } 16 | 17 | s.frameworks = ['CoreData', 'Security'] 18 | s.requires_arc = true 19 | 20 | s.ios.deployment_target = '8.0' 21 | s.osx.deployment_target = '10.10' 22 | s.source_files = 'Incremental Store/**/*.{h,m}' 23 | s.public_header_files = 'Incremental Store/EncryptedStore.h' 24 | 25 | s.dependency 'SQLCipher', '~> 3.4.0' 26 | 27 | s.xcconfig = { 28 | 'OTHER_CFLAGS' => '$(inherited) -DSQLITE_HAS_CODEC -DSQLCIPHER_CRYPTO_CC' 29 | } 30 | end 31 | -------------------------------------------------------------------------------- /Incremental Store/EncryptedStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // EncryptedStore.h 3 | // 4 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 5 | // 6 | // 7 | 8 | #import 9 | 10 | typedef struct _options { 11 | char * passphrase; 12 | char * database_location; 13 | int * cache_size; 14 | } EncryptedStoreOptions; 15 | 16 | extern NSString * const EncryptedStoreType; 17 | extern NSString * const EncryptedStorePassphraseKey; 18 | extern NSString * const EncryptedStoreErrorDomain; 19 | extern NSString * const EncryptedStoreErrorMessageKey; 20 | extern NSString * const EncryptedStoreDatabaseLocation; 21 | extern NSString * const EncryptedStoreCacheSize; 22 | extern NSString * const EncryptedStoreFileManagerOption; 23 | typedef NS_ENUM(NSInteger, EncryptedStoreError) 24 | { 25 | EncryptedStoreErrorIncorrectPasscode = 6000, 26 | EncryptedStoreErrorMigrationFailed 27 | }; 28 | 29 | @interface EncryptedStoreFileManagerConfiguration : NSObject 30 | #pragma mark - Initialization 31 | - (instancetype)initWithOptions:(NSDictionary *)options; 32 | #pragma mark - Properties 33 | @property (nonatomic, readwrite) NSFileManager *fileManager; 34 | @property (nonatomic, readwrite) NSBundle *bundle; 35 | @property (nonatomic, readwrite) NSString *databaseName; 36 | @property (nonatomic, readwrite) NSString *databaseExtension; 37 | @property (nonatomic, readonly) NSString *databaseFilename; 38 | @property (nonatomic, readwrite) NSURL *databaseURL; 39 | @end 40 | 41 | @interface EncryptedStoreFileManagerConfiguration (OptionsKeys) 42 | + (NSString *)optionFileManager; 43 | + (NSString *)optionBundle; 44 | + (NSString *)optionDatabaseName; 45 | + (NSString *)optionDatabaseExtension; 46 | + (NSString *)optionDatabaseURL; 47 | @end 48 | 49 | @interface EncryptedStoreFileManager : NSObject 50 | #pragma mark - Initialization 51 | + (instancetype)defaultManager; 52 | - (instancetype)initWithConfiguration:(EncryptedStoreFileManagerConfiguration *)configuration; 53 | 54 | #pragma mark - Setup 55 | - (void)setupDatabaseWithOptions:(NSDictionary *)options error:(NSError * __autoreleasing*)error; 56 | 57 | #pragma mark - Getters 58 | @property (nonatomic, readwrite) EncryptedStoreFileManagerConfiguration *configuration; 59 | @property (nonatomic, readonly) NSURL *databaseURL; 60 | @end 61 | 62 | @interface EncryptedStoreFileManager (FileManagerExtensions) 63 | @property (nonatomic, readonly) NSURL *applicationSupportURL; 64 | - (void)setAttributes:(NSDictionary *)attributes ofItemAtURL:(NSURL *)url error:(NSError * __autoreleasing*)error; 65 | @end 66 | 67 | @interface EncryptedStore : NSIncrementalStore 68 | #pragma mark - Initialization 69 | + (NSPersistentStoreCoordinator *)makeStoreWithOptions:(NSDictionary *)options managedObjectModel:(NSManagedObjectModel *)objModel; 70 | + (NSPersistentStoreCoordinator *)makeStoreWithStructOptions:(EncryptedStoreOptions *) options managedObjectModel:(NSManagedObjectModel *)objModel; 71 | + (NSPersistentStoreCoordinator *)makeStore:(NSManagedObjectModel *) objModel 72 | passcode:(NSString *) passcode; 73 | 74 | //+ (NSPersistentStoreCoordinator *)makeStoreWithOptions:(NSDictionary *)options managedObjectModel:(NSManagedObjectModel *)objModel error:(NSError * __autoreleasing*)error; 75 | + (NSPersistentStoreCoordinator *)makeStoreWithStructOptions:(EncryptedStoreOptions *) options managedObjectModel:(NSManagedObjectModel *)objModel error:(NSError * __autoreleasing*)error; 76 | + (NSPersistentStoreCoordinator *)makeStore:(NSManagedObjectModel *) objModel 77 | passcode:(NSString *) passcode error:(NSError * __autoreleasing*)error; 78 | 79 | #pragma mark - Passphrase manipulation 80 | #pragma mark - Public 81 | 82 | /** 83 | @discussion Check old passphrase and if success change old passphrase to new passphrase. 84 | 85 | @param oldPassphrase The old passhrase with which database was previously opened. 86 | @param newPassphrase The new passhrase which is desired for database. 87 | @param error Inout error. 88 | @return The status of operation. 89 | */ 90 | - (BOOL)checkAndChangeDatabasePassphrase:(NSString *)oldPassphrase toNewPassphrase:(NSString *)newPassphrase error:(NSError *__autoreleasing*)error; 91 | 92 | 93 | /** 94 | @discussion Check database passphrase. 95 | 96 | @param passphrase The desired passphrase to test for. 97 | @param error Inout error. 98 | @return The status of operation. 99 | */ 100 | - (BOOL)checkDatabasePassphrase:(NSString *)passphrase error:(NSError *__autoreleasing*)error; 101 | 102 | #pragma mark - Internal 103 | 104 | /** 105 | @brief Configure database with passhrase. 106 | 107 | @discussion Configure database with passphrase stored in options dictionary. 108 | 109 | @attention Internal usage. 110 | 111 | @pre (error != NULL) 112 | 113 | @param error Inout error. 114 | @return The status of operation. 115 | */ 116 | - (BOOL)configureDatabasePassphrase:(NSError *__autoreleasing*)error; 117 | 118 | /** 119 | @brief Test database connection against simple sql request. 120 | @discussion Test database connection against simple sql request. Success means database open state and correctness of previous passphrase manipulation operation. 121 | 122 | @attention Internal usage. 123 | 124 | @pre (error != NULL) 125 | 126 | @param error Inout error. 127 | @return The status of operation. 128 | */ 129 | - (BOOL)checkDatabaseStatusWithError:(NSError *__autoreleasing*)error; 130 | 131 | 132 | /** 133 | @brief 134 | Primitive change passphrase operation. 135 | 136 | @discussion Ignores database state and tries to change database passphrase. 137 | Behaviour is unknown if used before old passphrase validation. 138 | 139 | @attention Internal usage. 140 | 141 | @pre (error != NULL) 142 | 143 | @param passphrase The new passphrase. 144 | @param error Inout error. 145 | @return The status of operation. 146 | */ 147 | - (BOOL)changeDatabasePassphrase:(NSString *)passphrase error:(NSError *__autoreleasing*)error; 148 | 149 | 150 | /** 151 | @brief Primitive set passphrase operation. 152 | 153 | @discussion Ignores database state and tries to set database passphrase. 154 | One of first-call functions in database setup. 155 | 156 | @attention Internal usage. 157 | 158 | @pre (error != NULL) 159 | 160 | @param passphrase The desired first passphrase of database. 161 | @param error Inout error. 162 | @return The status of operation. 163 | */ 164 | - (BOOL)setDatabasePassphrase:(NSString *)passphrase error:(NSError *__autoreleasing*)error; 165 | 166 | 167 | /** 168 | @brief Validates database passphrase for correctness. 169 | 170 | @discussion Tries to reopen database on provided passphrase. 171 | Closes database and try to open in on provided passphrase. 172 | 173 | @warning Could close database connection ( look at an implementation for details ). 174 | 175 | @pre (error != NULL) 176 | 177 | @param passphrase The desired passphrase to validate. 178 | @param error Inout error. 179 | @return The status of operation. 180 | */ 181 | - (BOOL)validateDatabasePassphrase:(NSString *)passphrase error:(NSError *__autoreleasing*)error; 182 | 183 | /** 184 | @brief Primitive database change passphrase operation. 185 | 186 | @discussion Tries to open database on provided oldPassphrase and in success it tries to change passphrase to new passphrase. 187 | 188 | @attention Internal usage. 189 | 190 | @pre (error != NULL) 191 | 192 | @param oldPassphrase: The old passphrase. 193 | @param newPassphrase: The new passphrase. 194 | @param error: Inout error. 195 | @return The status of operation. 196 | */ 197 | - (BOOL)changeDatabasePassphrase:(NSString *)oldPassphrase toNewPassphrase:(NSString *)newPassphrase error:(NSError *__autoreleasing*)error; 198 | 199 | 200 | @end 201 | 202 | @interface EncryptedStore (Initialization) 203 | + (NSPersistentStoreCoordinator *)makeStoreWithOptions:(NSDictionary *)options managedObjectModel:(NSManagedObjectModel *)objModel error:(NSError * __autoreleasing*)error; 204 | + (NSPersistentStoreCoordinator *)coordinator:(NSPersistentStoreCoordinator *)coordinator byAddingStoreAtURL:(NSURL *)url configuration:(NSString *)configuration options:(NSDictionary *)options error:(NSError * __autoreleasing*)error; 205 | + (NSPersistentStoreDescription *)makeDescriptionWithOptions:(NSDictionary *)options configuration:(NSString *)configuration error:(NSError * __autoreleasing*)error API_AVAILABLE(macosx(10.12),ios(10.0),tvos(10.0),watchos(3.0)); 206 | @end 207 | 208 | @interface EncryptedStore (Configuration) 209 | //alias to options. 210 | @property (copy, nonatomic, readonly) NSDictionary *configurationOptions; 211 | @property (strong, nonatomic, readonly) EncryptedStoreFileManager *fileManager; 212 | @end 213 | 214 | @interface EncryptedStore (OptionsKeys) 215 | + (NSString *)optionType; 216 | + (NSString *)optionPassphraseKey; 217 | + (NSString *)optionErrorDomain; 218 | + (NSString *)optionErrorMessageKey; 219 | + (NSString *)optionDatabaseLocation; 220 | + (NSString *)optionCacheSize; 221 | + (NSString *)optionFileManager; 222 | @end 223 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 iMAS 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Encrypted Core Data SQLite Store [![Build Status](https://travis-ci.org/project-imas/encrypted-core-data.svg?branch=master)](https://travis-ci.org/project-imas/encrypted-core-data)[![analytics](http://www.google-analytics.com/collect?v=1&t=pageview&_s=1&dl=https%3A%2F%2Fgithub.com%2Fproject-imas%2Fencrypted-core-data&_u=MAC~&cid=1757014354.1393964045&tid=UA-38868530-1)]() 2 | 3 | 4 | Provides a Core Data store that encrypts all data that is persisted. Besides the initial setup, the usage is exactly the same as Core Data and can be used in existing projects that use Core Data. 5 | 6 | # Vulnerabilities Addressed 7 | 8 | 1. SQLite database is not encrypted, contents are in plain text 9 | - CWE-311: Missing Encryption of Sensitive Data 10 | 2. SQLite database file protected with 4 digit system passcode 11 | - CWE-326: Inadequate Encryption Strength 12 | - SRG-APP-000129-MAPP-000029 Severity-CAT II: The mobile application must implement automated mechanisms to enforce access control restrictions which are not provided by the operating system 13 | 14 | # Project Setup 15 | * When creating the project make sure **Use Core Data** is selected 16 | * Switch into your project's root directory and checkout the encrypted-core-data project code 17 | ``` 18 | cd ~/Documents/code/YourApp 19 | 20 | git clone https://github.com/project-imas/encrypted-core-data.git 21 | ``` 22 | * Click on the top level Project item and add files ("option-command-a") 23 | * Navigate to **encrypted-core-data**, highlight **Incremental Store**, and click **Add** 24 | 25 | * SQLCipher is added as a git submodule within ECD. A `git submodule init` and `git submodule update` should populate the sqlcipher submodule directory, where the `sqlcipher.xcodeproj` can be found and added to your project. 26 | * To use CommonCrypto with SQLCipher in Xcode: 27 | - add the compiler flags `-DSQLCIPHER_CRYPTO_CC` and `-DSQLITE_HAS_CODEC` under the sqlcipher project settings > Build Settings > Custom Compiler Flags > Other C Flags 28 | - Under your application's project settings > Build Phases, add `sqlcipher` to Target Dependencies, and `libsqlcipher.a` and `Security.framework` to Link Binary With Libraries. 29 | 30 | * _Note:_ Along with the move to CommonCrypto, we've updated the version of SQLCipher included as a submodule from v2.0.6 to v3.1.0. Databases created with v2.0.6 will not be able to be read directly by v3.1.0, and support for legacy database migration is not yet supported by ECD. 31 | 32 | # Installation via CocoaPod 33 | * If you don't already have CocoaPods installed, do `$ sudo gem install cocoapods` in your terminal. (See the [CocoaPods website](http://guides.cocoapods.org/using/getting-started.html#getting-started) for details.) 34 | * In your project directory, do `pod init` to create a Podfile. 35 | * Add `pod 'EncryptedCoreData', :git => 'https://github.com/project-imas/encrypted-core-data.git'` to your Podfile 36 | * Run `pod install` 37 | * In your application delegate source file (AppDelegate.m), add `#import "EncryptedStore.h"` 38 | 39 | # Using EncryptedStoreFileManager 40 | In case of strong coupling with file system functions and others default conventions FileManager was introduced. 41 | 42 | Have a look at components: 43 | 44 | * EncryptedStoreFileManagerConfiguration 45 | * EncryptedStoreFileManager 46 | 47 | Various options are stored in Configuration. 48 | 49 | And FileManager could be passed to all functions as an option. 50 | 51 | ``` 52 | NSDictionary *options = @{ EncryptedStore.optionFileManager : fileManager }; 53 | ``` 54 | 55 | However, it should solve some dirty hacks. 56 | Let's try. 57 | 58 | ## Database lives in different bundle. 59 | 60 | ``` 61 | NSBundle *bundle = [NSBundle bundleWithIdentifier:"com.opensource.database_framework"]; 62 | EncryptedStoreFileManagerConfiguration *configuration = [EncryptedStoreFileManagerConfiguration new]; 63 | configuration.bundle = bundle; 64 | 65 | // or 66 | [[EncryptedStoreFileManagerConfiguration alloc] initWithOptions: @{EncryptedStoreFileManagerConfiguration.optionBundle : bundle}]; 67 | 68 | // next, you need to bypassing configuration to setup store. 69 | EncryptedStoreFileManager *fileManager = [[EncryptedStoreFileManager alloc] initWithConfiguration:configuration]; 70 | NSDictionary *options = @{ EncryptedStore.optionFileManager : fileManager }; 71 | ``` 72 | 73 | ## Complex setup and file system methods separation. 74 | 75 | By default, database file (sqlite) is stored on disk in Application Support Directory. 76 | But you can configure file extension, file name and file url in `EncryptedStoreFileManagerConfiguration`. 77 | 78 | ## Apply attributes to database file. 79 | In general, this functionality is not needed. 80 | It is a part of setup core data stack process. 81 | 82 | ## Configure persistentContainer 83 | `NSPersistentContainer` uses NSPersistentStoreDescriptions to configure stores. 84 | 85 | ``` 86 | NSManagedObjectModel *model = [NSManagedObjectModel new]; 87 | NSPersistentContainer *container = [[NSPersistentContainer alloc] initWithName:@"abc" managedObjectModel:model]; 88 | NSDictionary *options = @{ 89 | self.optionPassphraseKey : @"123", 90 | self.optionFileManager : [EncryptedStoreFileManager defaultManager] 91 | }; 92 | NSPersistentStoreDescription *description = [self makeDescriptionWithOptions:options configuration:nil error:nil]; 93 | 94 | container.persistentStoreDescriptions = @[description]; 95 | 96 | [container loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *description, NSError * error) { 97 | if (error) { 98 | NSLog(@"error! %@", error); 99 | } 100 | }]; 101 | ``` 102 | 103 | But if you wish: 104 | 105 | ``` 106 | EncryptedStore *store = // retrieve store from coordinator. 107 | 108 | // set database file attributes 109 | NSDictionary *attributes = // set attributes 110 | NSError *error = nil; 111 | [store.fileManager setAttributes:attributes ofItemAtURL:store.fileManager.databaseURL error:&error]; 112 | 113 | // inspect bundle 114 | store.fileManager.configuration.bundle; 115 | ``` 116 | 117 | 118 | # Using EncryptedStore 119 | 120 | EncryptedStore is known to work successfully on iOS versions 6.0 through 9.2. 121 | 122 | If you wish to set a custom cache size and/or custom database URL: 123 | create an NSDictionary to set the options for your EncryptedStore, replacing customPasscode, customCacheSize, and/or customDatabaseURL: 124 | ```objc 125 | NSDictionary *options = @{ EncryptedStorePassphraseKey: (NSString *) customPasscode, 126 | EncryptedStoreCacheSize: (NSNumber *) customCacheSize, 127 | EncryptedStoreDatabaseLocation: (NSURL *) customDatabaseURL 128 | }; 129 | ``` 130 | 131 | In your application delegate source file (i.e. AppDelegate.m) you should see 132 | ```objc 133 | NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; 134 | ``` 135 | If you created an NSDictionary with custom options, replace that line with 136 | ```objc 137 | NSPersistentStoreCoordinator *coordinator = [EncryptedStore makeStoreWithOptions:options managedObjectModel:[self managedObjectModel]]; 138 | ``` 139 | 140 | Otherwise, replace that line with: 141 | ```objc 142 | NSPersistentStoreCoordinator *coordinator = [EncryptedStore makeStore:[self managedObjectModel]:@"SOME_PASSCODE"]; 143 | ``` 144 | making sure to replace "SOME_PASSCODE" with a passcode of your own. 145 | 146 | Also in the same file add an import for EncryptedStore.h: 147 | ```objc 148 | #import "EncryptedStore.h" 149 | ``` 150 | 151 | If there are issues you can add `-com.apple.CoreData.SQLDebug 1` to see all statements encryted-cored-data generates be logged. 152 | 153 | # Features 154 | 155 | - One-to-one relationships 156 | - One-to-many relationships 157 | - Many-to-Many relationships (NEW) 158 | - Predicates 159 | - Inherited entities 160 | 161 | Missing features and known bugs are maintained on the [issue tracker](https://github.com/project-imas/encrypted-core-data/issues?state=open) 162 | 163 | # Diagram 164 | 165 | Below is a diagram showing the differences between NSSQLiteStore and EncryptedStore. Note that actual the SQLite calls are coupled fairly strongly with the layer wrapping it: 166 | 167 | 168 | 169 | # Strings Comparison 170 | 171 | Below is the output of doing the unix *strings* command on a sample applications .sqlite file. As you can see, the default persistence store leaves all information in plaintext: 172 | 173 | 174 | 175 | ## License 176 | 177 | Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 178 | 179 | Licensed under the Apache License, Version 2.0 (the "License"); 180 | you may not use this work except in compliance with the License. 181 | You may obtain a copy of the License at 182 | 183 | http://www.apache.org/licenses/LICENSE-2.0 184 | 185 | Unless required by applicable law or agreed to in writing, software 186 | distributed under the License is distributed on an "AS IS" BASIS, 187 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 188 | See the License for the specific language governing permissions and 189 | limitations under the License. 190 | 191 | -------------------------------------------------------------------------------- /diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-imas/encrypted-core-data/b97ffaf2f19dad4d1558bc9b0668cc2e09d17347/diagram.jpg -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FBCDAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // FBCDAppDelegate.h 3 | // FailedBankCD 4 | // 5 | // Created by Adam Burkepile on 3/23/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FBCDAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext; 16 | @property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel; 17 | @property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator; 18 | 19 | - (void)saveContext; 20 | - (NSURL *)applicationDocumentsDirectory; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FBCDAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // FBCDAppDelegate.m 3 | // FailedBankCD 4 | // 5 | // Created by Adam Burkepile on 3/23/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "FBCDAppDelegate.h" 10 | #import "FBCDMasterViewController.h" 11 | #import "FailedBankInfo.h" 12 | #import "FailedBankDetails.h" 13 | #import "EncryptedStore.h" 14 | 15 | /* 16 | * USE_ENCRYPTED_STORE 17 | * 0 : Core Data 18 | * 1 : EncryptedStore makeStore:passcode: 19 | * 2 : EncryptedStore makeStoreWithOptions:managedObjectModel: 20 | * 3 : EncryptedStore makeStoreWithStructOptions:managedObjectModel: 21 | */ 22 | 23 | #define USE_ENCRYPTED_STORE 3 24 | 25 | @implementation FBCDAppDelegate 26 | 27 | @synthesize window = _window; 28 | @synthesize managedObjectContext = __managedObjectContext; 29 | @synthesize managedObjectModel = __managedObjectModel; 30 | @synthesize persistentStoreCoordinator = __persistentStoreCoordinator; 31 | 32 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 33 | { 34 | 35 | 36 | 37 | // NSError *error; 38 | // 39 | // NSFetchRequest *req = [[NSFetchRequest alloc] initWithEntityName:@"FailedBankDetails"]; 40 | // NSArray *fetched = [[self managedObjectContext] executeFetchRequest:req error:&error]; 41 | // NSDate *this = [[fetched lastObject] closeDate]; 42 | // 43 | // [req setPredicate:[NSPredicate predicateWithFormat:@"ANY closeDate < %@",this]]; 44 | // fetched = [[self managedObjectContext] executeFetchRequest:req error:&error]; 45 | // NSLog(@"%d---%@",[this timeIntervalSince1970] == [[[fetched lastObject] closeDate] timeIntervalSince1970],fetched); 46 | 47 | // Override point for customization after application launch. 48 | UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController; 49 | FBCDMasterViewController *controller = (FBCDMasterViewController *)navigationController.topViewController; 50 | controller.managedObjectContext = self.managedObjectContext; 51 | return YES; 52 | } 53 | 54 | - (void)applicationWillResignActive:(UIApplication *)application 55 | { 56 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 57 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 58 | } 59 | 60 | - (void)applicationDidEnterBackground:(UIApplication *)application 61 | { 62 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 63 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 64 | } 65 | 66 | - (void)applicationWillEnterForeground:(UIApplication *)application 67 | { 68 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 69 | } 70 | 71 | - (void)applicationDidBecomeActive:(UIApplication *)application 72 | { 73 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 74 | } 75 | 76 | - (void)applicationWillTerminate:(UIApplication *)application 77 | { 78 | // Saves changes in the application's managed object context before the application terminates. 79 | [self saveContext]; 80 | } 81 | 82 | - (void)saveContext 83 | { 84 | NSError *error = nil; 85 | NSManagedObjectContext *managedObjectContext = self.managedObjectContext; 86 | if (managedObjectContext != nil) { 87 | if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) { 88 | // Replace this implementation with code to handle the error appropriately. 89 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 90 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 91 | abort(); 92 | } 93 | } 94 | } 95 | 96 | #pragma mark - Core Data stack 97 | 98 | // Returns the managed object context for the application. 99 | // If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application. 100 | - (NSManagedObjectContext *)managedObjectContext 101 | { 102 | if (__managedObjectContext != nil) { 103 | return __managedObjectContext; 104 | } 105 | 106 | NSPersistentStoreCoordinator *coordinator; 107 | 108 | #if USE_ENCRYPTED_STORE == 1 109 | coordinator = [EncryptedStore makeStore:[self managedObjectModel] passcode:@"SOME_PASSWORD"]; 110 | #elif USE_ENCRYPTED_STORE == 2 111 | 112 | [[NSFileManager defaultManager] createDirectoryAtURL:[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] withIntermediateDirectories:NO attributes:nil error:nil]; 113 | 114 | NSURL *databaseURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:[NSString stringWithFormat:@"FailedBankCD.sqlite"]]; 115 | 116 | int cache = 2345; 117 | EncryptedStoreOptions options; 118 | options.passphrase = "SOME_PASSWORD"; 119 | options.database_location = (char*)[[databaseURL description] UTF8String]; 120 | options.cache_size = &cache; 121 | 122 | coordinator = [EncryptedStore makeStoreWithStructOptions:&options managedObjectModel:[self managedObjectModel]]; 123 | 124 | #elif USE_ENCRYPTED_STORE == 3 125 | 126 | [[NSFileManager defaultManager] createDirectoryAtURL:[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] withIntermediateDirectories:NO attributes:nil error:nil]; 127 | 128 | NSURL *databaseURL = [[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject] URLByAppendingPathComponent:[NSString stringWithFormat:@"FailedBankCD.sqlite"]]; 129 | 130 | coordinator = [EncryptedStore makeStoreWithOptions:@{ 131 | EncryptedStorePassphraseKey : @"SOME_PASSWORD", 132 | EncryptedStoreDatabaseLocation : [databaseURL description], 133 | EncryptedStoreCacheSize : @(2345)} 134 | managedObjectModel:[self managedObjectModel]]; 135 | #else 136 | coordinator = [self persistentStoreCoordinator]; 137 | #endif 138 | 139 | 140 | if (coordinator != nil) { 141 | __managedObjectContext = [[NSManagedObjectContext alloc] init]; 142 | [__managedObjectContext setPersistentStoreCoordinator:coordinator]; 143 | } 144 | return __managedObjectContext; 145 | } 146 | 147 | // Returns the managed object model for the application. 148 | // If the model doesn't already exist, it is created from the application's model. 149 | - (NSManagedObjectModel *)managedObjectModel 150 | { 151 | if (__managedObjectModel != nil) { 152 | return __managedObjectModel; 153 | } 154 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"FailedBankCD" withExtension:@"momd"]; 155 | __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 156 | return __managedObjectModel; 157 | } 158 | 159 | // Returns the persistent store coordinator for the application. 160 | // If the coordinator doesn't already exist, it is created and the application's store added to it. 161 | - (NSPersistentStoreCoordinator *)persistentStoreCoordinator 162 | { 163 | if (__persistentStoreCoordinator != nil) { 164 | return __persistentStoreCoordinator; 165 | } 166 | 167 | NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"FailedBankCD.sqlite"]; 168 | 169 | NSError *error = nil; 170 | __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; 171 | if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) { 172 | /* 173 | Replace this implementation with code to handle the error appropriately. 174 | 175 | abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 176 | 177 | Typical reasons for an error here include: 178 | * The persistent store is not accessible; 179 | * The schema for the persistent store is incompatible with current managed object model. 180 | Check the error message to determine what the actual problem was. 181 | 182 | 183 | If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory. 184 | 185 | If you encounter schema incompatibility errors during development, you can reduce their frequency by: 186 | * Simply deleting the existing store: 187 | [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil] 188 | 189 | * Performing automatic lightweight migration by passing the following dictionary as the options parameter: 190 | [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; 191 | 192 | Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details. 193 | 194 | */ 195 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 196 | abort(); 197 | } 198 | 199 | return __persistentStoreCoordinator; 200 | } 201 | 202 | #pragma mark - Application's Documents directory 203 | 204 | // Returns the URL to the application's Documents directory. 205 | - (NSURL *)applicationDocumentsDirectory 206 | { 207 | return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; 208 | } 209 | 210 | @end 211 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FBCDMasterViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FBCDMasterViewController.h 3 | // FailedBankCD 4 | // 5 | // Created by Adam Burkepile on 3/23/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SMSearchViewControllerViewController.h" 11 | #import "Tag.h" 12 | #import "SMBankDetailViewController.h" 13 | 14 | @interface FBCDMasterViewController : UITableViewController 15 | 16 | @property (nonatomic,strong) NSManagedObjectContext* managedObjectContext; 17 | @property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController; 18 | @end 19 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FBCDMasterViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FBCDMasterViewController.m 3 | // FailedBankCD 4 | // 5 | // Created by Adam Burkepile on 3/23/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "FBCDMasterViewController.h" 10 | #import "FailedBankInfo.h" 11 | #import "FailedBankDetails.h" 12 | 13 | @interface FBCDMasterViewController () 14 | - (void) populateDB; 15 | @end 16 | 17 | @implementation FBCDMasterViewController 18 | @synthesize managedObjectContext; 19 | @synthesize fetchedResultsController = _fetchedResultsController; 20 | 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | 26 | NSError *error; 27 | if (![[self fetchedResultsController] performFetch:&error]) { 28 | // Update to handle the error appropriately. 29 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 30 | exit(-1); // Fail 31 | } 32 | 33 | // if (self.fetchedResultsController.fetchedObjects.count != 0) { 34 | // 35 | // for (FailedBankInfo *info in self.fetchedResultsController.fetchedObjects) { 36 | // NSLog(@"%@", info.details.tags); 37 | // } 38 | // 39 | // } 40 | 41 | self.title = @"Failed Banks"; 42 | 43 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(showSearch)]; 44 | 45 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addBank)]; 46 | 47 | 48 | } 49 | 50 | - (void) populateDB { 51 | 52 | NSManagedObjectContext *context = [self managedObjectContext]; 53 | FailedBankInfo *failedBankInfo = [NSEntityDescription 54 | insertNewObjectForEntityForName:@"FailedBankInfo" 55 | inManagedObjectContext:context]; 56 | failedBankInfo.name = @"Bank"; 57 | failedBankInfo.city = @"Bankville"; 58 | failedBankInfo.state = @"Bankland"; 59 | 60 | 61 | 62 | 63 | FailedBankDetails *failedBankDetails = [NSEntityDescription 64 | insertNewObjectForEntityForName:@"FailedBankDetails" 65 | inManagedObjectContext:context]; 66 | failedBankDetails.closeDate = [NSDate date]; 67 | failedBankDetails.updateDate = [NSDate date]; 68 | failedBankDetails.zip = [NSNumber numberWithInt:123]; 69 | failedBankDetails.info = failedBankInfo; 70 | 71 | Tag *tag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" 72 | inManagedObjectContext:context]; 73 | tag.name = @"tag1"; 74 | [failedBankDetails addTagsObject:tag]; 75 | 76 | failedBankInfo.details = failedBankDetails; 77 | 78 | 79 | 80 | 81 | NSError *error; 82 | if (![context save:&error]) { 83 | NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]); 84 | } 85 | 86 | } 87 | 88 | - (void)viewDidUnload 89 | { 90 | [super viewDidUnload]; 91 | 92 | self.fetchedResultsController = nil; 93 | } 94 | 95 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 96 | { 97 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 98 | } 99 | 100 | #pragma mark - Actions 101 | 102 | - (void) addBank { 103 | 104 | FailedBankInfo *failedBankInfo = (FailedBankInfo *)[NSEntityDescription insertNewObjectForEntityForName:@"FailedBankInfo" 105 | inManagedObjectContext:managedObjectContext]; 106 | failedBankInfo.name = @"Test Bank"; 107 | failedBankInfo.city = @"Testville"; 108 | failedBankInfo.state = @"Testland"; 109 | 110 | FailedBankDetails *failedBankDetails = [NSEntityDescription insertNewObjectForEntityForName:@"FailedBankDetails" 111 | inManagedObjectContext:managedObjectContext]; 112 | failedBankDetails.closeDate = [NSDate date]; 113 | failedBankDetails.updateDate = [NSDate date]; 114 | failedBankDetails.zip = [NSNumber numberWithInt:123]; 115 | failedBankDetails.info = failedBankInfo; 116 | failedBankInfo.details = failedBankDetails; 117 | 118 | NSError *error = nil; 119 | if (![managedObjectContext save:&error]) { 120 | // Replace this implementation with code to handle the error appropriately. 121 | // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 122 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 123 | abort(); 124 | } 125 | 126 | 127 | } 128 | 129 | - (void) showSearch { 130 | 131 | SMSearchViewControllerViewController *searchViewController = [[SMSearchViewControllerViewController alloc] init]; 132 | searchViewController.managedObjectContext = managedObjectContext; 133 | [self.navigationController presentViewController:searchViewController animated:YES completion:nil]; 134 | 135 | } 136 | 137 | #pragma mark - Table view data source 138 | 139 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 140 | { 141 | return 1; 142 | } 143 | 144 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 145 | { 146 | // Return the number of rows in the section. 147 | id sectionInfo = 148 | [[_fetchedResultsController sections] objectAtIndex:section]; 149 | return [sectionInfo numberOfObjects]; 150 | } 151 | 152 | - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { 153 | FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath]; 154 | cell.textLabel.text = info.name; 155 | cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@", 156 | info.city, info.state]; 157 | } 158 | 159 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 160 | { 161 | static NSString *CellIdentifier = @"Cell"; 162 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 163 | 164 | if (!cell) { 165 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; 166 | } 167 | 168 | // Configure the cell... 169 | [self configureCell:cell atIndexPath:indexPath]; 170 | 171 | return cell; 172 | } 173 | 174 | 175 | // Override to support conditional editing of the table view. 176 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 177 | { 178 | return YES; 179 | } 180 | 181 | 182 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 183 | { 184 | if (editingStyle == UITableViewCellEditingStyleDelete) { 185 | 186 | [managedObjectContext deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]]; 187 | 188 | NSError *error = nil; 189 | if (![managedObjectContext save:&error]) { 190 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 191 | abort(); 192 | } 193 | } 194 | } 195 | 196 | 197 | #pragma mark - Table view delegate 198 | 199 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 200 | 201 | FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath]; 202 | SMBankDetailViewController *detailViewController = [[SMBankDetailViewController alloc] initWithBankInfo:info]; 203 | [self.navigationController pushViewController:detailViewController animated:YES]; 204 | } 205 | 206 | #pragma mark - fetchedResultsController 207 | 208 | - (NSFetchedResultsController *)fetchedResultsController { 209 | 210 | if (_fetchedResultsController != nil) { 211 | return _fetchedResultsController; 212 | } 213 | 214 | NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 215 | NSEntityDescription *entity = [NSEntityDescription 216 | entityForName:@"FailedBankInfo" inManagedObjectContext:managedObjectContext]; 217 | [fetchRequest setEntity:entity]; 218 | 219 | NSSortDescriptor *sort = [[NSSortDescriptor alloc] 220 | initWithKey:@"details.closeDate" ascending:NO]; 221 | [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]]; 222 | 223 | [fetchRequest setFetchBatchSize:20]; 224 | 225 | NSFetchedResultsController *theFetchedResultsController = 226 | [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 227 | managedObjectContext:managedObjectContext sectionNameKeyPath:nil 228 | cacheName:@"Root"]; 229 | self.fetchedResultsController = theFetchedResultsController; 230 | _fetchedResultsController.delegate = self; 231 | 232 | return _fetchedResultsController; 233 | 234 | } 235 | 236 | - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { 237 | // The fetch controller is about to start sending change notifications, so prepare the table view for updates. 238 | [self.tableView beginUpdates]; 239 | } 240 | 241 | 242 | - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 243 | 244 | UITableView *tableView = self.tableView; 245 | 246 | switch(type) { 247 | 248 | case NSFetchedResultsChangeInsert: 249 | [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 250 | break; 251 | 252 | case NSFetchedResultsChangeDelete: 253 | [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 254 | break; 255 | 256 | case NSFetchedResultsChangeUpdate: 257 | [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; 258 | break; 259 | 260 | case NSFetchedResultsChangeMove: 261 | [tableView deleteRowsAtIndexPaths:[NSArray 262 | arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 263 | [tableView insertRowsAtIndexPaths:[NSArray 264 | arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 265 | break; 266 | } 267 | } 268 | 269 | 270 | - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { 271 | 272 | switch(type) { 273 | 274 | case NSFetchedResultsChangeInsert: 275 | [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 276 | break; 277 | 278 | case NSFetchedResultsChangeDelete: 279 | [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; 280 | break; 281 | } 282 | } 283 | 284 | 285 | - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { 286 | // The fetch controller has sent all current change notifications, so tell the table view to process all updates. 287 | [self.tableView endUpdates]; 288 | } 289 | 290 | @end 291 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FailedBankCD-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | com.adamburkepile.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | MainStoryboard 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FailedBankCD-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'FailedBankCD' target in the 'FailedBankCD' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #import 15 | #endif 16 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FailedBankCD.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | FailedBankCD.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FailedBankCD.xcdatamodeld/FailedBankCD.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FailedBankDetails.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @class FailedBankInfo, Tag; 5 | 6 | @interface FailedBankDetails : NSManagedObject 7 | 8 | @property (nonatomic, retain) NSDate * closeDate; 9 | @property (nonatomic, retain) NSDate * updateDate; 10 | @property (nonatomic, retain) NSNumber * zip; 11 | @property (nonatomic, retain) FailedBankInfo *info; 12 | @property (nonatomic, retain) NSSet *tags; 13 | @end 14 | 15 | @interface FailedBankDetails (CoreDataGeneratedAccessors) 16 | 17 | - (void)addTagsObject:(Tag *)value; 18 | - (void)removeTagsObject:(Tag *)value; 19 | - (void)addTags:(NSSet *)values; 20 | - (void)removeTags:(NSSet *)values; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FailedBankDetails.m: -------------------------------------------------------------------------------- 1 | #import "FailedBankDetails.h" 2 | #import "FailedBankInfo.h" 3 | #import "Tag.h" 4 | 5 | 6 | @implementation FailedBankDetails 7 | 8 | @dynamic closeDate; 9 | @dynamic updateDate; 10 | @dynamic zip; 11 | @dynamic info; 12 | @dynamic tags; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FailedBankInfo.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @class FailedBankDetails; 5 | 6 | @interface FailedBankInfo : NSManagedObject 7 | 8 | @property (nonatomic, retain) NSString * city; 9 | @property (nonatomic, retain) NSString * name; 10 | @property (nonatomic, retain) NSString * state; 11 | @property (nonatomic, retain) FailedBankDetails *details; 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/FailedBankInfo.m: -------------------------------------------------------------------------------- 1 | #import "FailedBankInfo.h" 2 | #import "FailedBankDetails.h" 3 | 4 | 5 | @implementation FailedBankInfo 6 | 7 | @dynamic city; 8 | @dynamic name; 9 | @dynamic state; 10 | @dynamic details; 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMBankDetailViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SMBankDetailViewController.h 3 | // FailedBankCD 4 | // 5 | // Created by cesarerocchi on 5/24/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FailedBankInfo.h" 11 | #import "FailedBankDetails.h" 12 | #import "Tag.h" 13 | #import "SMTagListViewController.h" 14 | 15 | @interface SMBankDetailViewController : UIViewController 16 | 17 | @property (nonatomic, strong) FailedBankInfo *bankInfo; 18 | @property (nonatomic, strong) IBOutlet UITextField *nameField; 19 | @property (nonatomic, strong) IBOutlet UITextField *cityField; 20 | @property (nonatomic, strong) IBOutlet UITextField *zipField; 21 | @property (nonatomic, strong) IBOutlet UITextField *stateField; 22 | @property (nonatomic, strong) IBOutlet UILabel *tagsLabel; 23 | @property (nonatomic, strong) IBOutlet UILabel *dateLabel; 24 | @property (nonatomic, strong) IBOutlet UIDatePicker *datePicker; 25 | 26 | 27 | - (id) initWithBankInfo:(FailedBankInfo *) info; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMBankDetailViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SMBankDetailViewController.m 3 | // FailedBankCD 4 | // 5 | // Created by cesarerocchi on 5/24/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "SMBankDetailViewController.h" 10 | 11 | @interface SMBankDetailViewController () 12 | - (void) hidePicker; 13 | - (void) showPicker; 14 | @end 15 | 16 | @implementation SMBankDetailViewController 17 | 18 | @synthesize bankInfo = _bankInfo; 19 | @synthesize nameField; 20 | @synthesize cityField; 21 | @synthesize zipField; 22 | @synthesize stateField; 23 | @synthesize tagsLabel; 24 | @synthesize dateLabel; 25 | @synthesize datePicker; 26 | 27 | - (id) initWithBankInfo:(FailedBankInfo *) info { 28 | 29 | if (self = [super init]) { 30 | 31 | _bankInfo = info; 32 | 33 | } 34 | 35 | return self; 36 | 37 | } 38 | 39 | - (void)viewDidLoad { 40 | 41 | [super viewDidLoad]; 42 | 43 | self.title = self.bankInfo.name; 44 | 45 | // setting the right button 46 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:@selector(saveBankInfo)]; 47 | 48 | // setting interaction on tag label 49 | self.tagsLabel.userInteractionEnabled = YES; 50 | UITapGestureRecognizer *tagsTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self 51 | action:@selector(tagsTapped)]; 52 | [self.tagsLabel addGestureRecognizer:tagsTapRecognizer]; 53 | 54 | // setting interaction on date label 55 | self.dateLabel.userInteractionEnabled = YES; 56 | UITapGestureRecognizer *dateTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self 57 | action:@selector(dateTapped)]; 58 | [self.dateLabel addGestureRecognizer:dateTapRecognizer]; 59 | 60 | // setting labels background 61 | self.tagsLabel.backgroundColor = self.dateLabel.backgroundColor = [UIColor lightGrayColor]; 62 | 63 | 64 | [datePicker addTarget:self 65 | action:@selector(dateHasChanged:) 66 | forControlEvents:UIControlEventValueChanged]; 67 | 68 | 69 | 70 | } 71 | 72 | - (void)dateHasChanged:(id) sender { 73 | 74 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 75 | [formatter setDateStyle:NSDateFormatterMediumStyle]; 76 | self.dateLabel.text = [formatter stringFromDate:self.datePicker.date]; 77 | 78 | } 79 | 80 | - (void) viewWillAppear:(BOOL)animated { 81 | 82 | [super viewWillAppear:animated]; 83 | 84 | // setting values to fields 85 | self.nameField.text = self.bankInfo.name; 86 | self.cityField.text = self.bankInfo.city; 87 | self.zipField.text = [self.bankInfo.details.zip stringValue]; 88 | self.stateField.text = self.bankInfo.state; 89 | NSArray *tags = [self.bankInfo.details.tags allObjects]; 90 | NSMutableArray *tagNamesArray = [[NSMutableArray alloc] initWithCapacity:tags.count]; 91 | 92 | for (Tag *tag in tags) { 93 | [tagNamesArray addObject:tag.name]; 94 | } 95 | 96 | self.tagsLabel.text = [tagNamesArray componentsJoinedByString:@","]; 97 | 98 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 99 | [formatter setDateStyle:NSDateFormatterMediumStyle]; 100 | self.dateLabel.text = [formatter stringFromDate:self.bankInfo.details.closeDate]; 101 | 102 | } 103 | 104 | #pragma mark - Actions 105 | 106 | - (void) tagsTapped { 107 | 108 | SMTagListViewController *tagPicker = [[SMTagListViewController alloc] initWithBankDetails:self.bankInfo.details]; 109 | [self.navigationController pushViewController:tagPicker 110 | animated:YES]; 111 | } 112 | 113 | - (void) dateTapped { 114 | 115 | [self showPicker]; 116 | 117 | } 118 | 119 | - (void) showPicker { 120 | 121 | [self.zipField resignFirstResponder]; 122 | [self.nameField resignFirstResponder]; 123 | [self.stateField resignFirstResponder]; 124 | [self.cityField resignFirstResponder]; 125 | 126 | [UIView beginAnimations:@"SlideInPicker" context:nil]; 127 | [UIView setAnimationDuration:0.5]; 128 | self.datePicker.transform = CGAffineTransformMakeTranslation(0, -216); 129 | [UIView commitAnimations]; 130 | 131 | } 132 | 133 | - (void) hidePicker { 134 | 135 | [UIView beginAnimations:@"SlideOutPicker" context:nil]; 136 | [UIView setAnimationDuration:0.5]; 137 | self.datePicker.transform = CGAffineTransformMakeTranslation(0, 216); 138 | [UIView commitAnimations]; 139 | 140 | } 141 | 142 | - (void) saveBankInfo { 143 | 144 | self.bankInfo.name = self.nameField.text; 145 | self.bankInfo.city = self.cityField.text; 146 | self.bankInfo.details.zip = [NSNumber numberWithInt:[self.zipField.text intValue]]; 147 | self.bankInfo.state = self.stateField.text; 148 | self.bankInfo.details.closeDate = self.datePicker.date; 149 | 150 | NSError *error; 151 | if ([self.bankInfo.managedObjectContext hasChanges] && ![self.bankInfo.managedObjectContext save:&error]) { 152 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 153 | abort(); 154 | } 155 | 156 | [self.navigationController popViewControllerAnimated:YES]; 157 | 158 | } 159 | 160 | - (void)viewDidUnload 161 | { 162 | [super viewDidUnload]; 163 | // Release any retained subviews of the main view. 164 | // e.g. self.myOutlet = nil; 165 | } 166 | 167 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 168 | { 169 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 170 | } 171 | 172 | @end 173 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMBankDetailViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | VFppZgAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAAAAAC5AAAABAAAABCepkign7sVkKCGKqChmveQ 73 | y4kaoNIj9HDSYSYQ1v50INiArZDa/tGg28CQENzes6DdqayQ3r6VoN+JjpDgnneg4WlwkOJ+WaDjSVKQ 74 | 5F47oOUpNJDmR1gg5xJREOgnOiDo8jMQ6gccIOrSFRDr5v4g7LH3EO3G4CDukdkQ76/8oPBxuxDxj96g 75 | 8n/BkPNvwKD0X6OQ9U+ioPY/hZD3L4Sg+CiiEPkPZqD6CIQQ+viDIPvoZhD82GUg/chIEP64RyD/qCoQ 76 | AJgpIAGIDBACeAsgA3EokARhJ6AFUQqQBkEJoAcw7JAHjUOgCRDOkAmtvyAK8LCQC+CvoAzZzRANwJGg 77 | DrmvEA+priAQmZEQEYmQIBJ5cxATaXIgFFlVEBVJVCAWOTcQFyk2IBgiU5AZCRggGgI1kBryNKAb4heQ 78 | HNIWoB3B+ZAesfigH6HbkCB2KyAhgb2QIlYNICNq2hAkNe8gJUq8ECYV0SAnKp4QJ/7toCkKgBAp3s+g 79 | KupiECu+saAs036QLZ6ToC6zYJAvfnWgMJNCkDFnkiAycySQM0d0IDRTBpA1J1YgNjLokDcHOCA4HAUQ 80 | OOcaIDn75xA6xvwgO9vJEDywGKA9u6sQPo/6oD+bjRBAb9ygQYSpkEJPvqBDZIuQRC+goEVEbZBF89Mg 81 | Ry2KEEfTtSBJDWwQSbOXIErtThBLnLOgTNZqkE18laBOtkyQT1x3oFCWLpBRPFmgUnYQkFMcO6BUVfKQ 82 | VPwdoFY11JBW5TogWB7xEFjFHCBZ/tMQWqT+IFvetRBchOAgXb6XEF5kwiBfnnkQYE3eoGGHlZBiLcCg 83 | Y2d3kGQNoqBlR1mQZe2EoGcnO5BnzWagaQcdkGmtSKBq5v+Qa5ZlIGzQHBBtdkcgbq/+EG9WKSBwj+AQ 84 | cTYLIHJvwhBzFe0gdE+kEHT/CaB2OMCQdt7roHgYopB4vs2gefiEkHqer6B72GaQfH6RoH24SJB+XnOg 85 | f5gqkAABAAECAwEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA 86 | AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA 87 | AQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEAAQABAAEA 88 | AQABAAEAAQAB//+dkAEA//+PgAAE//+dkAEI//+dkAEMUERUAFBTVABQV1QAUFBUAAAAAAEAAAABA 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMSearchViewControllerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SMSearchViewControllerViewController.h 3 | // FailedBankCD 4 | // 5 | // Created by cesarerocchi on 5/22/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FailedBankInfo.h" 11 | 12 | @interface SMSearchViewControllerViewController : UIViewController 13 | 14 | @property (nonatomic,strong) NSManagedObjectContext* managedObjectContext; 15 | @property (nonatomic,retain) NSFetchedResultsController *fetchedResultsController; 16 | 17 | @property (nonatomic, strong) IBOutlet UISearchBar *searchBar; 18 | @property (nonatomic, strong) IBOutlet UITableView *tView; 19 | 20 | @property (nonatomic, strong) UILabel *noResultsLabel; 21 | 22 | - (IBAction)closeSearch; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMSearchViewControllerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SMSearchViewControllerViewController.m 3 | // FailedBankCD 4 | // 5 | // Created by cesarerocchi on 5/22/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "SMSearchViewControllerViewController.h" 10 | 11 | 12 | 13 | @interface SMSearchViewControllerViewController () 14 | - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath; 15 | @end 16 | 17 | @implementation SMSearchViewControllerViewController 18 | 19 | @synthesize managedObjectContext; 20 | @synthesize fetchedResultsController = _fetchedResultsController; 21 | @synthesize searchBar,tView; 22 | @synthesize noResultsLabel; 23 | 24 | - (IBAction)closeSearch { 25 | 26 | [self dismissViewControllerAnimated:YES completion:nil]; 27 | 28 | } 29 | 30 | - (void)viewDidLoad 31 | { 32 | [super viewDidLoad]; 33 | self.searchBar.delegate = self; 34 | self.tView.delegate = self; 35 | self.tView.dataSource = self; 36 | 37 | noResultsLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 90, 200, 30)]; 38 | [self.view addSubview:noResultsLabel]; 39 | noResultsLabel.text = @"No Results"; 40 | [noResultsLabel setHidden:YES]; 41 | 42 | } 43 | 44 | - (void) viewWillAppear:(BOOL)animated { 45 | 46 | [super viewWillAppear:animated]; 47 | [self.searchBar becomeFirstResponder]; 48 | 49 | } 50 | 51 | #pragma mark - Search bar delegate 52 | 53 | - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar { 54 | 55 | NSError *error; 56 | 57 | if (![[self fetchedResultsController] performFetch:&error]) { 58 | 59 | NSLog(@"Error in search %@, %@", error, [error userInfo]); 60 | 61 | } else { 62 | 63 | [self.tView reloadData]; 64 | [self.searchBar resignFirstResponder]; 65 | 66 | [noResultsLabel setHidden:_fetchedResultsController.fetchedObjects.count > 0]; 67 | 68 | } 69 | 70 | } 71 | 72 | 73 | #pragma mark - Table view data source 74 | 75 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 76 | { 77 | return 1; 78 | } 79 | 80 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 81 | { 82 | id sectionInfo = 83 | [[_fetchedResultsController sections] objectAtIndex:section]; 84 | return [sectionInfo numberOfObjects]; 85 | 86 | } 87 | 88 | - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { 89 | FailedBankInfo *info = [_fetchedResultsController objectAtIndexPath:indexPath]; 90 | cell.textLabel.text = info.name; 91 | cell.detailTextLabel.text = [NSString stringWithFormat:@"%@, %@", 92 | info.city, info.state]; 93 | } 94 | 95 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 96 | { 97 | static NSString *CellIdentifier = @"Cell"; 98 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 99 | 100 | if (!cell) { 101 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier]; 102 | } 103 | 104 | [self configureCell:cell atIndexPath:indexPath]; 105 | 106 | return cell; 107 | } 108 | 109 | 110 | #pragma mark - fetchedResultsController 111 | 112 | // Change this value to experiment with different predicates 113 | #define SEARCH_TYPE 0 114 | 115 | 116 | - (NSFetchedResultsController *)fetchedResultsController { 117 | 118 | NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 119 | NSEntityDescription *entity = [NSEntityDescription 120 | entityForName:@"FailedBankInfo" inManagedObjectContext:managedObjectContext]; 121 | [fetchRequest setEntity:entity]; 122 | 123 | NSSortDescriptor *sort = [[NSSortDescriptor alloc] 124 | initWithKey:@"details.closeDate" ascending:NO]; 125 | [fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]]; 126 | [fetchRequest setFetchBatchSize:20]; 127 | 128 | NSArray *queryArray; 129 | 130 | if ([self.searchBar.text rangeOfString:@":"].location != NSNotFound) { 131 | 132 | queryArray = [self.searchBar.text componentsSeparatedByString:@":"]; 133 | 134 | } 135 | 136 | NSLog(@"search is %@", self.searchBar.text); 137 | 138 | NSPredicate *pred; 139 | 140 | switch (SEARCH_TYPE) { 141 | 142 | case 0: // name contains, case sensitive 143 | pred = [NSPredicate predicateWithFormat:@"name CONTAINS %@", self.searchBar.text]; 144 | break; 145 | 146 | case 1: // name contains, case insensitive 147 | pred = [NSPredicate predicateWithFormat:@"name CONTAINS[c] %@", self.searchBar.text]; 148 | break; 149 | 150 | case 2: // name is exactly the same 151 | pred = [NSPredicate predicateWithFormat:@"name == %@", self.searchBar.text]; 152 | break; 153 | 154 | case 3: { // name begins with 155 | pred = [NSPredicate predicateWithFormat:@"name BEGINSWITH[c] %@", self.searchBar.text]; 156 | break; 157 | } 158 | 159 | case 4: { // name matches with, e.g. .*nk 160 | pred = [NSPredicate predicateWithFormat:@"name MATCHES %@", self.searchBar.text]; 161 | break; 162 | } 163 | 164 | case 5: { // zip ends with 165 | 166 | pred = [NSPredicate predicateWithFormat: @"details.zip ENDSWITH %@", self.searchBar.text]; 167 | break; 168 | } 169 | 170 | case 6: { // date is greater than, e.g 2011-12-14 171 | 172 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 173 | [dateFormatter setDateFormat:@"yyyy-MM-dd"]; 174 | NSDate *date = [dateFormatter dateFromString:self.searchBar.text]; 175 | 176 | pred = [NSPredicate predicateWithFormat: @"details.closeDate > %@", date]; 177 | 178 | break; 179 | } 180 | 181 | case 7: { // has at least a tag 182 | 183 | pred = [NSPredicate predicateWithFormat: @"details.tags.@count > 0"]; 184 | 185 | break; 186 | } 187 | 188 | 189 | case 8: // string contains (case insensitive) X and zip is exactly equal to Y. e.g. bank:ville 190 | pred = [NSPredicate predicateWithFormat:@"(name CONTAINS[c] %@) AND (city CONTAINS[c] %@)", [queryArray objectAtIndex:0], [queryArray objectAtIndex:1] 191 | ]; 192 | break; 193 | 194 | case 9: // name contains X and zip is exactly equal to Y, e.g. bank:123 195 | pred = [NSPredicate predicateWithFormat:@"(name CONTAINS[c] %@) AND (details.zip == %i)", [queryArray objectAtIndex:0], 196 | [[queryArray objectAtIndex:1] intValue] 197 | ]; 198 | break; 199 | 200 | 201 | 202 | case 10: // name contains X and tag name is exactly equal to Y, e.g. bank:tag1 203 | pred = [NSPredicate predicateWithFormat:@"(name CONTAINS[c] %@) AND (details.tags == %i)", [queryArray objectAtIndex:0], 204 | [[queryArray objectAtIndex:1] intValue] 205 | ]; 206 | break; 207 | 208 | case 11: { // has a tag whose name contains 209 | 210 | pred = [NSPredicate predicateWithFormat: @"ANY details.tags.name contains[c] %@", self.searchBar.text]; 211 | break; 212 | } 213 | 214 | default: 215 | break; 216 | } 217 | 218 | [fetchRequest setPredicate:pred]; 219 | 220 | NSFetchedResultsController *theFetchedResultsController = 221 | [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 222 | managedObjectContext:managedObjectContext sectionNameKeyPath:nil 223 | cacheName:nil]; // better to not use cache 224 | self.fetchedResultsController = theFetchedResultsController; 225 | _fetchedResultsController.delegate = self; 226 | 227 | return _fetchedResultsController; 228 | 229 | } 230 | 231 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 232 | { 233 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 234 | } 235 | 236 | @end 237 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMSearchViewControllerViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1296 5 | 11D50d 6 | 2182 7 | 1138.32 8 | 568.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 1181 12 | 13 | 14 | IBUIBarButtonItem 15 | IBUIToolbar 16 | IBUITableView 17 | IBUIView 18 | IBUISearchBar 19 | IBProxyObject 20 | 21 | 22 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 23 | 24 | 25 | PluginDependencyRecalculationVersion 26 | 27 | 28 | 29 | 30 | IBFilesOwner 31 | IBCocoaTouchFramework 32 | 33 | 34 | IBFirstResponder 35 | IBCocoaTouchFramework 36 | 37 | 38 | 39 | 274 40 | 41 | 42 | 43 | 266 44 | {320, 44} 45 | 46 | 47 | 48 | _NS:9 49 | NO 50 | NO 51 | IBCocoaTouchFramework 52 | 53 | 54 | Close 55 | IBCocoaTouchFramework 56 | 1 57 | 58 | 59 | 60 | 61 | 62 | 63 | 274 64 | {{0, 88}, {330, 372}} 65 | 66 | 67 | 68 | _NS:9 69 | 70 | 3 71 | MQA 72 | 73 | YES 74 | IBCocoaTouchFramework 75 | YES 76 | 1 77 | 0 78 | YES 79 | 44 80 | 22 81 | 22 82 | 83 | 84 | 85 | 290 86 | {{0, 44}, {320, 44}} 87 | 88 | 89 | _NS:9 90 | 3 91 | IBCocoaTouchFramework 92 | 93 | IBCocoaTouchFramework 94 | 95 | 96 | 97 | {{0, 20}, {320, 460}} 98 | 99 | 100 | 101 | 102 | 3 103 | MQA 104 | 105 | 2 106 | 107 | 108 | 109 | IBCocoaTouchFramework 110 | 111 | 112 | 113 | 114 | 115 | 116 | view 117 | 118 | 119 | 120 | 3 121 | 122 | 123 | 124 | tView 125 | 126 | 127 | 128 | 10 129 | 130 | 131 | 132 | searchBar 133 | 134 | 135 | 136 | 12 137 | 138 | 139 | 140 | closeSearch 141 | 142 | 143 | 144 | 6 145 | 146 | 147 | 148 | 149 | 150 | 0 151 | 152 | 153 | 154 | 155 | 156 | 1 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -1 167 | 168 | 169 | File's Owner 170 | 171 | 172 | -2 173 | 174 | 175 | 176 | 177 | 4 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 5 186 | 187 | 188 | 189 | 190 | 8 191 | 192 | 193 | 194 | 195 | 11 196 | 197 | 198 | 199 | 200 | 201 | 202 | SMSearchViewControllerViewController 203 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 204 | UIResponder 205 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 206 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 207 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 208 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 209 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 210 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 211 | 212 | 213 | 214 | 215 | 216 | 12 217 | 218 | 219 | 220 | 221 | SMSearchViewControllerViewController 222 | UIViewController 223 | 224 | closeSearch 225 | id 226 | 227 | 228 | closeSearch 229 | 230 | closeSearch 231 | id 232 | 233 | 234 | 235 | UISearchBar 236 | UITableView 237 | 238 | 239 | 240 | searchBar 241 | UISearchBar 242 | 243 | 244 | tView 245 | UITableView 246 | 247 | 248 | 249 | IBProjectSource 250 | ./Classes/SMSearchViewControllerViewController.h 251 | 252 | 253 | 254 | 255 | 0 256 | IBCocoaTouchFramework 257 | 258 | com.apple.InterfaceBuilder.CocoaTouchPlugin.iPhoneOS 259 | 260 | 261 | YES 262 | 3 263 | 1181 264 | 265 | 266 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMTagListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SMTagListViewController.h 3 | // FailedBankCD 4 | // 5 | // Created by cesarerocchi on 5/24/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FailedBankDetails.h" 11 | #import "Tag.h" 12 | 13 | @interface SMTagListViewController : UITableViewController 14 | 15 | @property (nonatomic, strong) FailedBankDetails *bankDetails; 16 | @property (nonatomic, strong) NSMutableSet *pickedTags; 17 | @property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController; 18 | 19 | 20 | - (id) initWithBankDetails:(FailedBankDetails *) details; 21 | 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMTagListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SMTagListViewController.m 3 | // FailedBankCD 4 | // 5 | // Created by cesarerocchi on 5/24/12. 6 | // Copyright (c) 2012 Adam Burkepile. All rights reserved. 7 | // 8 | 9 | #import "SMTagListViewController.h" 10 | 11 | @interface SMTagListViewController () 12 | 13 | @end 14 | 15 | @implementation SMTagListViewController 16 | 17 | @synthesize bankDetails = _bankDetails; 18 | @synthesize pickedTags; 19 | @synthesize fetchedResultsController = _fetchedResultsController; 20 | 21 | 22 | - (id) initWithBankDetails:(FailedBankDetails *) details { 23 | 24 | if (self = [super init]) { 25 | 26 | _bankDetails = details; 27 | 28 | } 29 | 30 | return self; 31 | 32 | } 33 | 34 | - (void)viewDidLoad 35 | { 36 | [super viewDidLoad]; 37 | 38 | self.pickedTags = [[NSMutableSet alloc] init]; 39 | 40 | // Retrieve all tags 41 | NSError *error; 42 | if (![self.fetchedResultsController performFetch:&error]) { 43 | NSLog(@"Unresolved error %@, %@", error, [error userInfo]); 44 | abort(); 45 | } 46 | 47 | // Each tag attached to the details is included in the array 48 | NSSet *tags = self.bankDetails.tags; 49 | 50 | for (Tag *tag in tags) { 51 | 52 | [pickedTags addObject:tag]; 53 | 54 | } 55 | 56 | // setting up add button 57 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addTag)]; 58 | } 59 | 60 | - (void) viewWillDisappear:(BOOL)animated { 61 | 62 | [super viewWillDisappear:animated]; 63 | self.bankDetails.tags = pickedTags; 64 | 65 | NSError *error = nil; 66 | if (![self.bankDetails.managedObjectContext save:&error]) { 67 | NSLog(@"Core data error %@, %@", error, [error userInfo]); 68 | abort(); 69 | } 70 | } 71 | 72 | #pragma mark - Actions 73 | 74 | - (void) addTag { 75 | 76 | UIAlertView *newTagAlert = [[UIAlertView alloc] initWithTitle:@"New tag" message:@"Insert new tag name" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Save", nil]; 77 | 78 | newTagAlert.alertViewStyle = UIAlertViewStylePlainTextInput; 79 | 80 | [newTagAlert show]; 81 | 82 | } 83 | 84 | 85 | #pragma mark - Alert view delegate 86 | 87 | 88 | - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { 89 | 90 | if (buttonIndex == 0) { 91 | 92 | NSLog(@"cancel"); 93 | 94 | } else { 95 | 96 | 97 | NSString *tagName = [[alertView textFieldAtIndex:0] text]; 98 | 99 | Tag *tag = [NSEntityDescription insertNewObjectForEntityForName:@"Tag" 100 | inManagedObjectContext:self.bankDetails.managedObjectContext]; 101 | tag.name = tagName; 102 | 103 | NSError *error = nil; 104 | if (![tag.managedObjectContext save:&error]) { 105 | NSLog(@"Core data error %@, %@", error, [error userInfo]); 106 | abort(); 107 | } 108 | 109 | [self.fetchedResultsController performFetch:&error]; 110 | 111 | [self.tableView reloadData]; 112 | } 113 | 114 | } 115 | 116 | 117 | #pragma mark - Table view data source 118 | 119 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 120 | { 121 | return 1; 122 | } 123 | 124 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 125 | { 126 | id sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; 127 | return [sectionInfo numberOfObjects]; 128 | } 129 | 130 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 131 | { 132 | static NSString *CellIdentifier = @"TagCell"; 133 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 134 | 135 | if (cell == nil) { 136 | cell = [[UITableViewCell alloc] 137 | initWithStyle:UITableViewCellStyleDefault 138 | reuseIdentifier:CellIdentifier]; 139 | } 140 | cell.accessoryType = UITableViewCellAccessoryNone; 141 | 142 | Tag *tag = (Tag *)[self.fetchedResultsController objectAtIndexPath:indexPath]; 143 | if ([pickedTags containsObject:tag]) { 144 | cell.accessoryType = UITableViewCellAccessoryCheckmark; 145 | } 146 | 147 | cell.textLabel.text = tag.name; 148 | return cell; 149 | 150 | return cell; 151 | } 152 | 153 | /* 154 | // Override to support conditional editing of the table view. 155 | - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath 156 | { 157 | // Return NO if you do not want the specified item to be editable. 158 | return YES; 159 | } 160 | */ 161 | 162 | /* 163 | // Override to support editing the table view. 164 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 165 | { 166 | if (editingStyle == UITableViewCellEditingStyleDelete) { 167 | // Delete the row from the data source 168 | [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 169 | } 170 | else if (editingStyle == UITableViewCellEditingStyleInsert) { 171 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view 172 | } 173 | } 174 | */ 175 | 176 | 177 | 178 | #pragma mark - Table view delegate 179 | 180 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 181 | 182 | Tag *tag = (Tag *)[self.fetchedResultsController objectAtIndexPath:indexPath]; 183 | UITableViewCell * cell = [self.tableView cellForRowAtIndexPath:indexPath]; 184 | [cell setSelected:NO animated:YES]; 185 | 186 | if ([pickedTags containsObject:tag]) { 187 | 188 | [pickedTags removeObject:tag]; 189 | cell.accessoryType = UITableViewCellAccessoryNone; 190 | 191 | } else { 192 | 193 | [pickedTags addObject:tag]; 194 | cell.accessoryType = UITableViewCellAccessoryCheckmark; 195 | 196 | } 197 | } 198 | 199 | #pragma mark - Result controller 200 | 201 | - (NSFetchedResultsController *)fetchedResultsController 202 | { 203 | if (_fetchedResultsController != nil) { 204 | return _fetchedResultsController; 205 | } 206 | NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 207 | 208 | NSEntityDescription *entity = [NSEntityDescription 209 | entityForName:@"Tag" 210 | inManagedObjectContext:self.bankDetails.managedObjectContext]; 211 | [fetchRequest setEntity:entity]; 212 | 213 | NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] 214 | initWithKey:@"name" 215 | ascending:NO]; 216 | NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil]; 217 | [fetchRequest setSortDescriptors:sortDescriptors]; 218 | 219 | NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 220 | managedObjectContext:self.bankDetails.managedObjectContext 221 | sectionNameKeyPath:nil 222 | cacheName:nil]; 223 | 224 | self.fetchedResultsController = aFetchedResultsController; 225 | 226 | NSError *error = nil; 227 | if (![self.fetchedResultsController performFetch:&error]) { 228 | NSLog(@"Core data error %@, %@", error, [error userInfo]); 229 | abort(); 230 | } 231 | 232 | return _fetchedResultsController; 233 | } 234 | 235 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation 236 | { 237 | return (interfaceOrientation == UIInterfaceOrientationPortrait); 238 | } 239 | 240 | 241 | @end 242 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/SMTagListViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1280 5 | 11C25 6 | 1919 7 | 1138.11 8 | 566.00 9 | 10 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 11 | 916 12 | 13 | 14 | IBProxyObject 15 | IBUITableView 16 | 17 | 18 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 19 | 20 | 21 | PluginDependencyRecalculationVersion 22 | 23 | 24 | 25 | 26 | IBFilesOwner 27 | IBCocoaTouchFramework 28 | 29 | 30 | IBFirstResponder 31 | IBCocoaTouchFramework 32 | 33 | 34 | 35 | 274 36 | {{0, 20}, {320, 460}} 37 | 38 | 39 | 40 | 41 | 3 42 | MQA 43 | 44 | NO 45 | YES 46 | NO 47 | 48 | IBCocoaTouchFramework 49 | NO 50 | 1 51 | 0 52 | YES 53 | 44 54 | 22 55 | 22 56 | 57 | 58 | 59 | 60 | 61 | 62 | view 63 | 64 | 65 | 66 | 5 67 | 68 | 69 | 70 | dataSource 71 | 72 | 73 | 74 | 6 75 | 76 | 77 | 78 | delegate 79 | 80 | 81 | 82 | 7 83 | 84 | 85 | 86 | 87 | 88 | 0 89 | 90 | 91 | 92 | 93 | 94 | -1 95 | 96 | 97 | File's Owner 98 | 99 | 100 | -2 101 | 102 | 103 | 104 | 105 | 4 106 | 107 | 108 | 109 | 110 | 111 | 112 | SMTagListViewController 113 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 114 | UIResponder 115 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 116 | com.apple.InterfaceBuilder.IBCocoaTouchPlugin 117 | 118 | 119 | 120 | 121 | 122 | 7 123 | 124 | 125 | 126 | 127 | SMTagListViewController 128 | UIViewController 129 | 130 | IBProjectSource 131 | ./Classes/SMTagListViewController.h 132 | 133 | 134 | 135 | 136 | 0 137 | IBCocoaTouchFramework 138 | YES 139 | 3 140 | 916 141 | 142 | 143 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/Tag.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @class FailedBankDetails; 5 | 6 | @interface Tag : NSManagedObject 7 | 8 | @property (nonatomic, retain) NSString * name; 9 | @property (nonatomic, retain) NSSet *detail; 10 | @end 11 | 12 | @interface Tag (CoreDataGeneratedAccessors) 13 | 14 | - (void)addDetailObject:(FailedBankDetails *)value; 15 | - (void)removeDetailObject:(FailedBankDetails *)value; 16 | - (void)addDetail:(NSSet *)values; 17 | - (void)removeDetail:(NSSet *)values; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/Tag.m: -------------------------------------------------------------------------------- 1 | #import "Tag.h" 2 | #import "FailedBankDetails.h" 3 | 4 | 5 | @implementation Tag 6 | 7 | @dynamic name; 8 | @dynamic detail; 9 | 10 | @end 11 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/en.lproj/MainStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 46 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/FailedBankCD/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "FBCDAppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([FBCDAppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /exampleProjects/FailedBank/README: -------------------------------------------------------------------------------- 1 | Project is a modified version of the tutorial at http://www.raywenderlich.com/14742/core-data-on-ios-5-tutorial-how-to-work-with-relations-and-predicates 2 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModel.xcdatamodeld/ClassModel.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDChildA.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDChildA.h 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "ISDParent.h" 12 | 13 | 14 | @interface ISDChildA : ISDParent 15 | 16 | @property (nonatomic, retain) NSString * attributeA; 17 | @property (nonatomic, retain) ISDRoot *multipleOneToMany; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDChildA.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDChildA.m 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import "ISDChildA.h" 10 | 11 | 12 | @implementation ISDChildA 13 | 14 | @dynamic attributeA; 15 | @dynamic multipleOneToMany; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDChildB.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDChildB.h 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "ISDParent.h" 12 | 13 | 14 | @interface ISDChildB : ISDParent 15 | 16 | @property (nonatomic, retain) NSString * attributeB; 17 | @property (nonatomic, retain) ISDRoot *multipleOneToMany; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDChildB.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDChildB.m 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import "ISDChildB.h" 10 | 11 | 12 | @implementation ISDChildB 13 | 14 | @dynamic attributeB; 15 | @dynamic multipleOneToMany; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDModelCategories.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDModelCategories.h 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSManagedObject (ISDHelper) 12 | 13 | +(NSString *)entityName; 14 | 15 | +(instancetype)insertInManagedObjectContext:(NSManagedObjectContext *)context; 16 | 17 | +(NSFetchRequest *)fetchRequest; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDModelCategories.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDModelCategories.m 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import "ISDModelCategories.h" 10 | 11 | #import "ISDRoot.h" 12 | #import "ISDParent.h" 13 | #import "ISDChildA.h" 14 | #import "ISDChildB.h" 15 | 16 | @implementation NSManagedObject (ISDHelper) 17 | 18 | +(NSString *)entityName 19 | { 20 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Override in subclasses" userInfo:nil]; 21 | } 22 | 23 | +(instancetype)insertInManagedObjectContext:(NSManagedObjectContext *)context 24 | { 25 | return [NSEntityDescription insertNewObjectForEntityForName:[self entityName] inManagedObjectContext:context]; 26 | } 27 | 28 | +(NSFetchRequest *)fetchRequest 29 | { 30 | return [NSFetchRequest fetchRequestWithEntityName:[self entityName]]; 31 | } 32 | 33 | @end 34 | 35 | @implementation ISDRoot (ISDHelper) 36 | 37 | +(NSString *)entityName 38 | { 39 | return @"Root"; 40 | } 41 | 42 | @end 43 | 44 | @implementation ISDChildA (ISDHelper) 45 | 46 | +(NSString *)entityName 47 | { 48 | return @"ChildA"; 49 | } 50 | 51 | @end 52 | 53 | @implementation ISDChildB (ISDHelper) 54 | 55 | +(NSString *)entityName 56 | { 57 | return @"ChildB"; 58 | } 59 | 60 | @end 61 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDParent.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDParent.h 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class ISDRoot; 13 | 14 | @interface ISDParent : NSManagedObject 15 | 16 | @property (nonatomic, retain) NSString * name; 17 | @property (nonatomic, retain) ISDRoot *oneToManyInverse; 18 | @property (nonatomic, retain) ISDRoot *oneToOneInverse; 19 | @property (nonatomic, retain) ISDRoot *oneToOneNilInverse; 20 | @property (nonatomic, retain) NSSet *manyToManyInverse; 21 | @end 22 | 23 | @interface ISDParent (CoreDataGeneratedAccessors) 24 | 25 | - (void)addManyToManyInverseObject:(ISDRoot *)value; 26 | - (void)removeManyToManyInverseObject:(ISDRoot *)value; 27 | - (void)addManyToManyInverse:(NSSet *)values; 28 | - (void)removeManyToManyInverse:(NSSet *)values; 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDParent.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDParent.m 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import "ISDParent.h" 10 | #import "ISDRoot.h" 11 | 12 | 13 | @implementation ISDParent 14 | 15 | @dynamic name; 16 | @dynamic oneToManyInverse; 17 | @dynamic oneToOneInverse; 18 | @dynamic oneToOneNilInverse; 19 | @dynamic manyToManyInverse; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDRoot.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDRoot.h 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class ISDParent; 13 | 14 | @interface ISDRoot : NSManagedObject 15 | 16 | @property (nonatomic, retain) NSString * name; 17 | @property (nonatomic, retain) NSSet *oneToMany; 18 | @property (nonatomic, retain) ISDParent *oneToOne; 19 | @property (nonatomic, retain) ISDParent *oneToOneNil; 20 | @property (nonatomic, retain) NSSet *manyToMany; 21 | @property (nonatomic, retain) NSSet *multipleOneToManyChildA; 22 | @property (nonatomic, retain) NSSet *multipleOneToManyChildB; 23 | 24 | @end 25 | 26 | @interface ISDRoot (CoreDataGeneratedAccessors) 27 | 28 | - (void)addOneToManyObject:(ISDParent *)value; 29 | - (void)removeOneToManyObject:(ISDParent *)value; 30 | - (void)addOneToMany:(NSSet *)values; 31 | - (void)removeOneToMany:(NSSet *)values; 32 | 33 | - (void)addManyToManyObject:(ISDParent *)value; 34 | - (void)removeManyToManyObject:(ISDParent *)value; 35 | - (void)addManyToMany:(NSSet *)values; 36 | - (void)removeManyToMany:(NSSet *)values; 37 | 38 | - (void)addMultipleOneToManyChildAObject:(ISDParent *)value; 39 | - (void)removeMultipleOneToManyChildAObject:(ISDParent *)value; 40 | - (void)addMultipleOneToManyChildA:(NSSet *)values; 41 | - (void)removeMultipleOneToManyChildA:(NSSet *)values; 42 | 43 | - (void)addMultipleOneToManyChildBObject:(ISDParent *)value; 44 | - (void)removeMultipleOneToManyChildBObject:(ISDParent *)value; 45 | - (void)addMultipleOneToManyChildB:(NSSet *)values; 46 | - (void)removeMultipleOneToManyChildB:(NSSet *)values; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/ClassModels/ISDRoot.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDRoot.m 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import "ISDRoot.h" 10 | #import "ISDParent.h" 11 | 12 | 13 | @implementation ISDRoot 14 | 15 | @dynamic name; 16 | @dynamic oneToMany; 17 | @dynamic oneToOne; 18 | @dynamic oneToOneNil; 19 | @dynamic manyToMany; 20 | @dynamic multipleOneToManyChildA; 21 | @dynamic multipleOneToManyChildB; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/ISDAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDAppDelegate.h 3 | // Incremental Store Demo 4 | // 5 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @interface ISDAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | /* 16 | 17 | A shared application context created with the main queue concurrency type. 18 | 19 | */ 20 | + (NSManagedObjectContext *)managedObjectContext; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/ISDAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDAppDelegate.m 3 | // Incremental Store Demo 4 | // 5 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 6 | // 7 | 8 | #import "EncryptedStore.h" 9 | 10 | #import "ISDAppDelegate.h" 11 | 12 | // TOGGLE ECD ON = 1 AND OFF = 0 13 | #define USE_ENCRYPTED_STORE 1 14 | 15 | @implementation ISDAppDelegate 16 | 17 | + (NSPersistentStoreCoordinator *)persistentStoreCoordinator { 18 | static NSPersistentStoreCoordinator *coordinator = nil; 19 | static dispatch_once_t token; 20 | dispatch_once(&token, ^{ 21 | 22 | // get the model 23 | NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil]; 24 | 25 | // get the coordinator 26 | coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; 27 | 28 | // add store 29 | NSFileManager *fileManager = [NSFileManager defaultManager]; 30 | NSURL *applicationSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject]; 31 | [fileManager createDirectoryAtURL:applicationSupportURL withIntermediateDirectories:NO attributes:nil error:nil]; 32 | NSURL *databaseURL = [applicationSupportURL URLByAppendingPathComponent:@"database.sqlite"]; 33 | NSError *error = nil; 34 | 35 | // [[NSFileManager defaultManager] removeItemAtURL:databaseURL error:&error]; 36 | 37 | NSDictionary *options = @{ 38 | EncryptedStorePassphraseKey : @"DB_KEY_HERE", 39 | // EncryptedStoreDatabaseLocation : databaseURL, 40 | // NSMigratePersistentStoresAutomaticallyOption : @YES, 41 | NSInferMappingModelAutomaticallyOption : @YES 42 | }; 43 | NSPersistentStore *store = [coordinator 44 | addPersistentStoreWithType:EncryptedStoreType 45 | configuration:nil 46 | URL:databaseURL 47 | options:options 48 | error:&error]; 49 | // coordinator = [EncryptedStore makeStoreWithOptions:options managedObjectModel:model]; 50 | 51 | NSAssert(store, @"Unable to add persistent store!\n%@", error); 52 | 53 | }); 54 | return coordinator; 55 | } 56 | 57 | + (NSPersistentStoreCoordinator *)persistentStoreCoordinator_CoreData { 58 | NSError *error = nil; 59 | NSURL *storeURL = [[[[NSFileManager defaultManager] 60 | URLsForDirectory:NSDocumentDirectory 61 | inDomains:NSUserDomainMask] 62 | lastObject] 63 | URLByAppendingPathComponent:@"cleardb.sqlite"]; 64 | 65 | NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[NSManagedObjectModel mergedModelFromBundles:nil]]; 66 | [coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]; 67 | 68 | return coordinator; 69 | 70 | } 71 | 72 | + (NSManagedObjectContext *)managedObjectContext { 73 | static NSManagedObjectContext *context = nil; 74 | static dispatch_once_t token; 75 | dispatch_once(&token, ^{ 76 | context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 77 | #if USE_ENCRYPTED_STORE 78 | [context setPersistentStoreCoordinator:[self persistentStoreCoordinator]]; 79 | #else 80 | [context setPersistentStoreCoordinator:[self persistentStoreCoordinator_CoreData]]; 81 | #endif 82 | }); 83 | return context; 84 | } 85 | 86 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 87 | 88 | // insert a few objects if we don't have any 89 | { 90 | NSManagedObjectContext *context = [ISDAppDelegate managedObjectContext]; 91 | NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"User"]; 92 | NSUInteger count = [context countForFetchRequest:request error:nil]; 93 | if (count == 0) { 94 | NSArray *array = [NSArray arrayWithObjects:@"Gregg", @"Jon", @"Jase", @"Gavin", nil]; 95 | [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 96 | NSManagedObject *user = [NSEntityDescription insertNewObjectForEntityForName:@"User" inManagedObjectContext:context]; 97 | [user setValue:obj forKey:@"name"]; 98 | for (NSInteger i = 0; i < 3; i++) { 99 | NSManagedObject *post = [NSEntityDescription insertNewObjectForEntityForName:@"Post" inManagedObjectContext:context]; 100 | [post setValue:@"Test Title" forKey:@"title"]; 101 | [post setValue:@"Test body" forKey:@"body"]; 102 | [post setValue:user forKey:@"user"]; 103 | } 104 | }]; 105 | [context save:nil]; 106 | } 107 | } 108 | 109 | // return 110 | return YES; 111 | 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/ISDEditPostViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDEditPostViewController.h 3 | // Incremental Store Demo 4 | // 5 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @interface ISDEditPostViewController : UIViewController 12 | 13 | @property (nonatomic, weak) IBOutlet UITextView *bodyTextView; 14 | @property (nonatomic, weak) IBOutlet UITextField *titleTextField; 15 | @property (nonatomic, weak) IBOutlet UITextField *tagsTextField; 16 | @property (nonatomic, strong) NSManagedObject *post; 17 | 18 | - (IBAction)save:(id)sender; 19 | - (IBAction)cancel:(id)sender; 20 | 21 | @end 22 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/ISDEditPostViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDEditPostViewController.m 3 | // Incremental Store Demo 4 | // 5 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 6 | // 7 | 8 | #import "ISDEditPostViewController.h" 9 | 10 | @implementation ISDEditPostViewController 11 | 12 | - (id)initWithCoder:(NSCoder *)coder { 13 | self = [super initWithCoder:coder]; 14 | if (self) { 15 | self.title = @"New Post"; 16 | } 17 | return self; 18 | } 19 | 20 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { 21 | return (orientation == UIInterfaceOrientationPortrait); 22 | } 23 | 24 | - (void)populateFieldsFromModel { 25 | self.titleTextField.text = [self.post valueForKey:@"title"]; 26 | self.bodyTextView.text = [self.post valueForKey:@"body"]; 27 | self.tagsTextField.text = [[self.post valueForKey:@"tags"] componentsJoinedByString:@" "]; 28 | } 29 | 30 | - (void)setPost:(NSManagedObject *)post { 31 | _post = post; 32 | self.title = @"Edit Post"; 33 | [self populateFieldsFromModel]; 34 | } 35 | 36 | - (void)viewDidLoad { 37 | [super viewDidLoad]; 38 | [self populateFieldsFromModel]; 39 | [self.titleTextField becomeFirstResponder]; 40 | } 41 | 42 | - (IBAction)save:(id)sender { 43 | [self.post setValue:self.titleTextField.text forKey:@"title"]; 44 | [self.post setValue:self.bodyTextView.text forKey:@"body"]; 45 | [self.post setValue:[self.tagsTextField.text componentsSeparatedByString:@" "] forKey:@"tags"]; 46 | NSError *error = nil; 47 | if ([[self.post managedObjectContext] save:&error]) { 48 | [self.navigationController popViewControllerAnimated:YES]; 49 | } 50 | else { 51 | NSLog(@"%@", error); 52 | NSArray *array = [[error userInfo] objectForKey:NSPersistentStoreSaveConflictsErrorKey]; 53 | [array enumerateObjectsUsingBlock:^(id conflict, NSUInteger index, BOOL *stop) { 54 | NSLog(@"%@", conflict); 55 | }]; 56 | } 57 | } 58 | 59 | - (IBAction)cancel:(id)sender { 60 | [[self.post managedObjectContext] rollback]; 61 | [self.navigationController popViewControllerAnimated:YES]; 62 | } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/ISDPostListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDPostListViewController.h 3 | // Incremental Store Demo 4 | // 5 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 6 | // 7 | 8 | #import 9 | #import 10 | 11 | @interface ISDPostListViewController : UITableViewController 12 | 13 | @property (nonatomic, strong) NSManagedObject *user; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/ISDPostListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDPostListViewController.m 3 | // Incremental Store Demo 4 | // 5 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 6 | // 7 | 8 | #import "ISDPostListViewController.h" 9 | #import "ISDEditPostViewController.h" 10 | 11 | #import "ISDAppDelegate.h" 12 | 13 | @implementation ISDPostListViewController { 14 | NSArray *posts; 15 | } 16 | 17 | - (id)initWithCoder:(NSCoder *)coder { 18 | self = [super initWithCoder:coder]; 19 | if (self) { 20 | [[NSNotificationCenter defaultCenter] 21 | addObserver:self 22 | selector:@selector(managedObjectContextDidSave) 23 | name:NSManagedObjectContextDidSaveNotification 24 | object:[ISDAppDelegate managedObjectContext]]; 25 | } 26 | return self; 27 | } 28 | 29 | - (void)dealloc { 30 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 31 | } 32 | 33 | - (void)setUser:(NSManagedObject *)user { 34 | _user = user; 35 | [self reloadData]; 36 | NSString *name = [_user valueForKey:@"name"]; 37 | self.title = [NSString stringWithFormat:@"%@'s Posts", name]; 38 | } 39 | 40 | - (void)managedObjectContextDidSave { 41 | [self reloadData]; 42 | [self.tableView reloadData]; 43 | } 44 | 45 | - (void)reloadData { 46 | if ([self isViewLoaded]) { 47 | NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"Post"]; 48 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"user == %@", _user]; 49 | [request setPredicate:predicate]; 50 | NSManagedObjectContext *context = [ISDAppDelegate managedObjectContext]; 51 | NSError *error = nil; 52 | posts = [context executeFetchRequest:request error:&error]; 53 | if (error) { NSLog(@"%@", error); } 54 | } 55 | } 56 | 57 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { 58 | return (orientation == UIInterfaceOrientationPortrait); 59 | } 60 | 61 | - (void)didReceiveMemoryWarning { 62 | if ([self isViewLoaded] && !self.view.window) { 63 | posts = nil; 64 | } 65 | [super didReceiveMemoryWarning]; 66 | } 67 | 68 | - (void)viewDidLoad { 69 | [super viewDidLoad]; 70 | [self reloadData]; 71 | } 72 | 73 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 74 | return 1; 75 | } 76 | 77 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 78 | return [posts count]; 79 | } 80 | 81 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 82 | static NSString *identifier = @"Cell"; 83 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 84 | NSManagedObject *object = [posts objectAtIndex:indexPath.row]; 85 | cell.textLabel.text = [object valueForKey:@"title"]; 86 | return cell; 87 | } 88 | 89 | - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { 90 | if (editingStyle == UITableViewCellEditingStyleDelete) { 91 | NSManagedObject *object = [posts objectAtIndex:indexPath.row]; 92 | NSManagedObjectContext *context = [object managedObjectContext]; 93 | [context deleteObject:object]; 94 | NSError *error = nil; 95 | BOOL save = [context save:&error]; 96 | NSAssert(save, @"%@", error); 97 | } 98 | } 99 | 100 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 101 | NSManagedObject *object = [posts objectAtIndex:indexPath.row]; 102 | ISDEditPostViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:@"EditPostViewController"]; 103 | controller.post = object; 104 | [self.navigationController pushViewController:controller animated:YES]; 105 | } 106 | 107 | @end 108 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/ISDUserListViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ISDUserListViewController.h 3 | // Incremental Store Demo 4 | // 5 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 6 | // 7 | 8 | #import 9 | 10 | @interface ISDUserListViewController : UITableViewController 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/ISDUserListViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDUserListViewController.m 3 | // Incremental Store Demo 4 | // 5 | // Copyright 2012 - 2014 The MITRE Corporation, All Rights Reserved. 6 | // 7 | 8 | #import 9 | 10 | #import "ISDUserListViewController.h" 11 | #import "ISDPostListViewController.h" 12 | 13 | #import "ISDAppDelegate.h" 14 | 15 | @implementation ISDUserListViewController { 16 | NSArray *users; 17 | NSNumberFormatter *formatter; 18 | } 19 | 20 | - (id)initWithCoder:(NSCoder *)coder { 21 | self = [super initWithCoder:coder]; 22 | if (self) { 23 | [[NSNotificationCenter defaultCenter] 24 | addObserver:self 25 | selector:@selector(managedObjectContextDidSave) 26 | name:NSManagedObjectContextDidSaveNotification 27 | object:[ISDAppDelegate managedObjectContext]]; 28 | } 29 | return self; 30 | } 31 | 32 | - (void)dealloc { 33 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 34 | } 35 | 36 | - (void)managedObjectContextDidSave { 37 | [self reloadData]; 38 | [self.tableView reloadData]; 39 | } 40 | 41 | - (void)reloadData { 42 | if ([self isViewLoaded]) { 43 | NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"User"]; 44 | [request setSortDescriptors:@[ [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES] ]]; 45 | NSManagedObjectContext *context = [ISDAppDelegate managedObjectContext]; 46 | users = [context executeFetchRequest:request error:nil]; 47 | } 48 | } 49 | 50 | - (void)viewDidLoad { 51 | [super viewDidLoad]; 52 | [self reloadData]; 53 | formatter = [[NSNumberFormatter alloc] init]; 54 | } 55 | 56 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation { 57 | return (orientation == UIInterfaceOrientationPortrait); 58 | } 59 | 60 | - (void)didReceiveMemoryWarning { 61 | if ([self isViewLoaded] && !self.view.window) { 62 | users = nil; 63 | formatter = nil; 64 | } 65 | [super didReceiveMemoryWarning]; 66 | } 67 | 68 | #pragma mark - Table view data source 69 | 70 | - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { 71 | return 1; 72 | } 73 | 74 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { 75 | return [users count]; 76 | } 77 | 78 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { 79 | static NSString *identifier = @"Cell"; 80 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 81 | NSManagedObject *object = [users objectAtIndex:indexPath.row]; 82 | cell.textLabel.text = [object valueForKey:@"name"]; 83 | NSUInteger count = [[object valueForKey:@"posts"] count]; 84 | NSNumber *number = [NSNumber numberWithUnsignedInteger:count]; 85 | cell.detailTextLabel.text = [formatter stringFromNumber:number]; 86 | return cell; 87 | } 88 | 89 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 90 | NSManagedObject *object = [users objectAtIndex:indexPath.row]; 91 | ISDPostListViewController *posts = [self.storyboard instantiateViewControllerWithIdentifier:@"PostsViewController"]; 92 | posts.user = object; 93 | [self.navigationController pushViewController:posts animated:YES]; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "29x29", 26 | "scale" : "3x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "40x40", 36 | "scale" : "3x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "57x57", 41 | "scale" : "1x" 42 | }, 43 | { 44 | "idiom" : "iphone", 45 | "size" : "57x57", 46 | "scale" : "2x" 47 | }, 48 | { 49 | "idiom" : "iphone", 50 | "size" : "60x60", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "iphone", 55 | "size" : "60x60", 56 | "scale" : "3x" 57 | } 58 | ], 59 | "info" : { 60 | "version" : 1, 61 | "author" : "xcode" 62 | } 63 | } -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "minimum-system-version" : "7.0", 7 | "subtype" : "retina4", 8 | "scale" : "2x" 9 | }, 10 | { 11 | "idiom" : "iphone", 12 | "scale" : "1x", 13 | "orientation" : "portrait" 14 | }, 15 | { 16 | "idiom" : "iphone", 17 | "scale" : "2x", 18 | "orientation" : "portrait" 19 | }, 20 | { 21 | "orientation" : "portrait", 22 | "idiom" : "iphone", 23 | "subtype" : "retina4", 24 | "scale" : "2x" 25 | }, 26 | { 27 | "orientation" : "portrait", 28 | "idiom" : "iphone", 29 | "minimum-system-version" : "7.0", 30 | "scale" : "2x" 31 | } 32 | ], 33 | "info" : { 34 | "version" : 1, 35 | "author" : "xcode" 36 | } 37 | } -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/Incremental Store Demo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIcons 12 | 13 | CFBundleIcons~ipad 14 | 15 | CFBundleIdentifier 16 | $(PRODUCT_BUNDLE_IDENTIFIER) 17 | CFBundleInfoDictionaryVersion 18 | 6.0 19 | CFBundleName 20 | ${PRODUCT_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | 1.0 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | 1.0 29 | LSRequiresIPhoneOS 30 | 31 | UIMainStoryboardFile 32 | MainStoryboard 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/Incremental Store Demo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'Incremental Store Demo' target in the 'Incremental Store Demo' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/en.lproj/MainStoryboard.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 132 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store Demo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // Incremental Store Demo 4 | // 5 | // 6 | 7 | #import 8 | 9 | #import "ISDAppDelegate.h" 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([ISDAppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Incremental Store.xcodeproj/xcshareddata/xcschemes/Incremental Store Demo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 65 | 67 | 73 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 83 | 84 | 90 | 92 | 98 | 99 | 100 | 101 | 103 | 104 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Migration.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | Migration.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Migration.xcdatamodeld/Migration.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Migration.xcdatamodeld/New.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Model.xcdatamodeld/Model.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/SubEntitiesModel.xcdatamodeld/SubEntitiesModel.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Tests/ISDMigrationTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // ISDMigrationTests.m 3 | // Incremental Store 4 | // 5 | // Created by Daniel Broad on 22/12/2015. 6 | // Copyright © 2015 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "EncryptedStore.h" 12 | 13 | #define USE_ENCRYPTED_STORE 1 14 | 15 | @interface ISDMigrationTests : XCTestCase 16 | 17 | @end 18 | 19 | @implementation ISDMigrationTests { 20 | NSPersistentStoreCoordinator *coordinator; 21 | NSPersistentStore *store; 22 | NSManagedObjectContext *context; 23 | } 24 | 25 | + (void)initialize { 26 | if (self == [ISDMigrationTests class]) { 27 | srand((int)time(NULL)); 28 | } 29 | } 30 | 31 | + (NSBundle *)bundle { 32 | return [NSBundle bundleForClass:[EncryptedStore class]]; 33 | } 34 | 35 | + (NSURL *)databaseURL { 36 | NSBundle *bundle = [self bundle]; 37 | NSString *identifier = [[bundle infoDictionary] objectForKey:@"CFBundleIdentifier"]; 38 | NSString *path = NSTemporaryDirectory(); 39 | path = [path stringByAppendingPathComponent:identifier]; 40 | NSURL *URL = [NSURL fileURLWithPath:path]; 41 | [[NSFileManager defaultManager] createDirectoryAtURL:URL withIntermediateDirectories:YES attributes:nil error:nil]; 42 | URL = [URL URLByAppendingPathComponent:@"database-test.sqlite"]; 43 | return URL; 44 | } 45 | 46 | + (void)deleteDatabase { 47 | NSFileManager *manager = [NSFileManager defaultManager]; 48 | [manager removeItemAtURL:[self databaseURL] error:nil]; 49 | } 50 | 51 | - (NSManagedObjectModel *)managedObjectModelForVersion:(NSString *)version 52 | { 53 | NSURL *modelURL = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:@"Migration.momd/%@",version] withExtension:@"mom"]; 54 | NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 55 | return model; 56 | } 57 | 58 | -(void)createObjectGraph 59 | { 60 | 61 | // Jobs 62 | NSManagedObject *job = [NSEntityDescription insertNewObjectForEntityForName:@"Task" inManagedObjectContext:context]; 63 | [job setValue:@"A-Task" forKey:@"name"]; 64 | 65 | // Update 66 | NSManagedObject *update = [NSEntityDescription insertNewObjectForEntityForName:@"TaskStatusUpdate" inManagedObjectContext:context]; 67 | [update setValue:@"TaskStatusUpdate" forKey:@"name"]; 68 | [update setValue:[NSDate date] forKey:@"timeStamp"]; 69 | [update setValue:job forKey:@"task"]; 70 | 71 | NSManagedObject *projectupdate = [NSEntityDescription insertNewObjectForEntityForName:@"TaskGroupStatusUpdate" inManagedObjectContext:context]; 72 | [projectupdate setValue:[NSSet setWithObject:update] forKey:@"taskStatus"]; 73 | [projectupdate setValue:@"TaskStatusUpdate" forKey:@"name"]; 74 | [projectupdate setValue:[NSDate date] forKey:@"timeStamp"]; 75 | 76 | // Save 77 | NSError *error = nil; 78 | BOOL save = [context save:&error]; 79 | XCTAssertTrue(save, @"Unable to perform save.\n%@", error); 80 | } 81 | 82 | -(void)migrateStore { 83 | [coordinator removePersistentStore:store error:nil]; 84 | [self setUpDBWithModel:@"New"]; 85 | } 86 | 87 | - (void)setUpDBWithModel: (NSString*) modelName { 88 | // get the model 89 | NSManagedObjectModel *model = [self managedObjectModelForVersion:modelName]; 90 | 91 | // get the coordinator 92 | coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; 93 | 94 | // add store 95 | NSDictionary *options = @{ 96 | EncryptedStorePassphraseKey : @"DB_KEY_HERE", 97 | NSMigratePersistentStoresAutomaticallyOption : @YES, 98 | NSInferMappingModelAutomaticallyOption : @YES, 99 | EncryptedStoreCacheSize: @1000 100 | }; 101 | NSURL *URL = [self.class databaseURL]; 102 | NSLog(@"Working with database at URL: %@", URL); 103 | NSError *error = nil; 104 | 105 | NSString *storeType = nil; 106 | #if USE_ENCRYPTED_STORE 107 | storeType = EncryptedStoreType; 108 | #else 109 | storeType = NSSQLiteStoreType; 110 | #endif 111 | 112 | store = [coordinator 113 | addPersistentStoreWithType:storeType 114 | configuration:nil 115 | URL:URL 116 | options:options 117 | error:&error]; 118 | 119 | XCTAssertNotNil(store, @"Unable to add persistent store.\n%@", error); 120 | 121 | // load context 122 | context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType]; 123 | [context setPersistentStoreCoordinator:coordinator]; 124 | XCTAssertNotNil(context, @"Unable to create context.\n%@", error); 125 | 126 | // log 127 | NSLog(@"Working with database at %@", [URL path]); 128 | 129 | } 130 | - (void)setUp { 131 | [super setUp]; 132 | 133 | [self.class deleteDatabase]; 134 | 135 | [self setUpDBWithModel:@"Migration"]; 136 | 137 | [self createObjectGraph]; 138 | } 139 | 140 | - (void)tearDown { 141 | // Put teardown code here. This method is called after the invocation of each test method in the class. 142 | [super tearDown]; 143 | } 144 | 145 | - (NSManagedObject*)integrityCheck { 146 | NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"Task"]; 147 | 148 | NSError *error; 149 | NSArray *tasks = [context executeFetchRequest:request error:&error]; 150 | XCTAssertNil(error,@"Error fetching %@",error); 151 | XCTAssertEqual(tasks.count, 1,@"Did not find 1 job"); 152 | 153 | NSManagedObject *task = [tasks firstObject]; 154 | 155 | XCTAssertTrue([[task valueForKey:@"name"] isEqualToString:@"A-Task"],@"Task name check"); 156 | 157 | NSSet *statusUpdates = [task valueForKey:@"statusUpdates"]; 158 | 159 | XCTAssertNotNil(statusUpdates,@"Status updates relationship check"); 160 | XCTAssertEqual(statusUpdates.count,1,@"Status updates relationship count"); 161 | 162 | NSManagedObject *taskStatusUpdate = [statusUpdates anyObject]; 163 | 164 | XCTAssertNotNil([taskStatusUpdate valueForKey:@"task"],@"status update must have a task"); 165 | XCTAssertNotNil([taskStatusUpdate valueForKey:@"timeStamp"],@"status update must have a timeStamp"); 166 | 167 | NSManagedObject *projectStatusUpdate = [taskStatusUpdate valueForKey:@"projectUpdate"]; 168 | XCTAssertNotNil(projectStatusUpdate,@"status update must have a project"); 169 | 170 | XCTAssertNotNil([projectStatusUpdate valueForKey:@"name"],@"status update must have a name"); 171 | XCTAssertNotNil([projectStatusUpdate valueForKey:@"timeStamp"],@"status update must have a timeStamp"); 172 | 173 | NSSet *projectStatusUpdates = [projectStatusUpdate valueForKey:@"taskStatus"]; 174 | XCTAssertNotNil(projectStatusUpdates,@"Project Status updates relationship check"); 175 | XCTAssertEqual(projectStatusUpdates.count,1,@"Project Status updates relationship count"); 176 | 177 | return projectStatusUpdate; 178 | 179 | } 180 | 181 | - (void)testUnmigratedStore { 182 | [self integrityCheck]; 183 | } 184 | 185 | - (void)testMigrationPerformance { 186 | // This is an example of a performance test case. 187 | [self measureBlock:^{ 188 | [self migrateStore]; 189 | }]; 190 | } 191 | 192 | - (void) testMigratedStore { 193 | [self migrateStore]; 194 | NSManagedObject *projectStatusUpdate = [self integrityCheck]; 195 | 196 | NSManagedObject *taskStatus = [[projectStatusUpdate valueForKey:@"taskStatus"] anyObject]; 197 | 198 | [taskStatus setValue:@"A-Task-Update-Text" forKey:@"updateText"]; // will fail if not correctly migrated 199 | 200 | NSManagedObject *task = [taskStatus valueForKey:@"task"]; 201 | 202 | [task setValue:@1 forKey:@"newAttribute"]; // will fail if not correctly migrated 203 | } 204 | 205 | 206 | @end 207 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Tests/PasswordTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // PasswordTests.m 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 18/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "EncryptedStore.h" 12 | 13 | #import 14 | 15 | static BOOL const UseEncryptedStore = YES; 16 | 17 | static NSString *const CorrectPassword = @"CorrectPassword"; 18 | static NSString *const IncorrectPassword = @"IncorrectPassword"; 19 | 20 | @interface PasswordTests : XCTestCase 21 | 22 | @end 23 | 24 | @implementation PasswordTests { 25 | __strong NSPersistentStoreCoordinator *coordinator; 26 | } 27 | 28 | + (NSBundle *)bundle { 29 | return [NSBundle bundleForClass:self]; 30 | } 31 | 32 | + (NSManagedObjectModel *)model { 33 | return [NSManagedObjectModel mergedModelFromBundles:@[ [self bundle] ]]; 34 | } 35 | 36 | + (NSURL *)databaseURL { 37 | 38 | NSFileManager *fileManager = [NSFileManager defaultManager]; 39 | 40 | NSError *error = nil; 41 | NSURL *applicationDocumentsDirectory = [fileManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; 42 | 43 | NSAssert(applicationDocumentsDirectory, @"Unable to get the Documents directory: %@", error); 44 | 45 | return [applicationDocumentsDirectory URLByAppendingPathComponent:@"database-password_tests.sqlite"]; 46 | } 47 | 48 | - (NSPersistentStore *)openDatabaseWithPassword:(NSString *)password error:(NSError *__autoreleasing*)error 49 | { 50 | NSURL *URL; 51 | 52 | // get the model 53 | NSManagedObjectModel *model = [[self class] model]; 54 | 55 | // get the coordinator 56 | coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; 57 | 58 | // add store 59 | NSDictionary *options; 60 | if (password) { 61 | options = @{ 62 | EncryptedStorePassphraseKey : password 63 | }; 64 | } else { 65 | options = nil; 66 | } 67 | URL = [[self class] databaseURL]; 68 | NSLog(@"Working with database at URL: %@", URL); 69 | 70 | NSString *storeType = UseEncryptedStore ? EncryptedStoreType : NSSQLiteStoreType; 71 | 72 | NSPersistentStore *store; 73 | store = [coordinator 74 | addPersistentStoreWithType:storeType 75 | configuration:nil 76 | URL:URL 77 | options:options 78 | error:error]; 79 | 80 | return store; 81 | } 82 | 83 | - (void)setUp 84 | { 85 | [super setUp]; 86 | 87 | [[NSFileManager defaultManager] removeItemAtURL:[[self class] databaseURL] error:nil]; 88 | } 89 | 90 | - (void)tearDown 91 | { 92 | [[NSFileManager defaultManager] removeItemAtURL:[[self class] databaseURL] error:nil]; 93 | 94 | [super tearDown]; 95 | } 96 | 97 | - (void)cleanUp:(NSPersistentStore *)store 98 | { 99 | if (store) { 100 | [coordinator removePersistentStore:store error:nil]; 101 | store = nil; 102 | } 103 | coordinator = nil; 104 | } 105 | 106 | - (void)test_creatingDBAndOpeningWithCorrectPassword 107 | { 108 | NSError *error; 109 | NSPersistentStore *store = [self openDatabaseWithPassword:CorrectPassword error:&error]; 110 | 111 | XCTAssertNotNil(store, @"Nil store: %@", error); 112 | [self cleanUp:store]; 113 | 114 | store = [self openDatabaseWithPassword:CorrectPassword error:&error]; 115 | 116 | XCTAssertNotNil(store, @"Nil store: %@", error); 117 | [self cleanUp:store]; 118 | } 119 | 120 | - (void)test_creatingDBAndOpeningWithIncorrectPassword 121 | { 122 | NSError *error; 123 | NSPersistentStore *store = [self openDatabaseWithPassword:CorrectPassword error:&error]; 124 | 125 | XCTAssertNotNil(store, @"Nil store: %@", error); 126 | [self cleanUp:store]; 127 | 128 | store = [self openDatabaseWithPassword:IncorrectPassword error:&error]; 129 | 130 | XCTAssertNil(store, @"Nil context"); 131 | XCTAssertEqualObjects(error.domain, EncryptedStoreErrorDomain, @"Incorrect error domain"); 132 | XCTAssertEqual(error.code, EncryptedStoreErrorIncorrectPasscode, @"Incorrect error code"); 133 | 134 | NSError *sqliteError = error.userInfo[NSUnderlyingErrorKey]; 135 | XCTAssertNotNil(sqliteError, @"Nil SQLite error"); 136 | XCTAssertEqualObjects(sqliteError.domain, NSSQLiteErrorDomain, @"Incorrect error SQLite error domain"); 137 | XCTAssertEqual(sqliteError.code, (NSInteger)SQLITE_NOTADB, @"Incorrect error SQLite error code"); 138 | [self cleanUp:store]; 139 | 140 | // Try again once more to be sure it still opens 141 | store = [self openDatabaseWithPassword:CorrectPassword error:&error]; 142 | 143 | XCTAssertNotNil(store, @"Nil store: %@", error); 144 | [self cleanUp:store]; 145 | } 146 | 147 | #pragma mark - Empty password 148 | 149 | - (void)test_creatingDBAndOpeningWithEmptyPassword 150 | { 151 | NSError *error; 152 | NSPersistentStore *store = [self openDatabaseWithPassword:@"" error:&error]; 153 | 154 | XCTAssertNotNil(store, @"Nil store: %@", error); 155 | [self cleanUp:store]; 156 | 157 | store = [self openDatabaseWithPassword:@"" error:&error]; 158 | 159 | XCTAssertNotNil(store, @"Nil store: %@", error); 160 | [self cleanUp:store]; 161 | } 162 | 163 | - (void)test_creatingDBAndOpeningWithNilPassword 164 | { 165 | NSError *error; 166 | NSPersistentStore *store = [self openDatabaseWithPassword:nil error:&error]; 167 | 168 | XCTAssertNotNil(store, @"Nil store: %@", error); 169 | [self cleanUp:store]; 170 | 171 | store = [self openDatabaseWithPassword:nil error:&error]; 172 | 173 | XCTAssertNotNil(store, @"Nil store: %@", error); 174 | [self cleanUp:store]; 175 | } 176 | 177 | /// Creates with empty string, tries with incorrect string, tries again with nil 178 | - (void)test_creatingEmptyPasswordDBAndOpeningWithIncorrectPassword 179 | { 180 | NSError *error; 181 | NSPersistentStore *store = [self openDatabaseWithPassword:@"" error:&error]; 182 | 183 | XCTAssertNotNil(store, @"Nil store: %@", error); 184 | [self cleanUp:store]; 185 | 186 | store = [self openDatabaseWithPassword:IncorrectPassword error:&error]; 187 | 188 | XCTAssertNil(store, @"Nil context"); 189 | XCTAssertEqualObjects(error.domain, EncryptedStoreErrorDomain, @"Incorrect error domain"); 190 | XCTAssertEqual(error.code, EncryptedStoreErrorIncorrectPasscode, @"Incorrect error code"); 191 | 192 | NSError *sqliteError = error.userInfo[NSUnderlyingErrorKey]; 193 | XCTAssertNotNil(sqliteError, @"Nil SQLite error"); 194 | XCTAssertEqualObjects(sqliteError.domain, NSSQLiteErrorDomain, @"Incorrect error SQLite error domain"); 195 | XCTAssertEqual(sqliteError.code, (NSInteger)SQLITE_NOTADB, @"Incorrect error SQLite error code"); 196 | [self cleanUp:store]; 197 | 198 | // Try again once more to be sure it still opens 199 | store = [self openDatabaseWithPassword:nil error:&error]; 200 | 201 | XCTAssertNotNil(store, @"Nil store: %@", error); 202 | [self cleanUp:store]; 203 | } 204 | 205 | - (void)test_storeHelperMethodsWithEmptyPassword 206 | { 207 | NSPersistentStoreCoordinator *coord; 208 | 209 | // Dict 210 | NSDictionary *dictOpts = @{EncryptedStorePassphraseKey : @""}; 211 | XCTAssertNoThrowSpecificNamed(coord = [EncryptedStore makeStoreWithOptions:dictOpts managedObjectModel:[[self class] model]], NSException, NSInternalInconsistencyException, @"Assert was triggered as the created store was nil"); 212 | XCTAssertEqual([[coord persistentStores] count], (NSUInteger) 1, @"There should be one persistent store attached to the coordinator"); 213 | 214 | // Struct 215 | EncryptedStoreOptions structOpts; 216 | structOpts.database_location = NULL; 217 | structOpts.cache_size = 0; 218 | structOpts.passphrase = ""; 219 | XCTAssertNoThrowSpecificNamed(coord = [EncryptedStore makeStoreWithStructOptions:&structOpts managedObjectModel:[[self class] model]], NSException, NSInternalInconsistencyException, @"Assert was triggered as the created store was nil"); 220 | XCTAssertEqual([[coord persistentStores] count], (NSUInteger) 1, @"There should be one persistent store attached to the coordinator"); 221 | 222 | // Passcode 223 | XCTAssertNoThrowSpecificNamed(coord = [EncryptedStore makeStore:[[self class] model] passcode:@""], NSException, NSInternalInconsistencyException, @"Assert was triggered as the created store was nil"); 224 | XCTAssertEqual([[coord persistentStores] count], (NSUInteger) 1, @"There should be one persistent store attached to the coordinator"); 225 | } 226 | 227 | - (void)test_storeHelperMethodsWithNilPassword 228 | { 229 | NSPersistentStoreCoordinator *coord; 230 | 231 | // Dict 232 | NSDictionary *dictOpts = @{}; 233 | XCTAssertNoThrowSpecificNamed(coord = [EncryptedStore makeStoreWithOptions:dictOpts managedObjectModel:[[self class] model]], NSException, NSInternalInconsistencyException, @"Assert was triggered as the created store was nil"); 234 | XCTAssertEqual([[coord persistentStores] count], (NSUInteger) 1, @"There should be one persistent store attached to the coordinator"); 235 | 236 | // Struct 237 | EncryptedStoreOptions structOpts; 238 | structOpts.database_location = NULL; 239 | structOpts.cache_size = 0; 240 | structOpts.passphrase = NULL; 241 | XCTAssertNoThrowSpecificNamed(coord = [EncryptedStore makeStoreWithStructOptions:&structOpts managedObjectModel:[[self class] model]], NSException, NSInternalInconsistencyException, @"Assert was triggered as the created store was nil"); 242 | XCTAssertEqual([[coord persistentStores] count], (NSUInteger) 1, @"There should be one persistent store attached to the coordinator"); 243 | 244 | // Passcode 245 | XCTAssertNoThrowSpecificNamed(coord = [EncryptedStore makeStore:[[self class] model] passcode:nil], NSException, NSInternalInconsistencyException, @"Assert was triggered as the created store was nil"); 246 | XCTAssertEqual([[coord persistentStores] count], (NSUInteger) 1, @"There should be one persistent store attached to the coordinator"); 247 | } 248 | 249 | @end 250 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Tests/RelationTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // RelationTests.m 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 31/08/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "EncryptedStore.h" 12 | 13 | #import "ISDModelCategories.h" 14 | #import "ISDRoot.h" 15 | #import "ISDChildA.h" 16 | #import "ISDChildB.h" 17 | 18 | /* 19 | 20 | Flip between 0 and 1 to use the system SQLite store and custom incremental 21 | store subclass respectively. 22 | 23 | */ 24 | #define USE_ENCRYPTED_STORE 1 25 | 26 | @interface RelationTests : XCTestCase 27 | 28 | @end 29 | 30 | @implementation RelationTests { 31 | __strong NSPersistentStoreCoordinator *coordinator; 32 | __strong NSManagedObjectContext *context; 33 | } 34 | 35 | +(NSURL *)databaseURL { 36 | 37 | NSFileManager *fileManager = [NSFileManager defaultManager]; 38 | 39 | NSError *error = nil; 40 | NSURL *applicationDocumentsDirectory = [fileManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; 41 | 42 | NSAssert(applicationDocumentsDirectory, @"Unable to get the Documents directory: %@", error); 43 | 44 | NSString *name = [NSString stringWithFormat:@"database-%@", [NSStringFromClass([self class]) lowercaseString]]; 45 | #if USE_ENCRYPTED_STORE 46 | name = [name stringByAppendingString:@"-encrypted"]; 47 | #endif 48 | name = [name stringByAppendingString:@".sqlite"]; 49 | 50 | return [applicationDocumentsDirectory URLByAppendingPathComponent:name]; 51 | } 52 | 53 | + (void)deleteDatabase { 54 | NSFileManager *manager = [NSFileManager defaultManager]; 55 | [manager removeItemAtURL:[self databaseURL] error:nil]; 56 | } 57 | 58 | -(void)createCoordinator 59 | { 60 | NSURL *URL; 61 | 62 | NSURL *modelURL = [[NSBundle bundleForClass:[EncryptedStore class]] URLForResource:@"ClassModel" withExtension:@"momd"]; 63 | // get the model 64 | NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 65 | 66 | // get the coordinator 67 | coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; 68 | 69 | // add store 70 | NSDictionary *options = @{ 71 | EncryptedStorePassphraseKey : @"DB_KEY_HERE" 72 | }; 73 | URL = [[self class] databaseURL]; 74 | NSLog(@"Working with database at URL: %@", URL); 75 | NSError *error = nil; 76 | 77 | NSString *storeType = nil; 78 | #if USE_ENCRYPTED_STORE 79 | storeType = EncryptedStoreType; 80 | #else 81 | storeType = NSSQLiteStoreType; 82 | #endif 83 | 84 | NSPersistentStore *store = [coordinator 85 | addPersistentStoreWithType:storeType 86 | configuration:nil 87 | URL:URL 88 | options:options 89 | error:&error]; 90 | 91 | XCTAssertNotNil(store, @"Unable to add persistent store: %@", error); 92 | 93 | // load context 94 | context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType]; 95 | [context setPersistentStoreCoordinator:coordinator]; 96 | XCTAssertNotNil(context, @"Unable to create context.\n%@", error); 97 | 98 | // log 99 | NSLog(@"Working with database at %@", [URL path]); 100 | } 101 | 102 | -(void)resetCoordinator 103 | { 104 | if (coordinator) { 105 | NSError *error; 106 | XCTAssertTrue([coordinator removePersistentStore:[coordinator persistentStoreForURL:[[self class] databaseURL]] error:&error], @"Could not remove persistent store: %@", error); 107 | coordinator = nil; 108 | } 109 | context = nil; 110 | } 111 | 112 | /// Creates the CD stack and all the objects returning the root object 113 | -(void)createObjectGraph 114 | { 115 | [self createCoordinator]; 116 | 117 | // insert root 118 | ISDRoot *root = [ISDRoot insertInManagedObjectContext:context]; 119 | root.name = @"root"; 120 | 121 | ///////////////// 122 | // One-to-many // 123 | ///////////////// 124 | 125 | // Insert child A 126 | ISDChildA *childAOneToMany = [ISDChildA insertInManagedObjectContext:context]; 127 | childAOneToMany.attributeA = @"String for child A - 1"; 128 | childAOneToMany.oneToManyInverse = root; 129 | 130 | childAOneToMany = [ISDChildA insertInManagedObjectContext:context]; 131 | childAOneToMany.attributeA = @"String for child A - 2"; 132 | childAOneToMany.oneToManyInverse = root; 133 | 134 | // Insert child B 135 | ISDChildB *childBOneToMany = [ISDChildB insertInManagedObjectContext:context]; 136 | childBOneToMany.attributeB = @"String for child B - 1"; 137 | childBOneToMany.oneToManyInverse = root; 138 | 139 | //////////////// 140 | // One-to-one // 141 | //////////////// 142 | 143 | // Insert child A 144 | ISDChildA *childAOneToOne = [ISDChildA insertInManagedObjectContext:context]; 145 | childAOneToOne.attributeA = @"String for child A - 3"; 146 | childAOneToOne.oneToOneInverse = root; 147 | 148 | ////////////////// 149 | // Many-to-Many // 150 | ////////////////// 151 | ISDRoot *manyRoot = [ISDRoot insertInManagedObjectContext:context]; 152 | manyRoot.name = @"manyRoot"; 153 | 154 | // Insert child A 155 | ISDChildA *childAManyToMany = [ISDChildA insertInManagedObjectContext:context]; 156 | childAManyToMany.attributeA = @"String for child A - 4"; 157 | [childAManyToMany addManyToManyInverseObject:root]; 158 | [childAManyToMany addManyToManyInverseObject:manyRoot]; 159 | 160 | childAManyToMany = [ISDChildA insertInManagedObjectContext:context]; 161 | childAManyToMany.attributeA = @"String for child A - 5"; 162 | [childAManyToMany addManyToManyInverseObject:root]; 163 | [childAManyToMany addManyToManyInverseObject:manyRoot]; 164 | 165 | // Insert child B 166 | ISDChildB *childBManyToMany = [ISDChildB insertInManagedObjectContext:context]; 167 | childBManyToMany.attributeB = @"String for child B - 2"; 168 | [childBManyToMany addManyToManyInverseObject:root]; 169 | [childBManyToMany addManyToManyInverseObject:manyRoot]; 170 | 171 | childBManyToMany = [ISDChildB insertInManagedObjectContext:context]; 172 | childBManyToMany.attributeB = @"String for child B - 3"; 173 | [childBManyToMany addManyToManyInverseObject:root]; 174 | [childBManyToMany addManyToManyInverseObject:manyRoot]; 175 | 176 | childBManyToMany = [ISDChildB insertInManagedObjectContext:context]; 177 | childBManyToMany.attributeB = @"String for child B - 4"; 178 | [childBManyToMany addManyToManyInverseObject:root]; 179 | [childBManyToMany addManyToManyInverseObject:manyRoot]; 180 | 181 | ////////////////////////// 182 | // Multiple One-to-many // 183 | ////////////////////////// 184 | 185 | // Insert child A 186 | ISDChildA *childAMultipleOneToMany = [ISDChildA insertInManagedObjectContext:context]; 187 | childAMultipleOneToMany.attributeA = @"String for child A - 6"; 188 | childAMultipleOneToMany.multipleOneToMany = root; 189 | 190 | // Insert child B 191 | ISDChildB *childBMultipleOneToMany = [ISDChildB insertInManagedObjectContext:context]; 192 | childBMultipleOneToMany.attributeB = @"String for child B - 5"; 193 | childBMultipleOneToMany.multipleOneToMany = root; 194 | 195 | childBMultipleOneToMany = [ISDChildB insertInManagedObjectContext:context]; 196 | childBMultipleOneToMany.attributeB = @"String for child B - 6"; 197 | childBMultipleOneToMany.multipleOneToMany = root; 198 | 199 | // Save 200 | NSError *error = nil; 201 | BOOL save = [context save:&error]; 202 | XCTAssertTrue(save, @"Unable to perform save.\n%@", error); 203 | 204 | // Test one-to-many from cache 205 | { 206 | NSSet *oneToManyRelations = root.oneToMany; 207 | XCTAssertEqual([oneToManyRelations count], (NSUInteger)3, @"The number of one-to-many relations is wrong."); 208 | 209 | // Here the counts are correct as the objects are exactly the same as we just inserted 210 | NSSet *childrenA = [oneToManyRelations filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", [ISDChildA entityName]]]; 211 | NSSet *childrenB = [oneToManyRelations filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", [ISDChildB entityName]]]; 212 | 213 | // This should be correct as this is how we entered the values in above 214 | XCTAssertEqual([childrenA count], (NSUInteger)2, @"Wrong ChildA count"); 215 | XCTAssertEqual([childrenB count], (NSUInteger)1, @"Wrong ChildB count"); 216 | // Just for fun check the objects 217 | XCTAssertTrue([childrenA containsObject:childAOneToMany], @"Inserted ChildA isn't in the set"); 218 | XCTAssertTrue([childrenB anyObject] == childBOneToMany, @"Inserted ChildB object isn't the same"); 219 | } 220 | 221 | // Test one-to-one from cache 222 | { 223 | XCTAssertTrue(root.oneToOne == childAOneToOne, @"Inserted one-to-one ChildA isn't the same"); 224 | } 225 | 226 | // Test many-to-many from cache 227 | { 228 | NSSet *manyToManyRelations = root.manyToMany; 229 | XCTAssertEqual([manyToManyRelations count], (NSUInteger)5, @"The number of many-to-many relations is wrong."); 230 | 231 | // Here the counts are correct as the objects are exactly the same as we just inserted 232 | NSSet *childrenA = [manyToManyRelations filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", [ISDChildA entityName]]]; 233 | NSSet *childrenB = [manyToManyRelations filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", [ISDChildB entityName]]]; 234 | 235 | // This should be correct as this is how we entered the values in above 236 | XCTAssertEqual([childrenA count], (NSUInteger)2, @"Wrong ChildA count"); 237 | XCTAssertEqual([childrenB count], (NSUInteger)3, @"Wrong ChildB count"); 238 | // Just for fun check the objects 239 | XCTAssertTrue([childrenA containsObject:childAManyToMany], @"Inserted ChildA isn't in the many-to-many set"); 240 | XCTAssertTrue([childrenB containsObject:childBManyToMany], @"Inserted ChildB isn't in the many-to-many set"); 241 | } 242 | 243 | // Test multiple one-to-many from cache 244 | { 245 | NSSet *oneToManyChildA = root.multipleOneToManyChildA; 246 | NSSet *oneToManyChildB = root.multipleOneToManyChildB; 247 | XCTAssertEqual([oneToManyChildA count], (NSUInteger)1, @"The number of multiple one-to-many child A relations is wrong."); 248 | XCTAssertEqual([oneToManyChildB count], (NSUInteger)2, @"The number of multiple one-to-many child B relations is wrong."); 249 | 250 | // Check the objects 251 | XCTAssertTrue([oneToManyChildA anyObject] == childAMultipleOneToMany, @"Inserted ChildA object isn't the same"); 252 | XCTAssertTrue([oneToManyChildB containsObject:childBMultipleOneToMany], @"Inserted ChildB isn't in the set"); 253 | } 254 | } 255 | 256 | -(ISDRoot *)fetchRootObject 257 | { 258 | return [self fetchRootObjectByName:@"root"]; 259 | } 260 | 261 | -(ISDRoot *)fetchManyRootObject 262 | { 263 | return [self fetchRootObjectByName:@"manyRoot"]; 264 | } 265 | 266 | -(ISDRoot *)fetchRootObjectByName:(NSString *)name 267 | { 268 | NSError *error = nil; 269 | NSFetchRequest *request = [ISDRoot fetchRequest]; 270 | request.predicate = [NSPredicate predicateWithFormat:@"name == %@", name]; 271 | NSArray *results = [context executeFetchRequest:request error:&error]; 272 | XCTAssertNotNil(results, @"Could not execute fetch request."); 273 | XCTAssertEqual([results count], (NSUInteger)1, @"The number of root objects is wrong."); 274 | ISDRoot *root = [results firstObject]; 275 | XCTAssertEqualObjects(root.name, name, @"The name of the root object is wrong. It should be '%@' but it is '%@'", name, [root.name copy]); 276 | return root; 277 | } 278 | 279 | -(void)setUp 280 | { 281 | [super setUp]; 282 | [[self class] deleteDatabase]; 283 | 284 | [self createObjectGraph]; 285 | } 286 | 287 | -(void)tearDown 288 | { 289 | [self resetCoordinator]; 290 | [[self class] deleteDatabase]; 291 | [super tearDown]; 292 | } 293 | 294 | -(void)testFetchingOneToManyFromCache 295 | { 296 | [self checkOneToManyWithChildACount:2 childBCount:1]; 297 | } 298 | 299 | -(void)testFetchingOneToManyFromDatabase 300 | { 301 | // Make sure we're loading directly from DB 302 | [self resetCoordinator]; 303 | [self createCoordinator]; 304 | 305 | [self checkOneToManyWithChildACount:2 childBCount:1]; 306 | } 307 | 308 | -(void)testFetchingOneToOneFromCache 309 | { 310 | [self checkOneToOneWithChildA:YES childB:NO]; 311 | } 312 | 313 | -(void)testFetchingOneToOneFromDatabase 314 | { 315 | // Make sure we're loading directly from DB 316 | [self resetCoordinator]; 317 | [self createCoordinator]; 318 | 319 | [self checkOneToOneWithChildA:YES childB:NO]; 320 | } 321 | 322 | -(void)testFetchingOneToOneNilFromCache 323 | { 324 | [self checkOneToOneNil]; 325 | } 326 | 327 | -(void)testFetchingOneToOneNilFromDatabase 328 | { 329 | // Make sure we're loading directly from DB 330 | [self resetCoordinator]; 331 | [self createCoordinator]; 332 | 333 | [self checkOneToOneNil]; 334 | } 335 | 336 | -(void)testFetchingManyToManyFromCache 337 | { 338 | [self checkManyToManyWithChildACount:2 childBCount:3]; 339 | } 340 | 341 | -(void)testFetchingManyToManyFromDatabase 342 | { 343 | // Make sure we're loading directly from DB 344 | [self resetCoordinator]; 345 | [self createCoordinator]; 346 | 347 | [self checkManyToManyWithChildACount:2 childBCount:3]; 348 | } 349 | 350 | - (void)testDeleteAllManyToManyFromDatabase 351 | { 352 | // Remove all many to many relationships 353 | ISDRoot *fetchedRoot = [self fetchRootObject]; 354 | [fetchedRoot removeManyToMany:fetchedRoot.manyToMany]; 355 | ISDRoot *fetchedManyRoot = [self fetchManyRootObject]; 356 | [fetchedManyRoot removeManyToMany:fetchedManyRoot.manyToMany]; 357 | 358 | XCTAssertEqual(0, fetchedRoot.manyToMany.count + fetchedManyRoot.manyToMany.count, @"The total number of many-to-many relationships after removing all of them in memory should be 0"); 359 | 360 | // Save into database 361 | NSError *error = nil; 362 | BOOL save = [context save:&error]; 363 | XCTAssertTrue(save, @"Unable to perform save.\n%@", error); 364 | 365 | // Invalidate any existing NSManagedObject 366 | [context reset]; 367 | fetchedRoot = nil; 368 | fetchedManyRoot = nil; 369 | 370 | // Verify that relationships have been removed from database 371 | fetchedRoot = [self fetchRootObject]; 372 | fetchedManyRoot = [self fetchManyRootObject]; 373 | XCTAssertEqual(0, fetchedRoot.manyToMany.count + fetchedManyRoot.manyToMany.count, @"The total number of relationships when fetching from database after removing all of them & saving should be 0"); 374 | } 375 | 376 | /** 377 | Multiple one-to-many is designed to test the case where one entity (Root) has two one-to-many 378 | relationships that are queried using a shared attribute. 379 | */ 380 | -(void)testFetchingMultipleOneToManyFromDatabase 381 | { 382 | // Make sure we're loading directly from DB 383 | [self resetCoordinator]; 384 | [self createCoordinator]; 385 | 386 | [self checkMultipleOneToManyWithChildACount:1 childBCount:2]; 387 | } 388 | 389 | #pragma mark - Check methods 390 | 391 | /// Checks that the root object has the correct number of one-to-many relational ChildA and ChildB objects 392 | -(void)checkOneToManyWithChildACount:(NSUInteger)childACount childBCount:(NSUInteger)childBCount 393 | { 394 | ISDRoot *fetchedRoot = [self fetchRootObject]; 395 | NSSet *relations = fetchedRoot.oneToMany; 396 | XCTAssertEqual([relations count], childACount + childBCount, @"The total number of oneToMany objects is wrong."); 397 | 398 | NSSet *childrenA = [relations filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", [ISDChildA entityName]]]; 399 | NSSet *childrenB = [relations filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", [ISDChildB entityName]]]; 400 | 401 | XCTAssertEqual([childrenA count], childACount, @"Wrong ChildA count"); 402 | XCTAssertEqual([childrenB count], childBCount, @"Wrong ChildB count"); 403 | } 404 | 405 | /// Checks that the root object has the correct one-to-one relational ChildA/ChildB 406 | -(void)checkOneToOneWithChildA:(BOOL)childA childB:(BOOL)childB 407 | { 408 | ISDRoot *fetchedRoot = [self fetchRootObject]; 409 | ISDParent *child = fetchedRoot.oneToOne; 410 | XCTAssertNotNil(child, @"Nil one-to-one relation"); 411 | 412 | if (childA) { 413 | XCTAssertEqualObjects([child.entity name], [ISDChildA entityName], @"One-to-one child is of wrong entity"); 414 | if ([child respondsToSelector:@selector(attributeA)]) { 415 | XCTAssertTrue([((ISDChildA *) child).attributeA hasPrefix:@"String for child A"], @"One-to-one childs attribute does not start with the correct prefix, got: %@, expecting prefix: %@", ((ISDChildA *) child).attributeA, @"String for child A"); 416 | } else { 417 | XCTFail(@"One-to-one child does not have the correct attribute: %@", child); 418 | } 419 | XCTAssertTrue([child isKindOfClass:[ISDChildA class]], @"One-to-one child is of wrong class, got: %@, expecting: %@", NSStringFromClass([child class]), NSStringFromClass([ISDChildA class])); 420 | XCTAssertFalse([child isKindOfClass:[ISDChildB class]], @"One-to-one child is of wrong class, got: %@, expecting: %@", NSStringFromClass([child class]), NSStringFromClass([ISDChildA class])); 421 | } 422 | if (childB) { 423 | XCTAssertEqualObjects([child.entity name], [ISDChildB entityName], @"One-to-one child is of wrong entity"); 424 | if ([child respondsToSelector:@selector(attributeB)]) { 425 | XCTAssertTrue([((ISDChildB *) child).attributeB hasPrefix:@"String for child B"], @"One-to-one childs attribute does not start with the correct prefix, got: %@, expecting prefix: %@", ((ISDChildB *) child).attributeB, @"String for child B"); 426 | } else { 427 | XCTFail(@"One-to-one child does not have the correct attribute: %@", child); 428 | } 429 | XCTAssertTrue([child isKindOfClass:[ISDChildB class]], @"One-to-one child is of wrong class, got: %@, expecting: %@", NSStringFromClass([child class]), NSStringFromClass([ISDChildB class])); 430 | XCTAssertFalse([child isKindOfClass:[ISDChildA class]], @"One-to-one child is of wrong class, got: %@, expecting: %@", NSStringFromClass([child class]), NSStringFromClass([ISDChildB class])); 431 | } 432 | } 433 | 434 | -(void)checkOneToOneNil 435 | { 436 | ISDRoot *fetchedRoot = [self fetchRootObject]; 437 | XCTAssert(fetchedRoot.oneToOneNil == nil, @"We didn't set it, should be nil"); 438 | } 439 | 440 | /// Checks that the root object has the correct number of many-to-many relational ChildA and ChildB objects 441 | -(void)checkManyToManyWithChildACount:(NSUInteger)childACount childBCount:(NSUInteger)childBCount 442 | { 443 | ISDRoot *fetchedRoot = [self fetchRootObject]; 444 | NSSet *relations = fetchedRoot.manyToMany; 445 | XCTAssertEqual([relations count], childACount + childBCount, @"The total number of oneToMany objects is wrong."); 446 | 447 | NSSet *childrenA = [relations filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", [ISDChildA entityName]]]; 448 | NSSet *childrenB = [relations filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"entity.name == %@", [ISDChildB entityName]]]; 449 | 450 | XCTAssertEqual([childrenA count], childACount, @"Wrong ChildA count"); 451 | XCTAssertEqual([childrenB count], childBCount, @"Wrong ChildB count"); 452 | } 453 | 454 | /// Checks that the root object has the correct number of multiple one-to-many relational ChildA and ChildB objects 455 | -(void)checkMultipleOneToManyWithChildACount:(NSUInteger)childACount childBCount:(NSUInteger)childBCount 456 | { 457 | ISDRoot *fetchedRoot = [self fetchRootObject]; 458 | NSSet *multipleChildA = fetchedRoot.multipleOneToManyChildA; 459 | NSSet *multipleChildB = fetchedRoot.multipleOneToManyChildB; 460 | 461 | XCTAssertEqual([multipleChildA count], childACount, @"Wrong ChildA count"); 462 | XCTAssertEqual([multipleChildB count], childBCount, @"Wrong ChildB count"); 463 | } 464 | 465 | @end 466 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Tests/SubEntityTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SubEntityTests.m 3 | // Incremental Store 4 | // 5 | // Created by Richard Hodgkins on 23/09/2014. 6 | // Copyright (c) 2014 Caleb Davenport. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "EncryptedStore.h" 12 | 13 | /* 14 | 15 | Flip between 0 and 1 to use the system SQLite store and custom incremental 16 | store subclass respectively. 17 | 18 | */ 19 | #define USE_ENCRYPTED_STORE 1 20 | 21 | @interface SubEntityTests : XCTestCase 22 | 23 | @end 24 | 25 | @implementation SubEntityTests { 26 | __strong NSPersistentStoreCoordinator *coordinator; 27 | __strong NSManagedObjectContext *context; 28 | } 29 | 30 | +(NSURL *)databaseURL { 31 | 32 | NSFileManager *fileManager = [NSFileManager defaultManager]; 33 | 34 | NSError *error = nil; 35 | NSURL *applicationDocumentsDirectory = [fileManager URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:&error]; 36 | 37 | NSAssert(applicationDocumentsDirectory, @"Unable to get the Documents directory: %@", error); 38 | 39 | NSString *name = [NSString stringWithFormat:@"database-%@", [NSStringFromClass([self class]) lowercaseString]]; 40 | #if USE_ENCRYPTED_STORE 41 | name = [name stringByAppendingString:@"-encrypted"]; 42 | #endif 43 | name = [name stringByAppendingString:@".sqlite"]; 44 | 45 | return [applicationDocumentsDirectory URLByAppendingPathComponent:name]; 46 | } 47 | 48 | + (void)deleteDatabase { 49 | NSFileManager *manager = [NSFileManager defaultManager]; 50 | [manager removeItemAtURL:[self databaseURL] error:nil]; 51 | } 52 | 53 | -(void)createCoordinator 54 | { 55 | NSURL *URL; 56 | 57 | NSURL *modelURL = [[NSBundle bundleForClass:[EncryptedStore class]] URLForResource:@"SubEntitiesModel" withExtension:@"momd"]; 58 | // get the model 59 | NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; 60 | 61 | // get the coordinator 62 | coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model]; 63 | 64 | // add store 65 | NSDictionary *options = @{ 66 | EncryptedStorePassphraseKey : @"DB_KEY_HERE" 67 | }; 68 | URL = [[self class] databaseURL]; 69 | NSLog(@"Working with database at URL: %@", URL); 70 | NSError *error = nil; 71 | 72 | NSString *storeType = nil; 73 | #if USE_ENCRYPTED_STORE 74 | storeType = EncryptedStoreType; 75 | #else 76 | storeType = NSSQLiteStoreType; 77 | #endif 78 | 79 | NSPersistentStore *store = [coordinator 80 | addPersistentStoreWithType:storeType 81 | configuration:nil 82 | URL:URL 83 | options:options 84 | error:&error]; 85 | 86 | XCTAssertNotNil(store, @"Unable to add persistent store: %@", error); 87 | 88 | // load context 89 | context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType]; 90 | [context setPersistentStoreCoordinator:coordinator]; 91 | XCTAssertNotNil(context, @"Unable to create context.\n%@", error); 92 | 93 | // log 94 | NSLog(@"Working with database at %@", [URL path]); 95 | } 96 | 97 | -(void)resetCoordinator 98 | { 99 | if (coordinator) { 100 | NSError *error; 101 | XCTAssertTrue([coordinator removePersistentStore:[coordinator persistentStoreForURL:[[self class] databaseURL]] error:&error], @"Could not remove persistent store: %@", error); 102 | coordinator = nil; 103 | } 104 | context = nil; 105 | } 106 | 107 | /// Creates the CD stack and all the objects returning the root object 108 | -(void)createObjectGraph 109 | { 110 | [self createCoordinator]; 111 | 112 | // Jobs 113 | NSManagedObject *fullJob = [NSEntityDescription insertNewObjectForEntityForName:@"FullJob" inManagedObjectContext:context]; 114 | [fullJob setValue:@"A-FullJob" forKey:@"name"]; 115 | [fullJob setValue:@(INT64_MAX) forKey:@"longNumber"]; 116 | NSManagedObject *lightweightJob = [NSEntityDescription insertNewObjectForEntityForName:@"LightweightJob" inManagedObjectContext:context]; 117 | [lightweightJob setValue:@"B-LightweightJob" forKey:@"name"]; 118 | 119 | // Update 120 | NSManagedObject *update = [NSEntityDescription insertNewObjectForEntityForName:@"JobStatusUpdate" inManagedObjectContext:context]; 121 | [update setValue:@"update-JobStatusUpdate" forKey:@"name"]; 122 | 123 | // Save 124 | NSError *error = nil; 125 | BOOL save = [context save:&error]; 126 | XCTAssertTrue(save, @"Unable to perform save.\n%@", error); 127 | } 128 | 129 | -(void)setUp 130 | { 131 | [super setUp]; 132 | [[self class] deleteDatabase]; 133 | 134 | [self createObjectGraph]; 135 | } 136 | 137 | -(void)tearDown 138 | { 139 | [self resetCoordinator]; 140 | [[self class] deleteDatabase]; 141 | [super tearDown]; 142 | } 143 | 144 | -(void)testFetchingObjectsFromCache 145 | { 146 | [self checkObjects]; 147 | } 148 | 149 | -(void)testFetchingObjectsFromDatabase 150 | { 151 | // Make sure we're loading directly from DB 152 | [self resetCoordinator]; 153 | [self createCoordinator]; 154 | 155 | [self checkObjects]; 156 | } 157 | 158 | -(void)checkObjects 159 | { 160 | NSError *error; 161 | 162 | { 163 | // Jobs 164 | NSFetchRequest *jobsRequest = [NSFetchRequest fetchRequestWithEntityName:@"Job"]; 165 | jobsRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]]; 166 | 167 | NSArray *jobs = [context executeFetchRequest:jobsRequest error:&error]; 168 | XCTAssertNotNil(jobs, @"Error fetching jobs: %@", error); 169 | XCTAssertNil(error, @"Error"); 170 | 171 | XCTAssertEqual([jobs count], (NSUInteger)2, @"Incorrect number of jobs"); 172 | NSManagedObject *fullJob = [jobs firstObject]; 173 | XCTAssertNotNil(fullJob, @"No full job"); 174 | XCTAssertEqualObjects([fullJob entity].name, @"FullJob", @"Wrong full job entity type"); 175 | NSManagedObject *lightweightJob = [jobs lastObject]; 176 | XCTAssertNotNil(lightweightJob, @"No lightweight job"); 177 | XCTAssertEqualObjects([lightweightJob entity].name, @"LightweightJob", @"Wrong full job entity type"); 178 | 179 | XCTAssertEqualObjects([fullJob valueForKey:@"name"], @"A-FullJob", @"name property not correct"); 180 | XCTAssertEqualObjects([fullJob valueForKey:@"longNumber"], @((long long)INT64_MAX), @"longNumber property value not correct"); 181 | XCTAssertEqual([[fullJob valueForKey:@"longNumber"] longLongValue], (long long)INT64_MAX, @"longNumber property primitive value not correct"); 182 | 183 | XCTAssertEqualObjects([lightweightJob valueForKey:@"name"], @"B-LightweightJob", @"name property not correct"); 184 | } 185 | 186 | error = nil; 187 | { 188 | // Statuses 189 | NSFetchRequest *statusesRequest = [NSFetchRequest fetchRequestWithEntityName:@"BaseStatusUpdate"]; 190 | 191 | NSArray *statuses = [context executeFetchRequest:statusesRequest error:&error]; 192 | XCTAssertNotNil(statuses, @"Error fetching statuses: %@", error); 193 | XCTAssertNil(error, @"Error"); 194 | 195 | XCTAssertEqual([statuses count], (NSUInteger)1, @"Incorrect number of statuses"); 196 | NSManagedObject *status = [statuses firstObject]; 197 | XCTAssertNotNil(status, @"No status"); 198 | XCTAssertEqualObjects([status entity].name, @"JobStatusUpdate", @"Wrong full job entity type"); 199 | 200 | XCTAssertEqualObjects([status valueForKey:@"name"], @"update-JobStatusUpdate", @"name property not correct"); 201 | } 202 | } 203 | 204 | @end 205 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Tests/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 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #import 10 | #endif 11 | -------------------------------------------------------------------------------- /exampleProjects/IncrementalStore/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /stringOutput.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-imas/encrypted-core-data/b97ffaf2f19dad4d1558bc9b0668cc2e09d17347/stringOutput.jpg --------------------------------------------------------------------------------