0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

循環参照についてさらっとまとめてみる

Posted at

はじめに

下記順番でアウトプットしていく。

  • 循環参照とは?
  • 循環参照の例
  • 循環参照したときの修正案(さらっとまとめなので一部アウトプット)
  • おまけ(ちょっと複雑な循環参照の可能性を示唆するコードの修正案)

循環参照とは?

循環参照とは、Swiftプログラムでクラス同士がお互いに強い参照(強参照)を持つときに発生する。
これにより、メモリが正しく解放されず、プログラムが予期しない挙動をする可能性がある。(アプリが落ちるとかね)

循環参照の例

例えば、クラスAがクラスBを保持し、同時にクラスBもクラスAを保持する場合、お互いが解放されない状態になる。下記コードは、最後の行のinstanceA = nilinstanceB = nilを実行しても、クラスAとクラスBのインスタンスは解放されず、解放されたメッセージも表示されない。

Swift
class ClassA {
    var classB: ClassB? // AでBを保持するため型を定義
    
    deinit {
        print("ClassAが解放されました")
    }
}

class ClassB {
    var classA: ClassA? // BでAを保持するため型を定義
    
    deinit {
        print("ClassBが解放されました")
    }
}

var instanceA: ClassA? = ClassA()
var instanceB: ClassB? = ClassB()

instanceA?.classB = instanceB // AでBを保持する
instanceB?.classA = instanceA // BでAを保持する

instanceA = nil // インスタンスは解放されない
instanceB = nil // インスタンスは解放されない

循環参照したときの修正案(さらっとまとめなので一部アウトプット)

循環参照を避けるためには、クラスAかクラスBのどちらかのプロパティにweak修飾子、つまり、弱い参照(弱参照)を使用する。または参照関係を見直す。
今回は、弱い参照(弱参照)の方で対応する。

Swift
class ClassA {
    var classB: ClassB?
    
    deinit {
        print("ClassAが解放されました")
    }
}

class ClassB {
    weak var classA: ClassA? // weak付けて弱参照にする
    
    deinit {
        print("ClassBが解放されました")
    }
}

var instanceA: ClassA? = ClassA()
var instanceB: ClassB? = ClassB()

instanceA?.classB = instanceB
instanceB?.classA = instanceA

// 循環参照が解消され、インスタンスが適切に解放されるようになる。
instanceA = nil
instanceB = nil

おまけ(ちょっと複雑な循環参照の可能性を示唆するコードの修正案)

  • 前提
    • MVVMを想定した上でSwiftUIを使用し、Combineを用いてAPI通信を行う。
    • ModelとViewModelの部分である。
    • 循環参照です!がメインなのでデータ取得箇所などあまりよろしくないコードである。
struct ItemModel: Codable {
    let id: Int
    let title: String
}

class ItemViewModel: ObservableObject {
    private let apiService = APIService()

    @Published var items: [ItemModel] = []
    @Published var errorMessage: String = ""
    private var cancellables = Set<AnyCancellable>()

    func fetchItems() {
        apiService.fetchItems()
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { [weak self] completion in
                switch completion {
                case .failure(let error):
                    self?.errorMessage = "An error occurred: \(error.localizedDescription)"
                case .finished:
                    break
                }
            },
            receiveValue: { [weak self] items in
                self?.items = items
                self?.errorMessage = ""
            })
            .store(in: &cancellables)
    }
}

class APIService {
    func fetchItems() -> AnyPublisher<[ItemModel], Error> {
        let url = URL(string: "https://api.example.com/items")! // 仮のAPIエンドポイント
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [ItemModel].self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

おわりに

間違い等ありましたらコメント欄にてご指摘ください!

参考記事

開発環境

  • Xcode-14.3
  • Swift version 5.8
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?