├── CoreDataModelHelper ├── CoreDataModelHelper.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── dfreniche.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── dfreniche.xcuserdatad │ │ └── xcschemes │ │ ├── CoreDataModelHelper.xcscheme │ │ └── xcschememanagement.plist └── CoreDataModelHelper │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CoreDataModelHelper.xcdatamodeld │ ├── .xccurrentversion │ └── CoreDataModelHelper.xcdatamodel │ │ └── contents │ ├── Info.plist │ └── ViewController.swift ├── LICENSE.md ├── Modern-Core-Data-Playground.md ├── Modern-Core-Data-Playground.playground ├── Pages │ ├── Core Data Notifications.xcplaygroundpage │ │ └── Contents.swift │ ├── Custom Core Data Stack.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── Delete.xcplaygroundpage │ │ └── Contents.swift │ ├── Fetch Projects.xcplaygroundpage │ │ └── Contents.swift │ ├── Fetch Tasks.xcplaygroundpage │ │ └── Contents.swift │ ├── Generic Fetch.xcplaygroundpage │ │ └── Contents.swift │ ├── HowToUsePlaygrounds.xcplaygroundpage │ │ ├── Contents.swift │ │ ├── Resources │ │ │ ├── pages.gif │ │ │ ├── pages.png │ │ │ └── preview.gif │ │ └── timeline.xctimeline │ ├── Init Core Data the Easy way NSPersistentContainer.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ ├── Insert a Project with many tasks.xcplaygroundpage │ │ └── Contents.swift │ ├── Insert a Project.xcplaygroundpage │ │ └── Contents.swift │ ├── Intro.xcplaygroundpage │ │ └── Contents.swift │ ├── The Core Data Stack.xcplaygroundpage │ │ ├── Contents.swift │ │ └── timeline.xctimeline │ └── Undo management.xcplaygroundpage │ │ └── Contents.swift ├── Resources │ ├── CoreDataModelHelper.momd │ └── CoreDataModelHelper.xcdatamodeld │ │ ├── .xccurrentversion │ │ └── CoreDataModelHelper.xcdatamodel │ │ └── contents ├── Sources │ ├── Core Data Generated Files │ │ ├── Project+CoreDataClass.swift │ │ ├── Project+CoreDataProperties.swift │ │ ├── Task+CoreDataClass.swift │ │ └── Task+CoreDataProperties.swift │ ├── CountAllProjects.swift │ ├── FirstProject.swift │ ├── HWContainer.swift │ └── InsertSomeProjects.swift ├── contents.xcplayground └── playground.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ └── dfreniche.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── README.md └── img └── init-core-data.png /CoreDataModelHelper/CoreDataModelHelper.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 998802B31E590A6A00C807DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 998802B21E590A6A00C807DA /* AppDelegate.swift */; }; 11 | 998802B51E590A6A00C807DA /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 998802B41E590A6A00C807DA /* ViewController.swift */; }; 12 | 998802B81E590A6A00C807DA /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 998802B61E590A6A00C807DA /* Main.storyboard */; }; 13 | 998802BB1E590A6A00C807DA /* CoreDataModelHelper.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 998802B91E590A6A00C807DA /* CoreDataModelHelper.xcdatamodeld */; }; 14 | 998802BD1E590A6A00C807DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 998802BC1E590A6A00C807DA /* Assets.xcassets */; }; 15 | 998802C01E590A6A00C807DA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 998802BE1E590A6A00C807DA /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | 998802AF1E590A6A00C807DA /* CoreDataModelHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CoreDataModelHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | 998802B21E590A6A00C807DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | 998802B41E590A6A00C807DA /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | 998802B71E590A6A00C807DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 23 | 998802BA1E590A6A00C807DA /* CoreDataModelHelper.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreDataModelHelper.xcdatamodel; sourceTree = ""; }; 24 | 998802BC1E590A6A00C807DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | 998802BF1E590A6A00C807DA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 26 | 998802C11E590A6A00C807DA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 998802AC1E590A6A00C807DA /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | 998802A61E590A6A00C807DA = { 41 | isa = PBXGroup; 42 | children = ( 43 | 998802B11E590A6A00C807DA /* CoreDataModelHelper */, 44 | 998802B01E590A6A00C807DA /* Products */, 45 | ); 46 | sourceTree = ""; 47 | }; 48 | 998802B01E590A6A00C807DA /* Products */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | 998802AF1E590A6A00C807DA /* CoreDataModelHelper.app */, 52 | ); 53 | name = Products; 54 | sourceTree = ""; 55 | }; 56 | 998802B11E590A6A00C807DA /* CoreDataModelHelper */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | 998802B21E590A6A00C807DA /* AppDelegate.swift */, 60 | 998802B41E590A6A00C807DA /* ViewController.swift */, 61 | 998802B61E590A6A00C807DA /* Main.storyboard */, 62 | 998802BC1E590A6A00C807DA /* Assets.xcassets */, 63 | 998802BE1E590A6A00C807DA /* LaunchScreen.storyboard */, 64 | 998802C11E590A6A00C807DA /* Info.plist */, 65 | 998802B91E590A6A00C807DA /* CoreDataModelHelper.xcdatamodeld */, 66 | ); 67 | path = CoreDataModelHelper; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | 998802AE1E590A6A00C807DA /* CoreDataModelHelper */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = 998802C41E590A6A00C807DA /* Build configuration list for PBXNativeTarget "CoreDataModelHelper" */; 76 | buildPhases = ( 77 | 998802AB1E590A6A00C807DA /* Sources */, 78 | 998802AC1E590A6A00C807DA /* Frameworks */, 79 | 998802AD1E590A6A00C807DA /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = CoreDataModelHelper; 86 | productName = CoreDataModelHelper; 87 | productReference = 998802AF1E590A6A00C807DA /* CoreDataModelHelper.app */; 88 | productType = "com.apple.product-type.application"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | 998802A71E590A6A00C807DA /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastSwiftUpdateCheck = 0820; 97 | LastUpgradeCheck = 0820; 98 | ORGANIZATIONNAME = "Diego Freniche Brito"; 99 | TargetAttributes = { 100 | 998802AE1E590A6A00C807DA = { 101 | CreatedOnToolsVersion = 8.2.1; 102 | DevelopmentTeam = 722SF2V9RH; 103 | ProvisioningStyle = Automatic; 104 | }; 105 | }; 106 | }; 107 | buildConfigurationList = 998802AA1E590A6A00C807DA /* Build configuration list for PBXProject "CoreDataModelHelper" */; 108 | compatibilityVersion = "Xcode 3.2"; 109 | developmentRegion = English; 110 | hasScannedForEncodings = 0; 111 | knownRegions = ( 112 | en, 113 | Base, 114 | ); 115 | mainGroup = 998802A61E590A6A00C807DA; 116 | productRefGroup = 998802B01E590A6A00C807DA /* Products */; 117 | projectDirPath = ""; 118 | projectRoot = ""; 119 | targets = ( 120 | 998802AE1E590A6A00C807DA /* CoreDataModelHelper */, 121 | ); 122 | }; 123 | /* End PBXProject section */ 124 | 125 | /* Begin PBXResourcesBuildPhase section */ 126 | 998802AD1E590A6A00C807DA /* Resources */ = { 127 | isa = PBXResourcesBuildPhase; 128 | buildActionMask = 2147483647; 129 | files = ( 130 | 998802C01E590A6A00C807DA /* LaunchScreen.storyboard in Resources */, 131 | 998802BD1E590A6A00C807DA /* Assets.xcassets in Resources */, 132 | 998802B81E590A6A00C807DA /* Main.storyboard in Resources */, 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | 998802AB1E590A6A00C807DA /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 998802B51E590A6A00C807DA /* ViewController.swift in Sources */, 144 | 998802BB1E590A6A00C807DA /* CoreDataModelHelper.xcdatamodeld in Sources */, 145 | 998802B31E590A6A00C807DA /* AppDelegate.swift in Sources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXSourcesBuildPhase section */ 150 | 151 | /* Begin PBXVariantGroup section */ 152 | 998802B61E590A6A00C807DA /* Main.storyboard */ = { 153 | isa = PBXVariantGroup; 154 | children = ( 155 | 998802B71E590A6A00C807DA /* Base */, 156 | ); 157 | name = Main.storyboard; 158 | sourceTree = ""; 159 | }; 160 | 998802BE1E590A6A00C807DA /* LaunchScreen.storyboard */ = { 161 | isa = PBXVariantGroup; 162 | children = ( 163 | 998802BF1E590A6A00C807DA /* Base */, 164 | ); 165 | name = LaunchScreen.storyboard; 166 | sourceTree = ""; 167 | }; 168 | /* End PBXVariantGroup section */ 169 | 170 | /* Begin XCBuildConfiguration section */ 171 | 998802C21E590A6A00C807DA /* Debug */ = { 172 | isa = XCBuildConfiguration; 173 | buildSettings = { 174 | ALWAYS_SEARCH_USER_PATHS = NO; 175 | CLANG_ANALYZER_NONNULL = YES; 176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 177 | CLANG_CXX_LIBRARY = "libc++"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_WARN_BOOL_CONVERSION = YES; 181 | CLANG_WARN_CONSTANT_CONVERSION = YES; 182 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 183 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 184 | CLANG_WARN_EMPTY_BODY = YES; 185 | CLANG_WARN_ENUM_CONVERSION = YES; 186 | CLANG_WARN_INFINITE_RECURSION = YES; 187 | CLANG_WARN_INT_CONVERSION = YES; 188 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 189 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 190 | CLANG_WARN_UNREACHABLE_CODE = YES; 191 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 192 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 193 | COPY_PHASE_STRIP = NO; 194 | DEBUG_INFORMATION_FORMAT = dwarf; 195 | ENABLE_STRICT_OBJC_MSGSEND = YES; 196 | ENABLE_TESTABILITY = YES; 197 | GCC_C_LANGUAGE_STANDARD = gnu99; 198 | GCC_DYNAMIC_NO_PIC = NO; 199 | GCC_NO_COMMON_BLOCKS = YES; 200 | GCC_OPTIMIZATION_LEVEL = 0; 201 | GCC_PREPROCESSOR_DEFINITIONS = ( 202 | "DEBUG=1", 203 | "$(inherited)", 204 | ); 205 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 206 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 207 | GCC_WARN_UNDECLARED_SELECTOR = YES; 208 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 209 | GCC_WARN_UNUSED_FUNCTION = YES; 210 | GCC_WARN_UNUSED_VARIABLE = YES; 211 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 212 | MTL_ENABLE_DEBUG_INFO = YES; 213 | ONLY_ACTIVE_ARCH = YES; 214 | SDKROOT = iphoneos; 215 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 216 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 217 | }; 218 | name = Debug; 219 | }; 220 | 998802C31E590A6A00C807DA /* Release */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | ALWAYS_SEARCH_USER_PATHS = NO; 224 | CLANG_ANALYZER_NONNULL = YES; 225 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 226 | CLANG_CXX_LIBRARY = "libc++"; 227 | CLANG_ENABLE_MODULES = YES; 228 | CLANG_ENABLE_OBJC_ARC = YES; 229 | CLANG_WARN_BOOL_CONVERSION = YES; 230 | CLANG_WARN_CONSTANT_CONVERSION = YES; 231 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 232 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 233 | CLANG_WARN_EMPTY_BODY = YES; 234 | CLANG_WARN_ENUM_CONVERSION = YES; 235 | CLANG_WARN_INFINITE_RECURSION = YES; 236 | CLANG_WARN_INT_CONVERSION = YES; 237 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 238 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 239 | CLANG_WARN_UNREACHABLE_CODE = YES; 240 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 241 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 242 | COPY_PHASE_STRIP = NO; 243 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 244 | ENABLE_NS_ASSERTIONS = NO; 245 | ENABLE_STRICT_OBJC_MSGSEND = YES; 246 | GCC_C_LANGUAGE_STANDARD = gnu99; 247 | GCC_NO_COMMON_BLOCKS = YES; 248 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 249 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 250 | GCC_WARN_UNDECLARED_SELECTOR = YES; 251 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 252 | GCC_WARN_UNUSED_FUNCTION = YES; 253 | GCC_WARN_UNUSED_VARIABLE = YES; 254 | IPHONEOS_DEPLOYMENT_TARGET = 10.2; 255 | MTL_ENABLE_DEBUG_INFO = NO; 256 | SDKROOT = iphoneos; 257 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 258 | VALIDATE_PRODUCT = YES; 259 | }; 260 | name = Release; 261 | }; 262 | 998802C51E590A6A00C807DA /* Debug */ = { 263 | isa = XCBuildConfiguration; 264 | buildSettings = { 265 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 266 | DEVELOPMENT_TEAM = 722SF2V9RH; 267 | INFOPLIST_FILE = CoreDataModelHelper/Info.plist; 268 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 269 | PRODUCT_BUNDLE_IDENTIFIER = com.freniche.CoreDataModelHelper; 270 | PRODUCT_NAME = "$(TARGET_NAME)"; 271 | SWIFT_VERSION = 3.0; 272 | }; 273 | name = Debug; 274 | }; 275 | 998802C61E590A6A00C807DA /* Release */ = { 276 | isa = XCBuildConfiguration; 277 | buildSettings = { 278 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 279 | DEVELOPMENT_TEAM = 722SF2V9RH; 280 | INFOPLIST_FILE = CoreDataModelHelper/Info.plist; 281 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 282 | PRODUCT_BUNDLE_IDENTIFIER = com.freniche.CoreDataModelHelper; 283 | PRODUCT_NAME = "$(TARGET_NAME)"; 284 | SWIFT_VERSION = 3.0; 285 | }; 286 | name = Release; 287 | }; 288 | /* End XCBuildConfiguration section */ 289 | 290 | /* Begin XCConfigurationList section */ 291 | 998802AA1E590A6A00C807DA /* Build configuration list for PBXProject "CoreDataModelHelper" */ = { 292 | isa = XCConfigurationList; 293 | buildConfigurations = ( 294 | 998802C21E590A6A00C807DA /* Debug */, 295 | 998802C31E590A6A00C807DA /* Release */, 296 | ); 297 | defaultConfigurationIsVisible = 0; 298 | defaultConfigurationName = Release; 299 | }; 300 | 998802C41E590A6A00C807DA /* Build configuration list for PBXNativeTarget "CoreDataModelHelper" */ = { 301 | isa = XCConfigurationList; 302 | buildConfigurations = ( 303 | 998802C51E590A6A00C807DA /* Debug */, 304 | 998802C61E590A6A00C807DA /* Release */, 305 | ); 306 | defaultConfigurationIsVisible = 0; 307 | }; 308 | /* End XCConfigurationList section */ 309 | 310 | /* Begin XCVersionGroup section */ 311 | 998802B91E590A6A00C807DA /* CoreDataModelHelper.xcdatamodeld */ = { 312 | isa = XCVersionGroup; 313 | children = ( 314 | 998802BA1E590A6A00C807DA /* CoreDataModelHelper.xcdatamodel */, 315 | ); 316 | currentVersion = 998802BA1E590A6A00C807DA /* CoreDataModelHelper.xcdatamodel */; 317 | path = CoreDataModelHelper.xcdatamodeld; 318 | sourceTree = ""; 319 | versionGroupType = wrapper.xcdatamodel; 320 | }; 321 | /* End XCVersionGroup section */ 322 | }; 323 | rootObject = 998802A71E590A6A00C807DA /* Project object */; 324 | } 325 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper.xcodeproj/project.xcworkspace/xcuserdata/dfreniche.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfreniche/modern-core-data-playground/f53476ebae24a3e7ddd22a3be5364bae52af6cce/CoreDataModelHelper/CoreDataModelHelper.xcodeproj/project.xcworkspace/xcuserdata/dfreniche.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper.xcodeproj/xcuserdata/dfreniche.xcuserdatad/xcschemes/CoreDataModelHelper.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper.xcodeproj/xcuserdata/dfreniche.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CoreDataModelHelper.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 998802AE1E590A6A00C807DA 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CoreDataModelHelper 4 | // 5 | // Created by Diego Freniche Brito on 19/02/17. 6 | // Copyright © 2017 Diego Freniche Brito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import CoreData 11 | 12 | @UIApplicationMain 13 | class AppDelegate: UIResponder, UIApplicationDelegate { 14 | 15 | var window: UIWindow? 16 | 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 19 | 20 | let context = persistentContainer.viewContext 21 | print(context.hasChanges) 22 | 23 | let p = Project() 24 | 25 | return true 26 | } 27 | 28 | func applicationWillResignActive(_ application: UIApplication) { 29 | // 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. 30 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 31 | } 32 | 33 | func applicationDidEnterBackground(_ application: UIApplication) { 34 | // 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. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | func applicationWillEnterForeground(_ application: UIApplication) { 39 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 40 | } 41 | 42 | func applicationDidBecomeActive(_ application: UIApplication) { 43 | // 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. 44 | } 45 | 46 | func applicationWillTerminate(_ application: UIApplication) { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | // Saves changes in the application's managed object context before the application terminates. 49 | self.saveContext() 50 | } 51 | 52 | // MARK: - Core Data stack 53 | 54 | lazy var persistentContainer: NSPersistentContainer = { 55 | /* 56 | The persistent container for the application. This implementation 57 | creates and returns a container, having loaded the store for the 58 | application to it. This property is optional since there are legitimate 59 | error conditions that could cause the creation of the store to fail. 60 | */ 61 | let container = NSPersistentContainer(name: "CoreDataModelHelper") 62 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 63 | if let error = error as NSError? { 64 | // Replace this implementation with code to handle the error appropriately. 65 | // fatalError() 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. 66 | 67 | /* 68 | Typical reasons for an error here include: 69 | * The parent directory does not exist, cannot be created, or disallows writing. 70 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 71 | * The device is out of space. 72 | * The store could not be migrated to the current model version. 73 | Check the error message to determine what the actual problem was. 74 | */ 75 | fatalError("Unresolved error \(error), \(error.userInfo)") 76 | } 77 | }) 78 | return container 79 | }() 80 | 81 | // MARK: - Core Data Saving support 82 | 83 | func saveContext () { 84 | let context = persistentContainer.viewContext 85 | if context.hasChanges { 86 | do { 87 | try context.save() 88 | } catch { 89 | // Replace this implementation with code to handle the error appropriately. 90 | // fatalError() 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. 91 | let nserror = error as NSError 92 | fatalError("Unresolved error \(nserror), \(nserror.userInfo)") 93 | } 94 | } 95 | } 96 | 97 | } 98 | 99 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper/Base.lproj/LaunchScreen.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 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper/Base.lproj/Main.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 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper/CoreDataModelHelper.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | CoreDataModelHelper.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper/CoreDataModelHelper.xcdatamodeld/CoreDataModelHelper.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /CoreDataModelHelper/CoreDataModelHelper/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CoreDataModelHelper 4 | // 5 | // Created by Diego Freniche Brito on 19/02/17. 6 | // Copyright © 2017 Diego Freniche Brito. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | // Do any additional setup after loading the view, typically from a nib. 16 | } 17 | 18 | override func didReceiveMemoryWarning() { 19 | super.didReceiveMemoryWarning() 20 | // Dispose of any resources that can be recreated. 21 | } 22 | 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT - Licence 2 | 3 | Copyright (c) 2017 Diego Freniche 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.md: -------------------------------------------------------------------------------- 1 | https://cocoacasts.com/building-the-perfect-core-data-stack-with-nspersistentcontainer/ 2 | 3 | 4 | NSPersistentContainer 5 | 6 | open var viewContext: NSManagedObjectContext { get } 7 | 8 | open var managedObjectModel: NSManagedObjectModel { get } 9 | 10 | open var persistentStoreCoordinator: NSPersistentStoreCoordinator { get } 11 | 12 | 13 | open func newBackgroundContext() -> NSManagedObjectContext 14 | 15 | open func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Swift.Void) 16 | 17 | 18 | 19 | http://stackoverflow.com/a/40056499/225503 20 | 21 | 22 | Code generation explained: 23 | 24 | https://useyourloaf.com/blog/core-data-code-generation/ -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Core Data Notifications.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | //: Every single time we insert, delete, etc. something in a Core Data Context a Notification is sent 4 | 5 | // we get a NSPersistentContainer 6 | let container = hwContainer() 7 | let context = container.viewContext 8 | 9 | public class CoreDataNotificationObserver: NSObject { 10 | override init() { 11 | super.init() 12 | NotificationCenter.default.addObserver(self, selector: #selector(receivedInsertNotification), name: NSNotification.Name.NSManagedObjectContextDidSave, object: nil) 13 | 14 | NotificationCenter.default.addObserver(self, selector: #selector(receivedUpdateNotification), name: NSNotification.Name.NSManagedObjectContextObjectsDidChange, object: nil) 15 | } 16 | 17 | deinit { 18 | NotificationCenter.default.removeObserver(self) 19 | } 20 | 21 | @objc public func receivedInsertNotification() { 22 | print("👉 Insert notification received") 23 | } 24 | 25 | @objc public func receivedUpdateNotification() { 26 | print("👉 Update notification received") 27 | } 28 | } 29 | 30 | 31 | // let's insert one object: still no notifications 32 | let project = insertEmptyProjectInContext(context: context) 33 | 34 | // we create a Notification Observer: this registers itself in the init() method 35 | let notificationObserver = CoreDataNotificationObserver() 36 | 37 | do { 38 | print("Preparing to save") 39 | try context.save() // here NSManagedObjectContextDidSave is emitted 40 | print("Saved") 41 | } catch { 42 | fatalError("Unresolved error \(error)") 43 | } 44 | 45 | // let's update that project 46 | 47 | project.name = "Changed!" 48 | 49 | do { 50 | print("Preparing to save after update") 51 | try context.save() // here NSManagedObjectContextDidSave is emitted 52 | print("Saved after update") 53 | } catch { 54 | fatalError("Unresolved error \(error)") 55 | } 56 | 57 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Custom Core Data Stack.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | /*: NSPersistentContainer is really nice. Creates a Core Data Stack for us automagically. 4 | But what if we need to configure that Core Data Stack? Can we create a Core Data Stack in Memory? */ 5 | 6 | //: For that, we first need to create a Persistent Store Description 7 | let persistentStoreDescription: NSPersistentStoreDescription = NSPersistentStoreDescription() 8 | 9 | //: Then, we configure the Persistent Store Description 10 | persistentStoreDescription.type = NSInMemoryStoreType 11 | persistentStoreDescription.shouldMigrateStoreAutomatically = true 12 | persistentStoreDescription.shouldInferMappingModelAutomatically = true 13 | 14 | /*: 15 | And now we simply create our `NSPersistentContainer` and set the `persistentStoreDescriptions` 16 | `persistentStoreDescriptions` is an array of descriptions 17 | */ 18 | 19 | let container = NSPersistentContainer(name: "CoreDataModelHelper") 20 | container.persistentStoreDescriptions = [persistentStoreDescription] 21 | 22 | /* important: set the store descriptions __before__ loading the persistent stores 23 | This makes sense, as store descriptions are the configuration for the persistent stores 24 | */ 25 | 26 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 27 | print("Store URL: \(String(describing: storeDescription.url))") 28 | if let error = error as NSError? { 29 | fatalError("Unresolved error \(error), \(error.userInfo)") 30 | } 31 | }) 32 | 33 | //: Now we can check this in-memory context 34 | 35 | // the viewContext has no changes 36 | container.viewContext.hasChanges 37 | 38 | // get the main thread attached context 39 | let context = container.viewContext 40 | 41 | // insert an Object in the context 42 | let project1 = Project(context: context) 43 | 44 | // set some properties 45 | project1.name = "World domination" 46 | project1.priority = 1 47 | 48 | do { 49 | container.viewContext.hasChanges 50 | 51 | // how many objects pending to be saved? 52 | let newObjectsCount = context.insertedObjects.count 53 | print("Preparing to save \(newObjectsCount)") 54 | 55 | // save 56 | try context.save() 57 | print("Saved") 58 | 59 | } catch { 60 | fatalError("Unresolved error \(error)") 61 | } 62 | 63 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Custom Core Data Stack.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Delete.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | //: Let's see how we delete objects in Core Data 4 | 5 | let container = hwContainer() // init an NSPersistentContainer 6 | 7 | // get the main thread attached context 8 | let context = container.viewContext 9 | 10 | // we insert some dummy data. This func is inside Sources > InsertSomeProjects.swift 11 | insertSomeProjectsInContext(context: context) 12 | 13 | let numProjectsBeforeDelete = countAllProjects(context: context) 14 | 15 | print("Projects before deleting \(numProjectsBeforeDelete)") 16 | 17 | //: Let's see how we can __delete__ all projects 18 | 19 | // This function, when called, deletes all projects from Core Data. It need a context 20 | 21 | func deleteAllProjects(context: NSManagedObjectContext) { 22 | let fetchRequest: NSFetchRequest = Project.fetchRequest() 23 | 24 | do { 25 | let result = try context.fetch(fetchRequest) 26 | print("Num records to delete: \( result.count )") 27 | for e in result { 28 | context.delete(e) // to delete an object, just call delete on the context 29 | } 30 | } catch { 31 | print("Something bad happened while deleting all projects") 32 | print("Here's the info I have \(error.localizedDescription)") 33 | } 34 | } 35 | 36 | deleteAllProjects(context: context) 37 | 38 | print("Projects after deleting \(countAllProjects(context: context))") 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Fetch Projects.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | /*: Fetching data from Core Data. This is how we get our objects from Core Data. It hasn't to be a Database, but think of fetching like issuing a SQL SELECT statement to Core Data */ 4 | 5 | // we get a NSPersistentContainer 6 | let container = hwContainer() 7 | let context = container.viewContext 8 | 9 | // insert some dummy test data 10 | insertSomeProjectsInContext(context: context) 11 | 12 | // fetch all projects 13 | let fetchRequest: NSFetchRequest = Project.fetchRequest() 14 | 15 | // Set the batch size to a suitable number. 16 | fetchRequest.fetchBatchSize = 20 17 | 18 | // Set a sort key so results get ordered 19 | let sortDescriptor = NSSortDescriptor(key: "name", ascending: false) 20 | fetchRequest.sortDescriptors = [sortDescriptor] 21 | 22 | do { 23 | // run the fetch request 24 | let result: [Project] = try context.fetch(fetchRequest) 25 | print("Num records: \( result.count )") 26 | 27 | // print all projects 28 | for p in result { 29 | print("title " + p.name! + p.description) 30 | } 31 | } catch { 32 | print("Error \(error.localizedDescription)") 33 | } 34 | 35 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Fetch Tasks.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | let container = hwContainer() 4 | let context = container.viewContext 5 | 6 | insertSomeProjectsInContext(context: context) 7 | 8 | // fetch all tasks 9 | 10 | let fetchRequest: NSFetchRequest = Task.fetchRequest() 11 | 12 | // Set the batch size to a suitable number. 13 | fetchRequest.fetchBatchSize = 20 14 | 15 | // Edit the sort key as appropriate. 16 | let sortDescriptor = NSSortDescriptor(key: "title", ascending: false) 17 | 18 | let fp = firstProject(context: context) 19 | 20 | fp?.description 21 | 22 | // fetchRequest.predicate = NSPredicate(format: "project == %@", fp!) 23 | fetchRequest.predicate = NSPredicate(format: "title == 'Locate 007'", fp!) 24 | 25 | fetchRequest.sortDescriptors = [sortDescriptor] 26 | 27 | let runFetch = { 28 | do { 29 | let result = try context.fetch(fetchRequest) 30 | print("*** Found Num records: \( result.count )") 31 | 32 | for p in result { 33 | print("title " + p.title!) 34 | } 35 | } catch { 36 | print("Error \(error.localizedDescription)") 37 | } 38 | } 39 | 40 | runFetch() 41 | 42 | fetchRequest.predicate = NSPredicate(format: "project.name == 'Evil project 1'", fp!) 43 | 44 | runFetch() 45 | 46 | // 47 | 48 | 49 | fetchRequest.predicate = NSPredicate(format: "title CONTAINS[c] '007'") 50 | 51 | runFetch() 52 | 53 | 54 | fetchRequest.predicate = NSPredicate(format: "project = %@", fp!) 55 | 56 | runFetch() 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Generic Fetch.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | /*: In our examples, Fetches depend on a concrete type. We're fetching projects, or fetching tasks. But code is almost the same. We can avoid that duplication just usign generics */ 4 | 5 | public class GenericFetch { 6 | // this func returns a NSFetchRequest for the type we need 7 | // call it like: 8 | // GenericFetch.fetchRequest() 9 | public class func fetchRequest() -> NSFetchRequest { 10 | return NSFetchRequest(entityName: String(describing: T.self)) 11 | } 12 | 13 | public class func execute(fetchRequest: NSFetchRequest, inContext context: NSManagedObjectContext) -> (result: [T], error: NSError?) { 14 | 15 | let result: [T] 16 | var err: NSError? 17 | do { 18 | err = nil 19 | result = try context.fetch(fetchRequest) as! [T] 20 | } catch { 21 | err = error as NSError? 22 | result = [T]() 23 | } 24 | return (result, err) 25 | } 26 | } 27 | 28 | //: Let's create a fetch request for Projects 29 | let fetchRequest = GenericFetch.fetchRequest() 30 | print(String(describing: fetchRequest.entityName)) // prints "Project" 31 | 32 | //: Let's create a fetch request for Tasks 33 | let tasksFetchRequest = GenericFetch.fetchRequest() 34 | print(String(describing: tasksFetchRequest.entityName)) // prints "Task" 35 | 36 | //: To test this, we start with our classic container 37 | let container = hwContainer() 38 | let context = container.viewContext 39 | 40 | //: Let's insert a bunch of Projects 41 | 42 | insertSomeProjectsInContext(context: context) 43 | 44 | //: Let's fetch all the Projects 45 | 46 | let result = GenericFetch.execute(fetchRequest: fetchRequest, inContext: context) 47 | 48 | print("😉 Result:") 49 | print(String(describing: result)) 50 | print("❤️ Num records: \( result.result.count )") 51 | 52 | for p in result.result { 53 | print("👉 title " + p.name! + p.description) 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/HowToUsePlaygrounds.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: [Previous](@previous) 2 | 3 | /*: 4 | # How to use this Playground 5 | 6 | - press ⌘ + 1 to show / hide all pages in this Playground 7 | 8 | ![](pages.gif) 9 | 10 | - click on a Page to jump into it. Read & start writing code! 11 | - use the Previous / Next links to navigate through Pages 12 | - you can see the results of your code in the right bar or add add a quick look 13 | 14 | ![](preview.gif) 15 | 16 | 17 | */ 18 | 19 | //: [Next](@next) 20 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/HowToUsePlaygrounds.xcplaygroundpage/Resources/pages.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfreniche/modern-core-data-playground/f53476ebae24a3e7ddd22a3be5364bae52af6cce/Modern-Core-Data-Playground.playground/Pages/HowToUsePlaygrounds.xcplaygroundpage/Resources/pages.gif -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/HowToUsePlaygrounds.xcplaygroundpage/Resources/pages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfreniche/modern-core-data-playground/f53476ebae24a3e7ddd22a3be5364bae52af6cce/Modern-Core-Data-Playground.playground/Pages/HowToUsePlaygrounds.xcplaygroundpage/Resources/pages.png -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/HowToUsePlaygrounds.xcplaygroundpage/Resources/preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfreniche/modern-core-data-playground/f53476ebae24a3e7ddd22a3be5364bae52af6cce/Modern-Core-Data-Playground.playground/Pages/HowToUsePlaygrounds.xcplaygroundpage/Resources/preview.gif -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/HowToUsePlaygrounds.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Init Core Data the Easy way NSPersistentContainer.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # The Core Data Stack 3 | 4 | The Core Data Stack is how we refer to the neccessary components to make a basic Core Data App work. We need at least one NSManagedObjectContext, a NSPersistentStoreCoordinator and a NSManagedObjectModel. In the past, creating this stack was a little bit tedious and you needed to master all the nitty-gritty details. Now it's way simpler. 5 | 6 | Let's init an NSPersistentContainer with the name `CoreDataModelHelper`. This will be the name of the generated SQLite database file and the name of the Managed Object Model it expects to load. See the global Resources folder in this Playground the file CoreDataModelHelper.momd? That file contains the "definitions" of our Core Data Entities. 7 | */ 8 | 9 | //: First step to use Core Data is to import the framework 10 | 11 | import CoreData 12 | 13 | let container = NSPersistentContainer(name: "CoreDataModelHelper") 14 | 15 | //: printing the container description gives us a lot of info 16 | 17 | print(container.persistentStoreDescriptions.debugDescription) 18 | 19 | print("\nNumber of persistent stores loaded \(container.persistentStoreCoordinator.persistentStores.count)") 20 | 21 | //: we load the Persistent Store here 22 | 23 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 24 | if let error = error as NSError? { 25 | fatalError("Unresolved error \(error), \(error.userInfo)") 26 | } 27 | }) 28 | 29 | //: now the persisten Store is loaded! 30 | 31 | print("\nNumber of persistent stores loaded \(container.persistentStoreCoordinator.persistentStores.count)") 32 | 33 | //: we get access to an NSManagedObjectContext, which is where we insert / delete / work with our Objects 34 | 35 | let context = container.viewContext 36 | 37 | print("Has this context pending changes? \(context.hasChanges)") 38 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Init Core Data the Easy way NSPersistentContainer.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Insert a Project with many tasks.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | let container = hwContainer() // init an NSPersistentContainer 4 | 5 | //: Insert a new Project and then some Tasks linked to that project 6 | 7 | // get the main thread attached context 8 | let context = container.viewContext 9 | 10 | // insert an Object in the context 11 | let project1 = Project(context: context) 12 | 13 | // set some properties 14 | project1.name = "Evil project 1" 15 | project1.priority = 1 16 | 17 | //: There's a 1--n relationship between Project and Task. So each task belongs to a project 18 | 19 | let task1 = Task(context: context) 20 | task1.title = "Locate 007" 21 | task1.project = project1 22 | 23 | let task2 = Task(context: context) 24 | task2.title = "Prepare super-trap" 25 | task2.project = project1 26 | 27 | do { 28 | // how many objects pending to be saved? 29 | let newObjectsCount = context.insertedObjects.count 30 | print("Preparing to save \(newObjectsCount)") 31 | 32 | // save 33 | try context.save() 34 | 35 | print("Saved") 36 | 37 | } catch { 38 | fatalError("Unresolved error \(error)") 39 | } 40 | 41 | // traverse 1-->n relationship, from Project to Task 42 | 43 | let tasks: NSOrderedSet = project1.tasks! 44 | for t in tasks { 45 | print("Task title: \(String(describing: (t as! Task).title))") 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Insert a Project.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | let container = hwContainer() // init an NSPersistentContainer 4 | 5 | /*: 6 | Core Data manages your data only if you create it inside a Context 7 | Inserting new instances inside a NSManagedObjectContext is really easy 8 | */ 9 | 10 | // get the main thread attached context 11 | let context = container.viewContext 12 | 13 | //: To insert an Object in the context, we just create it passing in an NSManagedObjectContext 14 | let project1 = Project(context: context) 15 | 16 | // set some properties 17 | project1.name = "World domination" 18 | project1.priority = 1 19 | 20 | //: after inserting, we can ask the context for the number of objects pending to be saved (in a _dirty_ state), then call the `save` method to persist all changes in the context 21 | 22 | do { 23 | // how many objects pending to be saved? 24 | let newObjectsCount = context.insertedObjects.count 25 | print("Preparing to save \(newObjectsCount)") 26 | 27 | // save 28 | try context.save() 29 | print("Saved") 30 | 31 | } catch { 32 | fatalError("Unresolved error \(error)") 33 | } 34 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Intro.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Modern Core Data Playground 4 | 5 | This Playground will help you quickly test and learn core concepts about Core Data in a simple way 6 | 7 | ## The model used in this example 8 | 9 | - you can create __Projects__ 10 | - a Project has _name_, _priority_, _startDate_ and several _tasks_ 11 | - you can create and add _tasks_ to a _project_ 12 | - a __Task__ belongs to just one Project (it's a 1-n relationship, 1 Project can have n Tasks) 13 | - a Task has _desc_ and _title_ 14 | 15 | ``` 16 | +-----------------+ +----------------+ 17 | | Project | | Task | 18 | |-----------------| 1 n |----------------| 19 | | name: String |---------------|title: String | 20 | | priority: Int | |desc: String | 21 | | startDate: Date | | | 22 | +-----------------+ +----------------+ 23 | 24 | ``` 25 | 26 | 27 | You can find this project [here](https://github.com/dfreniche/modern-core-data-playground) 28 | */ 29 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/The Core Data Stack.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | //: Let's explore the Core Data Stack created when using an NSPersistentContainer 2 | //: The Core Data Stack is at least one Context, the Persistent Store Coordinator and a Persistent Object Store 3 | 4 | import CoreData 5 | 6 | // hwContainer is a func located in this playground's common Sources folder, file HWContainer.swift 7 | 8 | let container = hwContainer() // init an NSPersistentContainer 9 | 10 | //: CONTEXT: get the main thread attached context. This is a NSManagedObjectContext 11 | 12 | let context = container.viewContext 13 | 14 | //: MANAGED OBJECT MODEL (MOM) 15 | 16 | let model = container.managedObjectModel 17 | 18 | // let's print all the Entities we have defined inside that MOM 19 | 20 | print("\nEntities inside MOM") 21 | for e in model.entities { 22 | print("Entity name: " + e.name!) 23 | } 24 | //: PERSISTENT STORE COORDINATOR 25 | 26 | let psc = container.persistentStoreCoordinator 27 | 28 | //: PERSISTENT STORES 29 | print("\n💾All Persistent Stores used:") 30 | for ps in psc.persistentStores { 31 | print(ps.description) 32 | } 33 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/The Core Data Stack.xcplaygroundpage/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Pages/Undo management.xcplaygroundpage/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | // we get a NSPersistentContainer 4 | let container = hwContainer() 5 | let context = container.viewContext 6 | 7 | // insert some dummy test data 8 | let project1 = Project(context: context) 9 | 10 | // set some properties 11 | project1.name = "World domination" 12 | project1.priority = 1 13 | 14 | do { 15 | let newObjectsCount = context.insertedObjects.count 16 | print("Preparing to save \(newObjectsCount)") 17 | 18 | // we save that project 19 | try context.save() 20 | 21 | print("Saved") 22 | } catch { 23 | fatalError("Unresolved error \(error)") 24 | } 25 | 26 | project1.name.map{ print($0) } // prints "World Domination" 27 | 28 | //: We need an UndoManager in our Context 29 | context.undoManager = UndoManager() 30 | context.undoManager?.beginUndoGrouping() 31 | 32 | // we change the name of that project 33 | project1.name = "Something strange" 34 | 35 | context.undoManager?.endUndoGrouping() 36 | 37 | project1.name.map{ print($0) } // prints "Something strange" 38 | 39 | // Now we undo that change 40 | 41 | context.undo() 42 | 43 | project1.name.map{ print($0) } // prints "World Domination" 44 | 45 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Resources/CoreDataModelHelper.momd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfreniche/modern-core-data-playground/f53476ebae24a3e7ddd22a3be5364bae52af6cce/Modern-Core-Data-Playground.playground/Resources/CoreDataModelHelper.momd -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Resources/CoreDataModelHelper.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | CoreDataModelHelper.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Resources/CoreDataModelHelper.xcdatamodeld/CoreDataModelHelper.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Sources/Core Data Generated Files/Project+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Project+CoreDataClass.swift 3 | // 4 | // 5 | // Created by Diego Freniche Brito on 20/02/17. 6 | // 7 | // This file was automatically generated and should not be edited. 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | @objc(Project) 14 | public class Project: NSManagedObject { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Sources/Core Data Generated Files/Project+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Project+CoreDataProperties.swift 3 | // 4 | // 5 | // Created by Diego Freniche Brito on 20/02/17. 6 | // 7 | // This file was automatically generated and should not be edited. 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | 14 | extension Project { 15 | 16 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 17 | return NSFetchRequest(entityName: "Project"); 18 | } 19 | 20 | @NSManaged public var name: String? 21 | @NSManaged public var priority: Int16 22 | @NSManaged public var startDate: NSDate? 23 | @NSManaged public var tasks: NSOrderedSet? 24 | 25 | } 26 | 27 | // MARK: Generated accessors for tasks 28 | extension Project { 29 | 30 | @objc(insertObject:inTasksAtIndex:) 31 | @NSManaged public func insertIntoTasks(_ value: Task, at idx: Int) 32 | 33 | @objc(removeObjectFromTasksAtIndex:) 34 | @NSManaged public func removeFromTasks(at idx: Int) 35 | 36 | @objc(insertTasks:atIndexes:) 37 | @NSManaged public func insertIntoTasks(_ values: [Task], at indexes: NSIndexSet) 38 | 39 | @objc(removeTasksAtIndexes:) 40 | @NSManaged public func removeFromTasks(at indexes: NSIndexSet) 41 | 42 | @objc(replaceObjectInTasksAtIndex:withObject:) 43 | @NSManaged public func replaceTasks(at idx: Int, with value: Task) 44 | 45 | @objc(replaceTasksAtIndexes:withTasks:) 46 | @NSManaged public func replaceTasks(at indexes: NSIndexSet, with values: [Task]) 47 | 48 | @objc(addTasksObject:) 49 | @NSManaged public func addToTasks(_ value: Task) 50 | 51 | @objc(removeTasksObject:) 52 | @NSManaged public func removeFromTasks(_ value: Task) 53 | 54 | @objc(addTasks:) 55 | @NSManaged public func addToTasks(_ values: NSOrderedSet) 56 | 57 | @objc(removeTasks:) 58 | @NSManaged public func removeFromTasks(_ values: NSOrderedSet) 59 | 60 | } 61 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Sources/Core Data Generated Files/Task+CoreDataClass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Task+CoreDataClass.swift 3 | // 4 | // 5 | // Created by Diego Freniche Brito on 20/02/17. 6 | // 7 | // This file was automatically generated and should not be edited. 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | @objc(Task) 14 | public class Task: NSManagedObject { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Sources/Core Data Generated Files/Task+CoreDataProperties.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Task+CoreDataProperties.swift 3 | // 4 | // 5 | // Created by Diego Freniche Brito on 20/02/17. 6 | // 7 | // This file was automatically generated and should not be edited. 8 | // 9 | 10 | import Foundation 11 | import CoreData 12 | 13 | 14 | extension Task { 15 | 16 | @nonobjc public class func fetchRequest() -> NSFetchRequest { 17 | return NSFetchRequest(entityName: "Task"); 18 | } 19 | 20 | @NSManaged public var desc: String? 21 | @NSManaged public var title: String? 22 | @NSManaged public var project: Project? 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Sources/CountAllProjects.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | public func countAllProjects(context: NSManagedObjectContext) -> Int { 4 | let fetchRequest: NSFetchRequest = Project.fetchRequest() 5 | 6 | // Set the batch size to a suitable number. 7 | fetchRequest.fetchBatchSize = 20 8 | 9 | // Edit the sort key as appropriate. 10 | let sortDescriptor = NSSortDescriptor(key: "name", ascending: false) 11 | 12 | fetchRequest.sortDescriptors = [sortDescriptor] 13 | 14 | do { 15 | let result = try context.fetch(fetchRequest) 16 | return result.count 17 | } catch { 18 | print("Error \(error.localizedDescription)") 19 | } 20 | 21 | return -1 22 | } 23 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Sources/FirstProject.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | // fetches the first project in our Context 4 | public func firstProject(context: NSManagedObjectContext) -> Project? { 5 | let fetchRequest: NSFetchRequest = Project.fetchRequest() 6 | 7 | // Set the batch size to a suitable number. 8 | fetchRequest.fetchBatchSize = 20 9 | 10 | // Edit the sort key as appropriate. 11 | let sortDescriptor = NSSortDescriptor(key: "name", ascending: false) 12 | 13 | fetchRequest.sortDescriptors = [sortDescriptor] 14 | 15 | do { 16 | let result = try context.fetch(fetchRequest) 17 | return result.first 18 | 19 | } catch { 20 | print("Error \(error.localizedDescription)") 21 | } 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Sources/HWContainer.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | public func hwContainer() -> NSPersistentContainer { 4 | 5 | let container = NSPersistentContainer(name: "CoreDataModelHelper") 6 | 7 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 8 | print("Store URL: \(String(describing: storeDescription.url))") 9 | if let error = error as NSError? { 10 | fatalError("Unresolved error \(error), \(error.userInfo)") 11 | } 12 | }) 13 | 14 | return container 15 | } 16 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/Sources/InsertSomeProjects.swift: -------------------------------------------------------------------------------- 1 | import CoreData 2 | 3 | public func insertEmptyProjectInContext(context: NSManagedObjectContext) -> Project { 4 | // insert a Project in the context 5 | let project = Project(context: context) 6 | 7 | // set some properties 8 | project.name = "Evil project 1" 9 | project.priority = 1 10 | 11 | do { 12 | // how many objects pending to be saved? 13 | let newObjectsCount = context.insertedObjects.count 14 | print("Preparing to save \(newObjectsCount)") 15 | 16 | // save 17 | try context.save() 18 | 19 | print("Saved") 20 | 21 | } catch { 22 | fatalError("Unresolved error \(error)") 23 | } 24 | 25 | return project 26 | } 27 | 28 | 29 | public func insertOneProjectInContext(context: NSManagedObjectContext) -> Project { 30 | // insert a Project in the context 31 | let project1 = Project(context: context) 32 | 33 | // set some properties 34 | project1.name = "Evil project 1" 35 | project1.priority = 1 36 | 37 | // create a couple Tasks 38 | 39 | let task1 = Task(context: context) 40 | task1.title = "Locate 007" 41 | task1.project = project1 // this task belongs to project1 42 | 43 | let task2 = Task(context: context) 44 | task2.title = "Prepare super-trap" 45 | task2.project = project1 // this task belongs to project1 46 | 47 | do { 48 | // how many objects pending to be saved? 49 | let newObjectsCount = context.insertedObjects.count 50 | print("Preparing to save \(newObjectsCount)") 51 | 52 | // save 53 | try context.save() 54 | 55 | print("Saved") 56 | 57 | } catch { 58 | fatalError("Unresolved error \(error)") 59 | } 60 | 61 | return project1 62 | } 63 | 64 | public func insertSomeProjectsInContext(context: NSManagedObjectContext) { 65 | 66 | for i in 0..<10 { 67 | // insert a Project in the context 68 | let project1 = Project(context: context) 69 | 70 | // set some properties 71 | project1.name = "Evil project \(i)" 72 | project1.priority = 1 73 | 74 | // create some Tasks 75 | 76 | let randomNum:UInt32 = arc4random_uniform(10) 77 | 78 | for j in 0.. 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/playground.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Modern-Core-Data-Playground.playground/playground.xcworkspace/xcuserdata/dfreniche.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfreniche/modern-core-data-playground/f53476ebae24a3e7ddd22a3be5364bae52af6cce/Modern-Core-Data-Playground.playground/playground.xcworkspace/xcuserdata/dfreniche.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Modern Core Data Playground: Core Data in Swift / Xcode... inside a Playground! 2 | 3 | An introduction to Core Data you can test, use, change & code, all in just one Playground! 4 | 5 | ![A screen capture showing one playground page](img/init-core-data.png) 6 | 7 | ## The model used in this example 8 | 9 | Core Data wants to be the model of your App, [Markus Zarra](http://martiancraft.com/blog/2015/03/core-data-stack/) once dixit. 10 | 11 | So in this Playground we have a simple model: 12 | 13 | - you can create __Projects__ 14 | - a Project has _name_, _priority_, _startDate_ and several _tasks_ 15 | - you can create and add _tasks_ to a _project_ 16 | - a __Task__ belongs to just one Project (it's a 1-n relationship, 1 Project can have n Tasks) 17 | - a Task has _desc_ and _title_ 18 | 19 | ``` 20 | |-----------------| |----------------| 21 | | Project | | Task | 22 | |-----------------| 1 n |----------------| 23 | | name: String |---------------|title: String | 24 | | priority: Int | |desc: String | 25 | | startDate: Date | | | 26 | |-----------------| |----------------| 27 | 28 | ``` 29 | 30 | - You can browse the model opening the file [CoreDataModelHelper.xcdatamodeld](https://github.com/dfreniche/modern-core-data-playground/CoreDataModelHelper/CoreDataModelHelper/CoreDataModelHelper.xcdatamodeld) 31 | 32 | - Xcode 8 creates automagically in `Derived Folder` the Swift classes to represent your Core Data Entities. I've copied them inside the `Sources` folder > `Core Data Generated Files` 33 | 34 | - for Project Entity, you have `Project+CoreDataProperties.swift` 35 | 36 | ## What you'll find here 37 | 38 | - how to initialise the Core Data Stack using NSPersistentContainer 39 | - how to create a fine - tuned NSPersistentContainer with a memory-only Core Data Stack 40 | - how to do CRUD operations 41 | - how to fetch, using generics 42 | - how to use the Undo Manager 43 | - ... and more to come 44 | 45 | 46 | ## Requirements 47 | * [Xcode](https://developer.apple.com/xcode/downloads/) or greater 48 | * Swift (included in Xcode) 49 | * there are Xcode 8 (Swift 3) and 9 (Swift 4) versions. Look at the tags 50 | 51 | ## Installation 52 | 53 | Clone the repo and open the playground directly in Xcode. 54 | 55 | $ git clone https://github.com/ && cd CoreDataPlayground 56 | $ open core-data-playground.playground 57 | 58 | 59 | ## Helper Project 60 | 61 | I've used a helper project, just a simple Core Data template App to create the Core Data Model file and to let Xcode generate the Swift source code files for each Entity in that Model. 62 | 63 | ## Getting the mom file 64 | 65 | - We can add files to our `Resources` folder (open side Project Navigator with Cmd + 1) 66 | - In this playground we have a CoreDataModelHelper.xcdatamodel file (not the regular xcdatamodeld directory we use in Core Data projects). This file shows you the Core Data Model of this example. 67 | - I've run this code in the Simulator and extracted from the App Bundle the compiled version of the xcdatamodel, CoreDataModelHelper.mom, which is the one Core Data loads. 68 | 69 | ## Acknowledgements 70 | 71 | I was wondering if I could run a complete Core Data example inside a Playground. Searching around to see if anyone has tried that before I found [Andrew Shepard's repo on Core Data]( https://github.com/andyshep/CoreDataPlayground.git). This inspired me to create my own, but with a twist: I didn't wanted to create the Managed Object Model in code. 72 | 73 | 74 | ## License 75 | 76 | The MIT License (MIT) 77 | 78 | Copyright (c) 2017 Diego Freniche 79 | 80 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 81 | 82 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 83 | 84 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 85 | -------------------------------------------------------------------------------- /img/init-core-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfreniche/modern-core-data-playground/f53476ebae24a3e7ddd22a3be5364bae52af6cce/img/init-core-data.png --------------------------------------------------------------------------------