self
を使える限り使っていきたいという感覚があるのですが、ちゃんと言語化したいなと思って記事を書いています。
selfのユースケース
self
を定型的に使いがちなシーンをいくつかピックアップしてみました。
delegate設定時
まず思い浮かぶのは、delegate
の設定時ではないでしょうか。
デリゲートパターンでは、デリゲートを受ける側のクラスが、デリゲート提供元クラスのインターフェースのメソッドを呼び出すことで、特定のイベントに応じた処理を委譲します。
この時、提供元クラスのdelegate
プロパティに、受ける側のclass
を記述します。
class SomeClass: SomeDelegate {
let anotherClass = AnotherClass()
func setupDelegate() {
anotherClass.delegate = self
}
}
self
の嬉しさを体感したいので、使わないとどうなるか試してみましょう。SomeClass
内で別Class
をインスタンス化し、それをデリゲートに設定します。
class SomeClass {
let anotherClass = AnotherClass()
let extraClass = ExtraClass()
func setupDelegate() {
anotherClass.delegate = extraClass
}
}
上記の例では、SomeClass
が直接デリゲートとして機能せず、別のインスタンス(ExtraClass
)をデリゲートとして設定しています。
せっかくプロトコルに基づいてインターフェースの振る舞いを定め、インターフェースを抽象化できたのに、登場人物が増えてしまいました。
上記のコードは一概に間違いとは言えませんが、可読時の負荷が上がる心地を覚えます。ExtraClass
と AnotherClass
の関係性に気を配りつつ読まなければならなくなってしまうので、苦しいです。
クロージャー内におけるselfキャプチャ
クロージャー内での self
のキャプチャは、特に非同期処理やイベントハンドラ・コールバックを定義する際に頻繁に遭遇します。
class MyViewController: UIViewController {
var myProperty: String = ""
func fetchData() {
DispatchQueue.global().async { [weak self] in
// 非同期処理を行う
let data = "データの取得"
DispatchQueue.main.async {
// UIの更新はメインスレッドで行う
self?.myProperty = data
self?.updateUI()
}
}
}
func updateUI() {
// UIの更新処理
}
}
fetchData
メソッド内で非同期にデータを取得し、UIを更新する処理を行っています。
非同期処理内のクロージャーで self
を [weak self]
としてキャプチャしています。これは、MyViewController
のインスタンスへの弱参照をクロージャーに持たせることで、強参照サイクルを防ぐための措置です。
ここでは、self
が nil
になる可能性があるため、self?
でオプショナルチェーンを使ってメソッドやプロパティにアクセスしています。
この例は、むしろself
を使うしかない状況かもしれません。
enum 内の自己参照
この記事を書く発端になったユースケースその1です。
enum
を宣言したのち、switch self
を使って値に応じて振る舞いを決定する関数を実装するときのself
です。
enum UserCategory: String {
case normalAccount = "通常アカウント"
case officialAccount = "企業用アカウント"
var sendable: Bool {
switch self {
case .normalAccount:
return true
case .officialAccount:
return false
}
}
}
enumの中に振る舞いを隠蔽できていることがまず嬉しいですね。
加えて、自分自身の型(self)に基づいて振る舞いを変えているので、機にする範囲が小さくてありがたいです。
抽象class内で、別classが保持するプロパティに自身を代入する
この記事を書く発端になったユースケースそのです。
これは実例を見た方がわかりやすいでしょう。
final class Game {
}
extension Game {
func makeGameDetail() -> GameDetail {
return GameDetail(parent: self)
}
}
final class GameDetail {
let parent: Game
}
このケースでは、、Game
インスタンスが self
を使って GameDetail
インスタンスを作成し、2つのクラス間の関連を設定しています。
これにより、GameDetail
はその「親」である Game
インスタンスを知ることができます。
class間の関係性を明瞭かつ簡潔に表現できるので、self
があると嬉しさを覚えます。
サンプルコードを踏まえ、総括
self
が宣言されていると、気を配る範囲が小さくなり、安心する点がまず一つ。
また、self
を使うことでコードの意図を明確にし、オブジェクト間の関連性を容易に表現できる点にも、「嬉しさ」を覚えていることがわかりました。
self
を使うことで、結果的にSwiftらしい書き方ができる側面もあるような心地を覚えています。self
を適切に使うことで、コードの明確性、可読性、そして保守性が向上していることは、上記のサンプルコードからも明らかです。
改めて、self
の使い所・使い時に注意しつつ、コードを書きたいなと実感させられました。