Swiftにおけるセレクタ(Selector)について調べてみた
記事内に広告を含む場合があります。記事内で紹介する商品を購入することで、当サイトに売り上げの一部が還元されることがあります。
NSTimerなどを使う際、引数に「Selector」という型があるのですが、これがSwiftではどのような扱いになっているのかを少し調べてみました。
Selectorの使用例 : タイマーの初期化処理
例えばタイマー処理を行う場合、例えば以下のようなコードを書いて初期化を行います。
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() NSTimer.scheduledTimerWithTimeInterval( 1, target: self, selector: "update:", userInfo: nil, repeats: true) } func update(timer:NSTimer) { print(timer) } }
scheduledTimerWithTimeIntervalメソッドの宣言は以下のようになっています。
public class func scheduledTimerWithTimeInterval(ti: NSTimeInterval, target aTarget: AnyObject, selector aSelector: Selector, userInfo: AnyObject?, repeats yesOrNo: Bool) -> NSTimer
引数targetと引数selectorを使って、定期的にどのメソッドを呼ばせるのかを指定します。
引数targetの型はAnyObjectなので、任意のクラスインスタンスを渡すことができます。
では、引数selectorの型「Selector」とは、一体何なのでしょうか?
Selectorの宣言
Swiftにおいて、Selectorは以下のように宣言されています。
/// The Objective-C SEL type. /// /// The Objective-C SEL type is typically an opaque pointer. Swift /// treats it as a distinct struct type, with operations to /// convert between C strings and selectors. /// /// The compiler has special knowledge of this type. public struct Selector : StringLiteralConvertible, NilLiteralConvertible { /// Create a selector from a string. public init(_ str: String) /// Create an instance initialized to `value`. public init(unicodeScalarLiteral value: String) /// Construct a selector from `value`. public init(extendedGraphemeClusterLiteral value: String) /// Create an instance initialized to `value`. public init(stringLiteral value: String) public init() /// Create an instance initialized with `nil`. public init(nilLiteral: ()) }
また、Selectorが適合している「StringLiteralConvertible」と「NilLiteralConvertible」の宣言は以下の通りです。
/// Conforming types can be initialized with arbitrary string literals. public protocol StringLiteralConvertible : ExtendedGraphemeClusterLiteralConvertible { typealias StringLiteralType /// Create an instance initialized to `value`. public init(stringLiteral value: Self.StringLiteralType) }
/// Conforming types can be initialized with `nil`. public protocol NilLiteralConvertible { /// Create an instance initialized with `nil`. public init(nilLiteral: ()) }
よって、Selectorとして文字列で指定することもできるし、nilで指定することもできるようになっています。
Selector型は構造体で、文字列を引数とするイニシャライザを持ちます。インスタンスobjに対し、引数のないメソッドsay()をセレクタを使って呼び出すには次のようにします。ここでperformSelector(_:)はNSObjectのメソッドです。
let sel = Selector(“say”)
obj.performSelector(sel)Selector型はプロトコルStringLiteralConvertibleを採用しているため、セレクタの代わりに文字列リテラルを記述することもできます。自動的にイニシャライザが呼び出されます。
obj.performSelector(“say”)
引用元 : 詳解 Swift 改訂版
Selectorの実装
なお、「swift/ObjectiveC.swift at 4d0c060e2189571b0103fa5473ed8d8145d0e231 · apple/swift」をチェックしてみると、Selectorの実装は以下のようになっていました。
/// The Objective-C SEL type. /// /// The Objective-C SEL type is typically an opaque pointer. Swift /// treats it as a distinct struct type, with operations to /// convert between C strings and selectors. /// /// The compiler has special knowledge of this type. public struct Selector : StringLiteralConvertible, NilLiteralConvertible { var ptr : COpaquePointer /// Create a selector from a string. public init(_ str : String) { ptr = str.withCString { sel_registerName($0).ptr } } /// Create an instance initialized to `value`. public init(unicodeScalarLiteral value: String) { self.init(value) } /// Construct a selector from `value`. public init(extendedGraphemeClusterLiteral value: String) { self.init(value) } // FIXME: Fast-path this in the compiler, so we don't end up with // the sel_registerName call at compile time. /// Create an instance initialized to `value`. public init(stringLiteral value: String) { self = sel_registerName(value) } public init() { ptr = nil } /// Create an instance initialized with `nil`. @_transparent public init(nilLiteral: ()) { ptr = nil } } @warn_unused_result public func ==(lhs: Selector, rhs: Selector) -> Bool { return sel_isEqual(lhs, rhs) } extension Selector : Equatable, Hashable { /// The hash value. /// /// **Axiom:** `x == y` implies `x.hashValue == y.hashValue` /// /// - Note: the hash value is not guaranteed to be stable across /// different invocations of the same program. Do not persist the /// hash value across program runs. public var hashValue: Int { return ptr.hashValue } } extension Selector : CustomStringConvertible { /// A textual representation of `self`. public var description: String { if let s = String.fromCStringRepairingIllFormedUTF8(sel_getName(self)).0 { return s } return "<NULL>" } } extension String { /// Construct the C string representation of an Objective-C selector. public init(_sel: Selector) { // FIXME: This misses the ASCII optimization. self = String.fromCString(sel_getName(_sel))! } } extension Selector : CustomReflectable { /// Returns a mirror that reflects `self`. public func customMirror() -> Mirror { return Mirror(reflecting: String(_sel: self)) } }
- Equatableプロトコルに適合しているので==演算子で比較することができる
- Hashableプロトコルに適合しているのでSetなどに入れられる
- CustomStringConvertibleプロトコルに適合しているのでprintなどでログ出力したりできる
ということが分かります。
@akio0911はこう思った。
「Selector型って書いてあるのに、なんでStringを渡せるんだろう?」って思ってたんですが、StringLiteralConvertibleプロトコルに適合していたんですね。
Swiftにおけるプロトコルの活用方法が少し分かった気がしました。
関連記事
この記事が気に入ったら「いいね!」しよう
Twitterで更新情報をゲット!