🐦

swiftの weak var の挙動について

2022/01/16に公開

はじめに

swiftにおいて、 letvar じゃなくて敢えて weak var を利用する場合のシチュエーションはどういう場合かや挙動の違いを自分の中で言語がしたいと思い書いてみようと思いました。

クロージャーでよく使う[weak self]の使いどころは数年前にこちらの記事に書いております。
https://qiita.com/hs7/items/1f17baf43b943949a7ff

let/var/weak varの違い

let

  • イミュータブル(変更不可能)な値
  • メモリ管理上、強参照される

var

  • ミュータブル(変更可能)な値
  • メモリ管理上、強参照される

weak var

  • ミュータブル(変更可能)な値
  • メモリ管理上、弱参照される
  • 参照型(class)の変数にしか weak は利用できない。

このように weak による違いは、弱参照される ところだと思います。

Weak References について

下記公式のARCのWeak References の説明に書いてありました。

https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID53

とくに、

A weak reference is a reference that doesn’t keep a strong hold on the instance it refers to, and so doesn’t stop ARC from disposing of the referenced instance. This behavior prevents the reference from becoming part of a strong reference cycle. You indicate a weak reference by placing the weak keyword before a property or variable declaration.
Because a weak reference doesn’t keep a strong hold on the instance it refers to, it’s possible for that instance to be deallocated while the weak reference is still referring to it. Therefore, ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated. And, because weak references need to allow their value to be changed to nil at runtime, they’re always declared as variables, rather than constants, of an optional type.

weakが付いている場合、その変数のインスタンスが破棄されることを防止しないため、そのインスタンスは破棄される可能性があって、その場合にARCが自動的にnilを設定することがあります。そのため、変数(var)として宣言される(weakvarじゃないといけない。weak let は宣言できない)。というのがあります。
ちなみに、weak letで宣言してみると下記のようなビルドエラーが出ます。

  • 'weak' must be a mutable variable, because it may change at runtime

このことから、あるオブジェクトAの中であるオブジェクトBを参照する際に、オブジェクトBの破棄があるオブジェクトA内に参照が残っていることで破棄出来なくなるのを防ぎたいときに利用する。と思われます。

コード例

var利用

import Foundation

class Person: CustomStringConvertible {
    let name: String
    
    init(name: String) {
        self.name = name
    }
    
    var description: String {
        "My name is \(self.name)."
    }
}

class Logic {
    var person: Person?
    
    init(person: Person) {
        self.person = person
    }
}

var person: Person? = Person(name: "hs7")
let logic = Logic(person: person!)
person = nil

print(logic.person)

// 出力結果
// Optional(My name is hs7.)

let利用

import Foundation

class Person: CustomStringConvertible {
    let name: String
    
    init(name: String) {
        self.name = name
    }
    
    var description: String {
        "My name is \(self.name)."
    }
}

class Logic {
    let person: Person?
    
    init(person: Person) {
        self.person = person
    }
}

var person: Person? = Person(name: "hs7")
let logic = Logic(person: person!)
person = nil

print(logic.person)

// 出力結果
// Optional(My name is hs7.)

weak var利用

import Foundation

class Person: CustomStringConvertible {
    let name: String
    
    init(name: String) {
        self.name = name
    }
    
    var description: String {
        "My name is \(self.name)."
    }
}

class Logic {
    weak var person: Person?
    
    init(person: Person) {
        self.person = person
    }
}

var person: Person? = Person(name: "hs7")
let logic = Logic(person: person!)
person = nil // 🐦このタイミングでlogic.personにnilがARCによって設定されます。logic.personはweakなためです。

print(logic.person)

// 出力結果
// nil

weak varにした場合のみ、person = nil 設定後、logic.personpersonにもnilが設定されます。

Unowned References

値を弱参照したいけど、nilにならないことがわかっているみたいな場合にはUnowned Referencesを利用するとよさそうです。こちらについてはインスタンスが破棄されてもARCはnilを設定しないため、unowned let も利用できます(破棄された後に参照すると signal SIGABRT. エラーになります)。私は unowned はあまり利用したことがないです。
https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html#ID54

import Foundation

class Person: CustomStringConvertible {
    let name: String

    init(name: String) {
        self.name = name
    }

    var description: String {
        "My name is \(self.name)."
    }
}

class Logic {
    unowned let person: Person

    init(person: Person) {
        self.person = person
    }
}

var person: Person? = Person(name: "hs7")
let logic = Logic(person: person!)
person = nil // このタイミングでpersonは破棄される。logic.personの参照は残ったまま。

print(logic.person) // ❌error: Execution was interrupted, reason: signal SIGABRT.

まとめ

weak var した場合の挙動についてまとめてみました。
weak var で宣言しておいた方が良いパターンとしては、ViewViewControllerなどのライフサイクルが存在するようなオブジェクトを、そのライフサイクルに依存しないオブジェクト内で変数として宣言する場合にweak var にしておくと、変にオブジェクトが維持されなくてメモリ効率がよくなりそうだし、誤動作(本来破棄されるタイミングで破棄されなかったViewを操作しようとするなど)が少なくなりそうだと思いました。

Discussion