swiftconcurrencyweak-referencesdispatch-asyncweak

Weak self does nothing or what am i doing wrong?


I created a project to test [weak self]

There is:

So, in the code I use weak self, that makes ViewModel deinitialized, when user closes the ContentView. But if I open CPU graphs, there still are calculations, making to the end, even if this instance was deinitialized:

CPU and memory charts compiresment (with weak self and without)

If you compare this graphs with use of [weak self] and without it - there is absolutaly no difference.

Here is the whole code:

struct ContentView: View {
    @StateObject var vm = ViewModel()

    var body: some View {
        VStack {
            Text("App started")
            Text(vm.helloData.count.description)
        }
        .padding()
    }
}

class ViewModel: ObservableObject {
    @Published var helloData: [String] = []

    init() {
        print("ViewModel инициализирован")
        
        let count = UserDefaults.standard.integer(forKey: "count")
        UserDefaults.standard.set(count+1, forKey: "count")


        var data: [String] = []
        DispatchQueue.global().async { [weak self] in
            for _ in (0..<100_000_000) {
                data.append("Hello, world!")
            }
            DispatchQueue.main.sync {
                self?.helloData = data
            }
        }
    }

    deinit {
        print("ViewModel деинициализирован")

        let count = UserDefaults.standard.integer(forKey: "count")
        UserDefaults.standard.set(count-1, forKey: "count")
    }
}

struct ContentView: View {
    @StateObject var vm = ViewModel()

    var body: some View {
        VStack {
            Text("App started")
            Text(vm.helloData.count.description)
        }
        .padding()
    }
}

struct MenuView: View {
    @AppStorage ("count") var count: Int = 0

    init() {
        self.count = 0
    }

    var body: some View {
        NavigationStack{
            NavigationLink("go to ContentView", destination: ContentView())
        }
        .overlay {
            VStack {
                Text(count.description)
                    .font(.title)
                Spacer()
            }
        }
    }
}

Where am i wrong? As i understand, the reason of using weak self is to get rid of this useless calculations...


Solution

  • As i understand, the reason of using weak self is to get rid of this useless calculations

    No it is't. The point of using weak self is to break potential reference cycles. In your example, I don't think it's a problem anyway. The closure maintains a reference to self because of the assignment to self.helloData but there's no reference from self to the closure that I can see: the only thing that holds a reference to the closure will be the dispatch queue - or the task on it.

    I assume by "useless calculations" you mean the appends to data inside the for loop, but data is a local variable, not a property: there's no reason why weak self would affect it. You can explicitly exit the loop if self does go away with a guard inside the loop e.g.

    guard self != nil else { return } // or break
    

    Incidentally, weak references impose a slight performance penalty because of the way they are implemented. IIRC when the instance is deallocated, all weak references have to be set to nil and I think it is done via an extra level of indirection, somehow.

    Edit: the implementation is slightly more complex than I thought

    https://www.mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html