-->

2020年5月3日日曜日

JSONデータを取り込んでRealmに保存する

プロジェクト内のJSONデータをRealmのモデルに変換して保存する。
xmlやplistを使うことも考えたが、codableを使うことで独自のモデルにも容易に対応させることができた。

環境


  • Xcode 11.4.1
  • Swift 5
  • iOS 13.4.1

StringやIntなどは自動Codableに準拠しているので、encodeやdecodeメソッドを使う必要はないが、
今回はその他のデータ型にも対応できるようにencode、decodeメソッドを実装した。

モデル[Recipe]

  1. import Foundation
  2. import RealmSwift
  3.  
  4. class Recipe: Object,Decodable {
  5. @objc dynamic var name = ""
  6. @objc dynamic var category = ""
  7. @objc dynamic var country = ""
  8. @objc dynamic var season = ""
  9. var ingredients = List<Ingredient>() //RecipeとIngredientは1対多
  10. override static func primaryKey() -> String? {
  11. return "name"
  12. }
  13. private enum CodingKeys:String,CodingKey{
  14. case name
  15. case category
  16. case country
  17. case season
  18. case ingredients
  19. }
  20. required convenience public init(from decoder:Decoder) throws{
  21. self.init()
  22. let container = try decoder.container(keyedBy: CodingKeys.self)
  23.  
  24. name = try container.decode(String.self, forKey: .name)
  25. category = try container.decode(String.self, forKey: .category)
  26. country = try container.decode(String.self, forKey: .country)
  27. season = try container.decode(String.self, forKey: .season)
  28. //一度配列に格納した後、Listに変換する
  29. let ingredientsArray = try container.decode([Ingredient].self, forKey: .ingredients)
  30. ingredients = ingredientsArray.reduce(List<Ingredient>()) {$0.append($1); return $0}
  31. }
  32. }

モデル[ingredient]

  1. import Foundation
  2. import RealmSwift
  3.  
  4. class Ingredient: Object,Decodable {
  5. @objc dynamic var name = ""
  6. let recipes = LinkingObjects(fromType: Recipe.self, property: "ingredients")
  7. private enum CodingKeys: String, CodingKey {
  8. case name
  9. }
  10. required convenience public init(from decoder: Decoder) throws {
  11. self.init()
  12. let container = try decoder.container(keyedBy: CodingKeys.self)
  13.  
  14. name = try container.decode(String.self, forKey: .name)
  15.  
  16. }
  17. }

JSONファイル

[
        {
        "name": "トマトパスタ",
        "category": "パスタ",
        "country": "イタリアン",
        "season": "Any",
        "ingredients":[
            {
                "name": "トマト"
            },
            {
                "name": "にんにく"
            }
        ]
        },
        {
        "name": "カツ丼",
        "category": "丼ぶり",
        "country": "和食",
        "season": "Any",
        "ingredients":[
            {
                "name": "たまねぎ"
            },
            {
                "name": "卵"
            },
            {
                "name": "豚肉"
            }
        ]
        },
        {
        "name": "ペペロンチーノ",
        "category": "パスタ",
        "country": "イタリアン",
        "season": "Any",
        "ingredients":[
            {
                "name": "にんにく"
            },
            {
                "name": "鷹の爪"
            }
        ]
        },
        {
        "name": "カレー",
        "category": "カレー",
        "country": "和食",
        "season": "Any",
        "ingredients":[
            {
                "name": "人参"
            },
            {
                "name": "たまねぎ"
            },
            {
                "name": "じゃがいも"
            },
            {
                "name": "牛肉"
            }
        ]
        },
        {
        "name": "シチュー",
        "category": "シチュー",
        "country": "和食",
        "season": "Any",
        "ingredients":[
            {
                "name": "人参"
            },
            {
                "name": "たまねぎ"
            },
            {
                "name": "じゃがいも"
            },
            {
                "name": "牛肉"
            }
        ]
        },
        {
        "name": "炒飯",
        "category": "ご飯",
        "country": "中華",
        "season": "Any",
        "ingredients":[
            {
                "name": "人参"
            },
            {
                "name": "たまねぎ"
            },
            {
                "name": "卵"
            },
            {
                "name": "にんにく"
            }
        ]
        },
        {
        "name": "炊き込みご飯",
        "category": "ご飯",
        "country": "和食",
        "season": "Any",
        "ingredients":[
            {
                "name": "人参"
            },
            {
                "name": "椎茸"
            },
            {
                "name": "鶏肉"
            },
            {
                "name": "大葉"
            }
        ]
        }
]

JSONデータの取り込み

  1. //JSONファイルのパスを取得
  2. guard let path = Bundle.main.path(forResource: "RecipesList", ofType: "json") else { return }
  3. let url = URL(fileURLWithPath: path)
  4. do {
  5. let realm = try! Realm()
  6.  
  7. let data = try Data(contentsOf: url)
  8. let recipe_obj = try! JSONDecoder().decode([Recipe].self, from: data)
  9.  
  10. try! realm.write {
  11. realm.deleteAll()
  12. realm.add(recipe_obj, update: true)
  13. //人参を使うレシピを取得・表示
  14. var objects: Results<Recipe>
  15. objects = realm.objects(Recipe.self).filter("SUBQUERY(ingredients, $ingredient, $ingredient.name = %@).@count >= 1","人参")
  16. print("objectsの中",objects)
  17. }
  18. } catch {
  19. print("エラー")
  20. }

参考にしたサイト

https://qiita.com/cottpan/items/4bb5ad8bce2e28c0220a

2020年5月2日土曜日

モデルが持つ配列内を検索し、条件に該当するモデルを取得する

モデルが持つ配列内を検索し、条件に該当するモデルを取得する
モデルデータ
  1. //レシピモデル
  2. class Recipe: Object {
  3. @objc dynamic var name = ""
  4. @objc dynamic var category = ""
  5. @objc dynamic var country = ""
  6. @objc dynamic var season = ""
  7. let ingredients = List() //レシピと材料は1対多の関係
  8. override static func primaryKey() -> String? {
  9. return "name"
  10. }
  11. }
  12. //材料モデル
  13. class Ingredient: Object {
  14. @objc dynamic var name = ""
  15. let recipes = LinkingObjects(fromType: Recipe.self, property: "ingredients")
  16. }

モデルデータの格納、検索、表示
  1. do {
  2. //トマトパスタ、カツ丼、カレーの3つのレシピのモデルを作成
  3. let realm = try Realm()
  4. let dictionary1: [String: Any] =
  5. ["name": "トマトパスタ",
  6. "category": "パスタ",
  7. "country" : "イタリア",
  8. "season" : "春",
  9. "ingredients":[["name": "トマト"],["name": "にんにく"]]
  10. ]
  11.  
  12. let dictionary2: [String: Any] =
  13. ["name": "カツ丼",
  14. "category": "丼",
  15. "country" : "日本",
  16. "season" : "夏",
  17. "ingredients":[["name": "たまねぎ"],["name": "卵"]]
  18. ]
  19. let dictionary3: [String: Any] =
  20. ["name": "カレー",
  21. "category": "カレー",
  22. "country" : "日本",
  23. "season" : "夏",
  24. "ingredients":[["name": "たまねぎ"],["name": "にんじん"],["name":"じゃがいも"]]
  25. ]
  26. let recipe1 = Recipe(value: dictionary1)
  27. let recipe2 = Recipe(value: dictionary2)
  28. let recipe3 = Recipe(value: dictionary3)
  29. try! realm.write {
  30. realm.deleteAll() //既に保存済のモデルを一旦削除
  31. realm.add(recipe1)//レシピモデルの保存
  32. realm.add(recipe2)//レシピモデルの保存
  33. realm.add(recipe3)//レシピモデルの保存
  34.  
  35. //材料にたまねぎを使用するレシピモデルを取得する
  36. var objects: Results<Recipe>
  37. objects = realm.objects(Recipe.self).filter("SUBQUERY(ingredients, $ingredient, $ingredient.name = %@).@count >= 1","たまねぎ")
  38. print("objectsの中",objects)
  39. }
  40. } catch {
  41. print("エラー")
  42. }

出力結果
objectsの中 Results <0x10461f0e0> (
 [0] Recipe {
  name = カツ丼;
  category = 丼;
  country = 日本;
  season = 夏;
  ingredients = List <0x280756760> (
   [0] Ingredient {
    name = たまねぎ;
   },
   [1] Ingredient {
    name = 卵;
   }
  );
 },
 [1] Recipe {
  name = カレー;
  category = カレー;
  country = 日本;
  season = 夏;
  ingredients = List <0x280756880> (
   [0] Ingredient {
    name = たまねぎ;
   },
   [1] Ingredient {
    name = にんじん;
   },
   [2] Ingredient {
    name = じゃがいも;
   }
  );
 }
)