問題点の整理
Swift で String の Array 内に特定の文字列があるか否か、または Array 内での オブジェクトの index を求める場合は、こんな感じで書けます。
var names = ["Tokyo", "New York", "London"]
let found = names.contains("Tokyo") // true
let index = names.indexOf("London") // 2
ところが、自作のクラスの Array で同じような事をしようとすると‥
class Item {
var name: String
init(_ name: String) { self.name = name }
}
let apple = Item("Apple")
let banana = Item("Banana")
let orange = Item("Orange")
var items = [apple, banana, orange]
let found = items.contains(apple) // error
let index = items.indexOf(banana) // error
以下のようなエラーになってしまいます。
Cannot convert value of type 'Item' to expected argument type '@noescape (Item) throws -> Bool'
原因は、Item
が Equatable
プロトコルに準拠していないからです。そこで、以下のようにクロージャー内に評価式を書く方法もありますが...
let found = items.contains({ return $0.name == apple.name }) // true
let index = items.indexOf({ return $0.name == banana.name }) // 1
(その1)Equatable プロトコルに準拠させる
そこはやっぱり、Item
を Equatable
プロトコルに準拠させてあげます。その為に、Equatable
の記述と、func ==
関数を用意します。この関数は、右辺と左辺を評価して、等価か否かを評価して Bool を戻すものです。この場合は、名前が一致すれば等価としてみました。
class Item: Equatable { // Equatable
var name: String
init(_ name: String) { self.name = name }
}
func == (l: Item, r: Item) -> Bool { // 評価関数
return l.name == r.name
}
let apple = Item("Apple")
let banana = Item("Banana")
let orange = Item("Orange")
var items = [apple, banana, orange]
let found = items.contains(apple) // true
let index = items.indexOf(banana) // 1
let index = items.indexOf(Item("Orange")) // 2
(その2)NSObject を基底クラスとする
自作クラスを NSObject のサブクラスにする方法も考えられます。
class Item: NSObject {
var name: String
init(_ name: String) { self.name = name }
}
let apple = Item("Apple")
let banana = Item("Banana")
let orange = Item("Orange")
var items = [apple, banana, orange]
let found = items.contains(apple) // true
let index = items.indexOf(banana) // 1
注目は、NSObject
。あまり手をかけずに、contains(element)
、indexOf(element)
が使えるようになりますが、このままでは、オブジェクトの名前ではなくオブジェクトが同一かどうかで判断されてしまうので、以下のような書き方をすると、異なる結果になってしまいます。
let found = items.contains(Item("Apple")) // false <-- !?
これは、いい悪いではなく、こういうものだと理解する必要があります。この挙動を変えたい場合は、==
評価関数を書いてあげます。プロパティが多いと大変そうですね。
func == (l: Item, r: Item) -> Bool {
return l.name == r.name
}
ちなみに、オブジェクトが同一かどうかで評価する場合は以下のように書きます。
func == (l: Item, r: Item) -> Bool {
return l === r
}
さらに、struct
や enum
の場合は、NSObject
のサブクラス化による方法は無理っぽい事は言うまでもありませんね。
【環境】
Xcode 7.2, Swift 2.1.1