Swiftに書き換える際に最初分からなくて困ったのは、SwiftにNSArrayとArrayの両方のクラスが存在することでした。自動変換ツールSWIFTIFYを使ったのですが、自動変換では変換後のコードにエラーが出て使えなかった上、配列を必要としたのがTableViewControllerだったため、随所にエラーが現れ、その数の多さにしばらく頭を抱えてしまいました。
SWIFTIFY: https://objectivec2swift.com/#/home
結果的にArrayとNSArray、2つのクラスの違いはこうでした。
- Arrayクラスは値型、NSArrayは参照型
- NSArrayだったものをArrayにしたければ「let」で宣言。NSMutableArrayだったものをArrayにしたければ「var」で宣言。
- Array、NSArrayそれぞれ使えるメソッドが違うので、片方にしかないメソッドがどうしても必要な場合はそちらを使うしかない。
【事例その1】
変換前
@property (nonatomic,strong) NSMutableArray *notes;
自動変換後
var notes : [Any] = []
手直し後
var notes : [NSManagedObject] = []
配列はvar notes: Array = Array()のように書くのだとてっきり思っていたので、自動変換後の式はあまりにシンプルで最初意味がわかりませんでした。[Any]は「中身はなんでもありの配列」ということですね。なんでもありはいいのですが、Objective Cではそれすらもわざわざ宣言したりしないので「違うな」と思いました。NSArrayでは、配列の中身のクラスがなんであるかは、そのクラスのメソッドが必要になるまで何も言う必要がないですよね。必要になったときに[(NSString*)[notes objectAtIndex:0] メソッド名]のようにキャストすればいい。自動変換したnotesというオブジェクトの中身はAnyなので、そこからさらにメソッドを呼ぶにはObjective Cにならってnotes[0] as! NSManagedObjectのようにしても良かったのですが、試行錯誤していくうちに、なんのことははない、中身は常にNSManagedObjectなんだから[NSManagedObject]で良いということで手直ししました。asでキャスティングが必要なのは配列に複数のクラスのオブジェクトが混在する場合ですね。そんな怖いことはしませんが。
あと、NSMutableArrayをそのままSwiftで使うことも考えたのですが、実際にはmutablecopyで複製して使っていたので、参照型のNSMutableArrayではなく、値型のArrayが良いということになりました。
【事例その2】
変換前
NSString *string = [[_notes objectAtIndex:indexPath.row] valueForKey:@”textString”];
自動変換後
var string = notes[indexPath.row][“textString”] as? String
手直し後
let string : String = notes[indexPath.row].value(forKeyPath: “textString”) as? String ?? “”
Core Dataに保存されたデータをTableViewに表示するときの処理です。notesにはNSManagedObjectが入ってます。「textString」という名前は私が決めたものでattribute名です。データベースで言うところのフィールド名ですね(Core Dataはデータベースではないとあちこちに書かれていますが厳密な定義は入門者を混乱させるだけだと個人的には思っていて、最初はとにかく動けばいいと思います)。自動変換後の[“textString”]はvalueForKeyから来ていますが、Objective CでvalueForKeyで呼び出していた値はSwiftではvalue(forKeyPath:)と微妙に異なる呼び出し方が必要だったので手直ししました。さらにnilが存在しうるオプショナル型(String?)ではなく、確実に値の存在するStringオブジェクトが欲しかったので、nilだったときは””を入れるように最後に?? “”を追加しました。
【事例その3】
変換前
[_notes insertObject:objectToMove atIndex:toIndexPath.row];
自動変換後
notes.insert(objectToMove, at: to.row)
TableViewの行を並べ替えたときの処理です。objectToMoveは一時的に用意した移動するNSManagedObjectオブジェクト。Objective CでtoIndexPathだったものはSwiftではtoに変わっています。英語圏の人にはswiftの方がより読みやすくなっているのでしょうが、英語の構文で考えていない日本人にはtoだけだと言葉が短すぎて分かりにくい。でも、TableViewDelegate標準のプロパティ名なので慣れるしかないですね。
【事例その4】
変換前
[_notes removeObjectAtIndex:indexPath.row];
自動変換後
notes.remove(at: indexPath.row)
TableViewの列を削除したときに使います。これは見たまんまですね。removeObjectAtIndexがremove(at:)と簡略化されています。同じSwift同士でもバージョンアップごとに言い回しがよく変わるので、私はあえて暗記しないことにしています。Xcodeのコードセンス機能で自動補完させてリターンキーで決定したほうが間違いがなくていいです。
【番外編】
変換前
for (int i = 0; i < _notes.count ; i++) {
自動変換後
for i in 0..<notes.count {
別候補
for note in notes {
直接Arrayの話ではありませんが、配列といえば必ずforループが必要になるので追記します。Swiftではforではなくfor inしか使えません。何番目の要素を処理しているかが大事な場合は自動変換のように整数iを使い、大事でない場合はnotesの各要素をnoteに代入する別候補の式が使えます。また、逆順にしたい場合は
for i in (0..<notes.count).reversed() {
また特定の範囲を飛び飛びに回したいときは
for i in stride(from: 0, to: 10, by: 2) {
のように記述します。上の式だとiは0,2,4,6,8,10の順に変化していきます。