はじめに
Swiftは2014年の公開以来、3.1までバージョンが更新されてきました。バージョンが上がると同時に大きな仕様が相次ぎ、互換性がなかったため次のバージョンアップへの不安もありました。
ですがSwift 3から4へのバージョンアップで、メソッドや関数の定義や命名規則が統一され、今後は極端な仕様の変更などもなくなると考えられています。
連載第2回では、アプリ内でよく利用されるDictionaryの扱いとJSONの解析に関し、Swift 4で追加された便利な機能を解説します。各項目で説明する内容についてのSwiftのバージョンは【】内に記します。
本連載は趣旨の通り、アプリ開発の最初に知っておくべき基本的な事柄の説明をメインテーマとしています。内容によっては、Swift 4以前のバージョンに触れることやSwift自体の言語仕様などには説明が及ばないこともご了承ください。本連載以上の各種情報は末尾の参考文献等を参照してください。
対象読者
本記事は、次の方を対象にしています。
- Swiftでの基本的なプログラムができる方
- Xcodeを使える方
Dictionaryの拡張された機能
Swift 3以前でも、Arrayに対してはmap/filter/reduce等の便利なメソッドが実装されていました。その一方で、Dictionaryに関してはそういった機能はほぼ存在していませんでした。Swift 4からはDictionaryについても同様に、便利に使える機能が実装されました。ここではその中でもよく利用されるものについて例を挙げます。
Dictionary定義の簡略化【Swift 4】
Dictinonary型の変数を宣言する際に、配列の定義と同様に[]リテラルを利用できるようになりました。名前とスコアのペアを保持するDictionary型の変数は次の通りに宣言できます。
// Dictionary型の変数を宣言 let scoreDic: [String: Int] = ["Muku": 10, "Tora": 20, "Tama": 30, "Tom": 40] // 以下の宣言と同じ意味です //let scoreDic: Dictionary<String, Int> = ["Muku": 10, "Tora": 20, "Tama": 30, "Tom":40]
Swift 3以前の書式と同様に、キーと値の順序は変わりません。
条件に合致する要素を取得する【Swift 4】
条件に合致する要素を取得するfilter(_:)メソッドが実装されました。filterメソッド(_:)の書式は次の通りです。
Dictionaryオブジェクト.filter(クロージャ)
クロージャとは名前のない関数のことです。filter(_:)メソッドのクロージャ内では、$0でDictionaryの要素が得られ、keyプロパティで要素のキー、valueプロパティで要素の値を参照できます。
// スコアが15以上の要素を取得 let scoreOver15Dic = scoreDic.filter({ $0.value > 15}) print(scoreOver15Dic) // 結果:["Tom": 40, "Tora": 20, "Tama": 30] // キーの文字数が3より大きい要素を取得 let char4Dic = scoreDic.filter({$0.key.count > 3}) print(char4Dic) // 結果:["Muku": 10, "Tora": 20, "Tama": 30]
filter(_:)メソッドを利用することで、Dictionaryオブジェクト内の要素を一定の条件で容易に抽出できます。
要素の値に対して処理を行う【Swift 4】
各要素に対して処理を行うmapValue(_:)メソッドが実装されました。mapValues(_:)メソッドの書式は次の通りです。
Dictionaryオブジェクト.mapValues(クロージャ)
mapValues(_:)メソッドを利用して、スコアの値を3倍にする処理は次のように書けます。
// スコアを3倍に let scoreX2Dic = scoreDic.mapValues({ $0 * 3}) print(scoreX2Dic) // 結果:["Muku": 30, "Tom": 120, "Tora": 60, "Tama": 90]
mapValues(_:)メソッドは、その名前の通り辞書の値に対する処理です。$0で得られる値はDicitionaryの要素の値です。キーは含まれません。$0の意味がfilterメソッドとは違う点に気をつけてください。
配列のグループ化【Swift 4】
配列をある規則でグループ化した後に、その結果をDictionaryで得るinit(grouping:by:)メソッドという初期化処理が追加されました。init(grouping:by:)メソッドの書式は次の通りです。
let Dictionaryオブジェクト = Dictionary(grouping: Arrayオブジェクト, by: クロージャ)
クロージャでは$0でArrayオブジェクトの要素が得られます。具体的な利用例は次の通りです。
// 要素の最初の1文字でグループ化 let people = ["あべ", "あさの", "あそう", "かとう", "かんだ", "さとう", "さやま", "たなか"] let groupedPeopleDic = Dictionary(grouping: people, by: { $0.characters.first! }) print(groupedPeopleDic) // 結果:["か": ["かとう", "かんだ"], "さ": ["さとう", "さやま"], "あ": ["あべ", "あさの", "あそう"], "た": ["たなか"]] // 1~10の数字を偶数と奇数で分ける let numbers = 1 ... 20 let groupedNum = Dictionary(grouping: numbers, by: { $0 % 2 == 0 }) print(groupedNum) // 結果:[false: [1, 3, 5, 7, 9], true: [2, 4, 6, 8, 10]]
任意の条件で配列をグループ化する機能は、ゲームやSNSでよく利用されるものです。もう少し実用的なサンプルを作成してみます。
リスト5のような動物の名前と種類を持つ動物を表すAnimal構造体があると仮定します。動物の種類についてはenumで指定できる値を限定しています。
struct Animal { var name : String // 名前 var type : AnimalType // 種類 enum AnimalType { case dog, cat, mouse, panda } }
この動物オブジェクトが複数混在する場合、その中からパンダの種類だけ取り出す処理は、グループ化を使って次のように行うことができます。
// 複数の動物オブジェクトを配列にする let animals = [ Animal(name:"レオ", type:.dog), Animal(name:"むく", type:.cat), Animal(name:"悠悠", type:.panda), Animal(name:"ジェリー", type:.mouse), Animal(name:"たま", type:.cat), Animal(name:"トム", type:.cat), Animal(name:"ちゅー太", type:.mouse), Animal(name:"ちび", type:.cat), Animal(name:"香香", type:.panda), Animal(name:"とら", type:.cat) ] // typeでグループ化する let groupin = Dictionary(grouping: animals, by: { $0.type}) // パンダのグループ let pandaGroup = groupin[.panda] // Optional([Sample.Animal(name: "悠悠", type: Sample.Animal.AnimalType.panda), Sample.Animal(name: "香香", type: Sample.Animal.AnimalType.panda)])
並列関係にある同じ型のオブジェクトは、一度配列にすれば属性で容易に分類することが可能です。for文やif文を使った長い処理を行う必要がなく、コードをシンプルに記述することができるようになりました。