[iOS] [Swift] 「iOS10以上」と「iOS9.3.5以下」を互換性を持たせてCoreDataを使う


#目的
今回、初めてXcodeを触って、アプリを作ってみた。
ただ、XcodeとSwiftのバージョンが新しいのに、実機で持っていたのが「iOS9.3.5」だった。仮想デバイスですら「iOS10」なのにである。

デバッグしてみると、悲しいことにいろんなところで調べたことが古くて使えないよ！と言われ、困り果て、何とかして両方使えるように共存できないか探ってみたら、何と似たようなことで困っていた人が記事にしてくれていたので、何とか助かった。

調べても、問題が解決しない記事が多かったので、今回自分でまとめることにした。備忘録。

##何が問題？
「iOS10以降」と「それ以下」の違いが大きく関わっている。

　・CoreDataの宣言の仕方が割と違う。
　　　→　そもそも、AppDelegateで宣言されているものが違うので、使えるメソッドも違う。

　・AppDelegateで宣言されているものが違う
　　　→　前述したように、宣言されているものが違うので、Contextの宣言方法もまるで違う。

※因みに、古いXcodeで作ればいいやんと思ってやってみたけど、macOS Mojaveでは、うまくXcodeの共存（つまり、Xcodeのバージョンが古いのと新しいのを両方とも置くこと）ができない。起動時にエラーが出る。（StackOverFlowでも探したけど、みんな同じことが起こって諦めている感じだった。）

##対応方法
ここでやりたいのは「共存」。どっちでも使えるようにしたいと考え、以下のように対応した。

- **古いXcodeで宣言されているコードを、新しい方に移植する。**

無ければ入れれば良い話で、そこからメソッドを呼び出せばいい。

古いバージョンのXcodeで宣言されているAppDelegateのメソッドを、新しいXcodeのAppDelegateに記述すれば、古い方からもそのバージョンにあったContextを呼び出せるという考え。

##今回作ったアプリ
<img width="375" alt="Simulator Screen Shot - iPhone 7 - 2019-10-08 at 22.23.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260089/837d3ea7-ff42-146a-9840-c2f0fdcfee94.png">
<img width="375" alt="Simulator Screen Shot - iPhone 7 - 2019-10-08 at 22.30.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260089/35036765-23b1-6293-2dc2-3c815e8f993b.png">
<img width="375" alt="Simulator Screen Shot - iPhone 7 - 2019-10-08 at 22.31.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260089/ad8e0b32-21fa-72ca-693e-fb7fa0b78673.png">
<img width="375" alt="Simulator Screen Shot - iPhone 7 - 2019-10-08 at 22.31.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260089/17fc479b-2543-fcbc-9412-4983c5b02076.png">



##AppDelegate

```AppDelegate.swift

import UIKit
import CoreData

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

/*ここまでは、新しいXCodeでは自動的に定義されている。
-------------------------------------------------------------------
ここから下が、古いXcodeからの移植 */
    
    lazy var applicationDocumentsDirectory: NSURL = {
        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return urls[urls.count-1] as NSURL
    }()
    
    lazy var managedObjectModel: NSManagedObjectModel = {
        // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
        //forResource:　には自分のAppの名前を入れる。
        let modelURL = Bundle.main.url(forResource: "MyAppName", withExtension: "momd")!
        return NSManagedObjectModel(contentsOf: modelURL)!
    }()
    
    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
        // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
        // Create the coordinator and store
        let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
        //appendingPathComponentの引数には、データベース名.sqlite
        let url = self.applicationDocumentsDirectory.appendingPathComponent("MyDB.sqlite")
        var failureReason = "There was an error creating or loading the application's saved data."
        do {
            try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)
        } catch {
            // Report any error we got.
            var dict = [String: AnyObject]()
            dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
            dict[NSLocalizedFailureReasonErrorKey] = failureReason as AnyObject?
            
            dict[NSUnderlyingErrorKey] = error as NSError
            let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
            // Replace this with code to handle the error appropriately.
            // 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.
            NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
            abort()
        }
        
        return coordinator
    }()
    
    lazy var managedObjectContext: NSManagedObjectContext = {
        // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
        let coordinator = self.persistentStoreCoordinator
        var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        managedObjectContext.persistentStoreCoordinator = coordinator
        return managedObjectContext
    }()

/*ここまでをまずは移植する。
------------------------------------------------------------------------
*/
    // MARK: - Core Data Saving support

    @available(iOS 10.0, *)
    func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // 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.
                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
    
    //iOS9以下のCoreData_Saveメソッド これも移植したもの。
    func saveContext9 () {
        if managedObjectContext.hasChanges {
            do {
                try managedObjectContext.save()
            } catch {
                // Replace this implementation with code to handle the error appropriately.
                // 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.
                let nserror = error as NSError
                NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
                abort()
            }
        }
    }
}
```

##Contextの宣言

```ViewController.swift
  func getData(){
        //ここでiOSのバージョンによって処理が変わる。
        if #available(iOS 10.0, *) {
            //Contextの宣言(iOS10)
            let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
            
            do{
                sample = try context.fetch(Sample.fetchRequest())
            }catch{
                print("Error")
            }
        }else{
            //Contextの宣言(iOS9)
            let context = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
            
            do{
                sample = try context.fetch(Sample.fetchRequest())
            }catch{
                print("Error")
            }   
        } 
    }
```

##CoreData-Save編-
```Add.swift
        if #available(iOS 10.0, *) {
            let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
            
            let defaultAction:UIAlertAction = UIAlertAction(title: "OK!", style: UIAlertAction.Style.default) {  (action: UIAlertAction!) -> Void in
                
                let sample = Sample(context: context)
                
                sample.id = Int32(self.AutoIncrements())
                sample.name = self.nameField.text
                sample.mail = self.mailField.text
                sample.pass = self.passField.text
                
                (UIApplication.shared.delegate as! AppDelegate).saveContext()
                
                self.navigationController!.popViewController(animated: true)
            }
            
             alert.addAction(defaultAction)
        } else {
            // Fallback on earlier versions
            let context9:NSManagedObjectContext = (UIApplication.shared.delegate as! AppDelegate).managedObjectContext
            
            let defaultAction:UIAlertAction = UIAlertAction(title: "OK!", style: UIAlertAction.Style.default) {  (action: UIAlertAction!) -> Void in
             //ここからが新しいXcodeと違うところ
             let entity = NSEntityDescription.entity(forEntityName: "Sample", in: context9)
             let sample = NSManagedObject(entity: entity!, insertInto: context9) as! Sample
                
             sample.id = Int32(self.AutoIncrements())
             sample.name = self.nameField.text
             sample.mail = self.mailField.text
             sample.pass = self.passField.text
                
             (UIApplication.shared.delegate as! AppDelegate).saveContext9()
                
             self.navigationController!.popViewController(animated: true)
            }
          alert.addAction(defaultAction)
        }
```

##終わりに
まだまだ記録できていないところはあるけれど、とりあえずここまで。
暇なときに追記していきます。

###参考サイト
-- xcode8でCoreData使うときにiOS9以前も対応に含めるときに --
[https://qiita.com/fujiwarawataru/items/12903b6e8fe42d6464f2](https://qiita.com/fujiwarawataru/items/12903b6e8fe42d6464f2)

ここでヒントをもらいました。@fujiwarawataruさん。この記事を書いていただき感謝です。助かりました。