SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

アップルの新プログラミング言語「Swift」を探検しよう

Swiftの「クラス」と「ストラクチャ」 その② ~ subscript、タイプキャスト、メモリ管理(およびクラス、ストラクチャ、列挙型のおさらい)

アップルの新プログラミング言語「Swift」を探検しよう 第5回

ARC

 ARC(Automatic reference counting)の管理下にあるクラスでのクロージャ式プロパティの宣言や、互いにプロパティ同士で参照しあうクラスでは、Objective-Cと同様に循環参照の問題が起こります。Swiftでは参照型のオブジェクトはARCの管理下にあります。クラスとクロージャは参照型のためにARCの管理下にあり、クラスのインスタンス同士が相互に参照し合う場合や、プロパティとして宣言したクロージャの中で自身のインスタンスselfを参照している場合に、循環参照が引き起こされます。

weakキーワード

 Objective-Cでのプロパティ宣言にweakを付けられたのと同様に、weakキーワードをプロパティに付けることで、インスタンスに対する弱参照を実現できます。さらに、弱参照を実現することにより、強参照によって引き起こされる循環参照の問題を解決できます[3]

class Employee {
    var employer: Employer?
    deinit {
        println("employee fired")
    }
}
class Employer {
    var employee: Employee?
    deinit {
        println("employer cllapsed")
    }
}

var employee = Employee()
var employer = Employer()
employer.employee = employee
employee.employer = employer

 この例では互いのクラスのインスタンスがプロパティを経由して強参照しあっているため、循環参照が引き起こされています。

 employee、employerのインスタンスはいずれも他のインスタンスを強参照のプロパティとして保持しているために、宣言されたスコープを外れてもARCによって破棄されずにデイニシャライザも呼ばれません。

 weakキーワードはこの循環参照の問題を解決します。weakキーワードを付けるプロパティは、オプショナル列挙型の変数プロパティとして宣言することが必要です。プロパティは弱参照として処理され、通常の強参照を用いた場合の循環参照の問題は解決されます。

class Employee {
    var employer: Employer?   
    deinit {
        println("employee fired")
    }
}

class Employer {
    weak var employee: Employee?    
    deinit {
        println("employer cllapsed")
    }
}

var employee = Employee()
var employer = Employer()
employer.employee = employee 
employee.employer = employer 

 この場合、employeeインスタンスはemployerインスタンスの弱参照のプロパティとして参照されているため、スコープを抜けると破棄されます。破棄と同時にemployeeインスタンスからの強参照がなくなるので、employerインスタンスも破棄されます。

 weakキーワードの付いたプロパティは参照があるのみで、保持によるメモリ管理を行わないため、参照先自体は他に保持するインスタンスがない場合には、いつでも解放されてしまう可能性があります。解放された場合、プロパティはnilとして扱われるので、weakプロパティを用いるときには、オプショナルチェインニングなどで値の存在を確認することになります。

[3] 強参照と弱参照の詳しい解説は、書籍『詳解 Objective-C 2.0 第3版』に書かれています。

unownedキーワード

 weakキーワードと同様に、強参照によるメモリ管理の問題を解決するためにunownedキーワードが用意されています。

 unownedキーワードはweakキーワードと異なり、プロパティに対して常に値が保持されていると確定している場合に使用されます。このためunowned付きのプロパティはnilとして扱われることはなく、非オプショナル列挙型で宣言します。

class Employee {
    unowned var employer: Employer
    init(employer: Employer) {
        self.employer = employer
    }
    deinit {
        println("employee fired")
    }
}

class Employer {
    var employee: Employee!
    init() {
        self.employee = Employee(employer: self)
    }
    deinit {
        println("employer cllapsed")
    }
}

var employer = Employer()

 weakキーワードの例とは異なり、まずこの例ではEmployerのインスタンス生成時にEmployeeがプロパティとして代入されます。

 Employerでは、Employeeに「!」を付けてImplicitlyUnwrappedOptional型にしています。初期化の過程での保持プロパティに関しては必ず何らかの値が入っている必要があり、Employee!とすることで保持プロパティにはデフォルト値nilが代入され、プロパティへの代入時にselfを使うことが許されるためです。

 「!」がない場合には、イニシャライザでemployeeプロパティの中に何らかの値を初期化完了までに代入する必要があります。この例ではEmployeeクラスのイニシャライザの引数として、初期化の完了していないselfを呼び出しているため、エラーとなります。

 また、unowenedキーワード付きプロパティが値を何も保持していなかった場合にアクセスを受けると、ランタイムエラーが起こります。

クロージャ式プロパティ内部での参照の扱い

 本連載第2回で、クロージャ式が外部のインスタンスをキャプチャすることを説明しました。

 クロージャ式をプロパティとして持つ次のようなクラスを考えてみましょう。

class WebService {
    
    let urlString: String
    
    lazy var onSuccess: String -> Void = {
        responce in
        println("request for " + self.urlString + " was succeeded with responce " + responce)
    }
    
    init(urlString: String) {
        self.urlString = urlString
    }

    deinit {
        println("web service removed")
    }
}

var service = WebService(urlString: "http://somedomain.com")
var successHandler = service.onSuccess

 こちらの例では、lazyキーワードの付いたonSuccessプロパティアクセス時にクロージャ式がselfをキャプチャしています。そしてself自身はonSuccessプロパティを保持しているために、serviceインスタンス内部での循環参照が起こっています。

 スコープを抜けてもインスタンスは解放されないために、web service removedのログが出力されません。

 なお、onSuccessプロパティ初期化時にデフォルト値としてselfの入ったクロージャ式を代入しようとすると、初期化を済ませていないselfがデフォルト値のクロージャ式に入っているため、エラーとなります。それを避けるためにlazyキーワードを追加しています。

 さて、この事象を解消するためには、プロパティへのデフォルトクロージャ式代入時にselfのキャプチャが起こらないよう、次のように宣言を変更します。

lazy var onSuccess: String -> Void = {
    [weak self] responce in
    if self != nil {
        println("request for " + self!.urlString + " was succeeded with responce " + responce)
    }
}

 この変更により自身のインスタンスのキャプチャが解消され、循環参照に伴うインスタンスが解放されない問題も解決します。

 キャプチャに関わるweakとunownedキーワードの使い分けもプロパティの宣言の時と同様に、weakはクロージャ式実行時にキャプチャ解除対象がnilを取りうる場合に用います。そのためweakを付けたキャプチャ解除対象の値はオプショナル列挙型として扱われます。unownedはクロージャ式実行時にキャプチャ解除対象が存在し続ける確証がある場合に用います。

次のページ
クラス、ストラクチャ、列挙型のおさらい

この記事は参考になりましたか?

アップルの新プログラミング言語「Swift」を探検しよう連載記事一覧

もっと読む

この記事の著者

yad(ヤド)

クラスメソッド株式会社のアプリケーションエンジニア。iPhoneアプリケーションの開発に2年以上従事している。開発に使用するObjective-Cのみならず、関数型言語Haskellや機械学習などにも関心がある。「Developers.IO」に寄稿した記事の一覧

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/8120 2014/09/29 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー