Skip to content

Commit 17aee4f

Browse files
committed
Implemented manager tests
1 parent 538ab91 commit 17aee4f

20 files changed

+380
-171
lines changed

CoreDataMigration-Example.xcodeproj/project.pbxproj

Lines changed: 78 additions & 48 deletions
Large diffs are not rendered by default.

CoreDataMigration-Example/Application/AppDelegate.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import UIKit
1010
import CoreData
1111

12-
@UIApplicationMain
1312
class AppDelegate: UIResponder, UIApplicationDelegate {
1413

1514
var window: UIWindow?
@@ -48,6 +47,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
4847
func applicationWillTerminate(_ application: UIApplication) {
4948
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
5049
// Saves changes in the application's managed object context before the application terminates.
50+
51+
try? CoreDataManager.shared.mainContext.save()
5152
}
5253

5354
// MARK: - Main
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//
2+
// main.swift
3+
// CoreDataMigration-Example
4+
//
5+
// Created by William Boles on 15/09/2017.
6+
// Copyright © 2017 William Boles. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import UIKit
11+
12+
let isRunningTests = NSClassFromString("XCTestCase") != nil
13+
let appDelegateClass : AnyClass = isRunningTests ? TestingAppDelegate.self : AppDelegate.self
14+
let args = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
15+
UIApplicationMain(CommandLine.argc, args, nil, NSStringFromClass(appDelegateClass))

CoreDataMigration-Example/CoreData/CoreDataManager.swift

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,12 @@ import CoreData
1111

1212
class CoreDataManager {
1313

14-
let fileManager: FileManager
1514
let migrator: CoreDataMigrator
1615

1716
lazy var persistentContainer: NSPersistentContainer! = {
1817
let persistentContainer = NSPersistentContainer(name: "CoreDataMigration_Example")
19-
20-
let url = self.fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).last!.appendingPathComponent("CoreDataMigration_Example.sqlite")
21-
let description = NSPersistentStoreDescription(url: url)
22-
description.shouldInferMappingModelAutomatically = false //inferred mapping will be handled else where
23-
24-
persistentContainer.persistentStoreDescriptions = [description]
18+
let description = persistentContainer.persistentStoreDescriptions.first
19+
description?.shouldInferMappingModelAutomatically = false //inferred mapping will be handled else where
2520

2621
return persistentContainer
2722
}()
@@ -46,8 +41,7 @@ class CoreDataManager {
4641

4742
// MARK: - Init
4843

49-
init(fileManager: FileManager = FileManager.default, migrator: CoreDataMigrator = CoreDataMigrator()) {
50-
self.fileManager = fileManager
44+
init(migrator: CoreDataMigrator = CoreDataMigrator()) {
5145
self.migrator = migrator
5246
}
5347

@@ -74,19 +68,20 @@ class CoreDataManager {
7468
}
7569

7670
private func migrateStoreIfNeeded(completion: @escaping () -> Void) {
77-
let storeLocation = persistentContainer.persistentStoreDescriptions[0].url!
78-
if migrator.requiresMigration(storeLocation: storeLocation) {
71+
guard let description = persistentContainer.persistentStoreDescriptions.first, let storeURL = description.url else {
72+
fatalError("persistentContainer not set up properly")
73+
}
74+
75+
if migrator.requiresMigration(storeURL: storeURL) {
7976
DispatchQueue.global(qos: .userInitiated).async {
80-
self.migrator.migrateStore(storeLocation: storeLocation)
77+
self.migrator.migrateStore(storeURL: storeURL)
8178

8279
DispatchQueue.main.async {
8380
completion()
8481
}
8582
}
8683
} else {
87-
DispatchQueue.main.async {
88-
completion()
89-
}
84+
completion()
9085
}
9186
}
9287
}

CoreDataMigration-Example/CoreData/Migration/CoreDataMigrationModel.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,12 +173,14 @@ class CoreDataMigrationModel {
173173

174174
// MARK: - Source
175175

176-
class CoreDataMigrationVersionSourceModel: CoreDataMigrationModel {
176+
class CoreDataMigrationSourceModel: CoreDataMigrationModel {
177177

178178
// MARK: - Init
179179

180180
init?(storeURL: URL) {
181-
let metadata = try! NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil)
181+
guard let metadata = NSPersistentStoreCoordinator.metadata(at: storeURL) else {
182+
return nil
183+
}
182184

183185
let migrationVersionModel = CoreDataMigrationModel.all.first {
184186
$0.managedObjectModel().isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata)

CoreDataMigration-Example/CoreData/Migration/CoreDataMigrator.swift

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,35 +28,39 @@ class CoreDataMigrator {
2828

2929
// MARK: - Check
3030

31-
func requiresMigration(storeLocation: URL, currentMigrationModel: CoreDataMigrationModel = CoreDataMigrationModel.current) -> Bool {
32-
guard let metadata = try? NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeLocation, options: nil) else {
31+
func requiresMigration(storeURL: URL, currentMigrationModel: CoreDataMigrationModel = CoreDataMigrationModel.current) -> Bool {
32+
guard let metadata = NSPersistentStoreCoordinator.metadata(at: storeURL) else {
3333
return false
3434
}
35-
35+
3636
return !currentMigrationModel.managedObjectModel().isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata)
3737
}
3838

3939
// MARK: - Migration
4040

41-
func migrateStore(storeLocation: URL) {
42-
migrateStore(from: storeLocation, to: storeLocation, targetVersion: CoreDataMigrationModel.current)
41+
func migrateStore(storeURL: URL) {
42+
migrateStore(from: storeURL, to: storeURL, targetVersion: CoreDataMigrationModel.current)
4343
}
4444

4545
func migrateStore(from sourceURL: URL, to targetURL: URL, targetVersion: CoreDataMigrationModel) {
46-
guard let sourceMigrationVersionModel = CoreDataMigrationVersionSourceModel(storeURL: sourceURL as URL) else {
46+
guard let sourceMigrationModel = CoreDataMigrationSourceModel(storeURL: sourceURL as URL) else {
4747
fatalError("unknown store version at URL \(sourceURL)")
4848
}
4949

50-
forceWALCheckpointing(storeLocation: sourceURL)
50+
forceWALCheckpointing(storeURL: sourceURL)
5151

5252
var currentURL = sourceURL
53-
let migrationSteps = sourceMigrationVersionModel.migrationSteps(to: targetVersion)
53+
let migrationSteps = sourceMigrationModel.migrationSteps(to: targetVersion)
5454

5555
for step in migrationSteps {
5656
let manager = NSMigrationManager(sourceModel: step.source, destinationModel: step.destination)
5757
let destinationURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(UUID().uuidString)
5858

59-
try! manager.migrateStore(from: currentURL, sourceType: NSSQLiteStoreType, options: nil, with: step.mapping, toDestinationURL: destinationURL, destinationType: NSSQLiteStoreType, destinationOptions: nil)
59+
do {
60+
try manager.migrateStore(from: currentURL, sourceType: NSSQLiteStoreType, options: nil, with: step.mapping, toDestinationURL: destinationURL, destinationType: NSSQLiteStoreType, destinationOptions: nil)
61+
} catch let error {
62+
fatalError("failed attempting to migrate from \(step.source) to \(step.destination), error: \(error)")
63+
}
6064

6165
if currentURL != sourceURL {
6266
//Destroy intermediate step's store
@@ -66,7 +70,7 @@ class CoreDataMigrator {
6670
currentURL = destinationURL
6771
}
6872

69-
try! NSPersistentStoreCoordinator.replaceStore(at: targetURL, withStoreAt: currentURL)
73+
NSPersistentStoreCoordinator.replaceStore(at: targetURL, withStoreAt: currentURL)
7074

7175
if (currentURL != sourceURL) {
7276
NSPersistentStoreCoordinator.destroyStore(at: currentURL)
@@ -75,8 +79,10 @@ class CoreDataMigrator {
7579

7680
// MARK: - WAL
7781

78-
func forceWALCheckpointing(storeLocation: URL) {
79-
let metadata = try! NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeLocation, options: nil)
82+
func forceWALCheckpointing(storeURL: URL) {
83+
guard let metadata = NSPersistentStoreCoordinator.metadata(at: storeURL) else {
84+
return
85+
}
8086

8187
let migrationVersionModel = CoreDataMigrationModel.all.first {
8288
$0.managedObjectModel().isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata)
@@ -86,11 +92,15 @@ class CoreDataMigrator {
8692
return
8793
}
8894

89-
let model = currentCoreDataStoreMigrationVersionModel.managedObjectModel()
90-
let psc = NSPersistentStoreCoordinator(managedObjectModel: model)
91-
92-
let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
93-
try! psc.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeLocation, options: options)
94-
try! psc.remove(psc.persistentStore(for: storeLocation)!)
95+
do {
96+
let model = currentCoreDataStoreMigrationVersionModel.managedObjectModel()
97+
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
98+
99+
let options = [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
100+
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: storeURL, options: options)
101+
try persistentStoreCoordinator.remove(persistentStoreCoordinator.persistentStore(for: storeURL)!)
102+
} catch let error {
103+
fatalError("failed when attempting to force WAL checkpointing, error: \(error)")
104+
}
95105
}
96106
}

CoreDataMigration-Example/Extensions/NSPersistentStoreCoordinator/NSPersistentStoreCoordinator+Move.swift

Lines changed: 0 additions & 31 deletions
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// NSPersistentStoreCoordinator+SQLite.swift
3+
// CoreDataMigration-Example
4+
//
5+
// Created by William Boles on 15/09/2017.
6+
// Copyright © 2017 William Boles. All rights reserved.
7+
//
8+
9+
import CoreData
10+
11+
extension NSPersistentStoreCoordinator {
12+
13+
// MARK: - Destroy
14+
15+
static func destroyStore(at storeURL: URL) {
16+
do {
17+
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: NSManagedObjectModel())
18+
try persistentStoreCoordinator.destroyPersistentStore(at: storeURL, ofType: NSSQLiteStoreType, options: nil)
19+
} catch let error {
20+
fatalError("failed to destroy persistent store at \(storeURL), error: \(error)")
21+
}
22+
}
23+
24+
// MARK: - Replace
25+
26+
static func replaceStore(at targetURL: URL, withStoreAt sourceURL: URL) {
27+
do {
28+
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: NSManagedObjectModel())
29+
try persistentStoreCoordinator.replacePersistentStore(at: targetURL, destinationOptions: nil, withPersistentStoreFrom: sourceURL, sourceOptions: nil, ofType: NSSQLiteStoreType)
30+
} catch let error {
31+
fatalError("failed to replace persistent store at \(targetURL) with \(sourceURL), error: \(error)")
32+
}
33+
}
34+
35+
// MARK: - Meta
36+
37+
static func metadata(at storeURL: URL) -> [String : Any]? {
38+
return try? NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil)
39+
}
40+
}

CoreDataMigration-Example/Storyboards/Base.lproj/Main.storyboard

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13189.4" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="gtP-dy-BLZ">
2+
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="gtP-dy-BLZ">
33
<device id="retina4_7" orientation="portrait">
44
<adaptation id="fullscreen"/>
55
</device>
66
<dependencies>
77
<deployment identifier="iOS"/>
8-
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13165.3"/>
8+
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
99
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
1010
</dependencies>
1111
<scenes>
12-
<!--Root View Controller-->
12+
<!--Posts-->
1313
<scene sceneID="FXI-iW-yPe">
1414
<objects>
1515
<tableViewController id="aaS-4r-zql" customClass="PostsViewController" customModule="CoreDataMigration_Example" customModuleProvider="target" sceneMemberID="viewController">
@@ -91,7 +91,7 @@
9191
<outlet property="delegate" destination="aaS-4r-zql" id="MHw-VM-eM8"/>
9292
</connections>
9393
</tableView>
94-
<navigationItem key="navigationItem" title="Root View Controller" id="2vk-Y5-WEZ">
94+
<navigationItem key="navigationItem" title="Posts" id="2vk-Y5-WEZ">
9595
<barButtonItem key="rightBarButtonItem" systemItem="add" id="jFW-h7-OCU">
9696
<connections>
9797
<action selector="addButtonPressed:" destination="aaS-4r-zql" id="wec-6d-3el"/>

CoreDataMigration-Example/ViewControllers/Posts/PostsViewController.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@ class PostsViewController: UITableViewController {
4646

4747
func addPost(completion: @escaping () -> Void) {
4848
DispatchQueue.global(qos: .userInitiated).async {
49-
let coreDataManager: CoreDataManager = CoreDataManager.shared
50-
let context = coreDataManager.backgroundContext
49+
let context = CoreDataManager.shared.backgroundContext
5150
context.performAndWait {
5251
let post = NSEntityDescription.insertNewObject(forEntityName: "Post", into: context) as! Post
5352
post.postID = UUID().uuidString

0 commit comments

Comments
 (0)