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の使い所・使い時に注意しつつ、コードを書きたいなと実感させられました。