Possible Memory Leak w/Combine's Future Publisher?

Wondering if I'm doing something obviously wrong in the code below. It very loosely mimics the pattern used to capture Effects in the Swift Composable Architecture. After tapping "Press Me" and waiting for the Text to update, Xcode's "Debug Memory Graph" is showing a leaked Future<String,Never>.Conduit. Instruments shows it as well. Tapping "Press Me" again and waiting leaks a second Future<String,Never>.Conduit. Interestingly, if I tap "Press Me" multiple times in quick succession, only the Futures that complete, leak.

The code below was written to drop into "ContentView.swift" in a new "Single View App" project in Xcode. I've replicated this finding with Xcode 11.2.1, 11,3.1, 11.4.1, 11.5beta & 11.5beta2.

I've filed Feedback #7700518

Thanks,
--David

import Combine
import SwiftUI

var futureCancellable: AnyCancellable?

struct ContentView: View {

    @State var buttonPressTime: String? = nil

    var body: some View {
        VStack {
            Text(buttonPressTime ?? "Not Pressed Yet")
            Button(
                action: {
                    futureCancellable = Future<String,Never> { callback in
                        let timeString = "\(Date())"

                        DispatchQueue.main.asyncAfter(
                            deadline: .now() + 2
                        ) {
                            callback(.success(timeString))
                        }
                    }.sink(
                        receiveCompletion: { _ in
                            futureCancellable = nil
                        },
                        receiveValue: {
                            self.buttonPressTime = $0
                        }
                    )
                },
                label: {
                    Text("Press Me")
                }
            )
        }
    }
}
4 Likes

Hello, I've been having the same issue since I started working with Combine a few months ago. I have built a Combine wrapper around FirebaseFirestore to use in my app and I'm getting the same memory leak on Future<X,Y>.Conduit. The next example reproduces it.

struct Root: View {
    @ObservedObject var vm: ViewModel
    
    var body: some View {
        Text("Hello World!")
    }
}

class ViewModel: ObservableObject {
    var subscriptions = Set<AnyCancellable>()

    init() {
        myFuture()
            .sink { (message) in
                print(message)
        }
        .store(in: &self.subscriptions)
    }
    
    deinit {
        self.subscriptions.removeAll()
    }
    
    func myFuture() -> AnyPublisher<String, Never> {
        Future<String, Never> { promise in
            DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
                promise(.success("Leaking???"))
            }
        }
        .eraseToAnyPublisher()
    }
}

Since there's no other object referencing the leaked instance I've come to assume that it's a Combine bug, and I really hope Apple will fix it soon.

Same issue here. Has anyone found a solution?
I've tried replacing the Future with a PassthroughSubject which worked – but I'm still wondering what is causing the leak…

1 Like

Hi, this issue seems to be fixed on Xcode 12. I just tested it, no more leaks.

3 Likes

I hit the memory leak issue with Xcode 12.0. The memory leak graph shows a Future.conduit, downstreamLock, lock with two malloc blocks.

I used the same way, by replacing the Future with a PassthroughSubject, the memory leak disappeared.

Here is a snippet I created for replacing Future:

I came across this issue also earlier this year. Dropped Combine from the app altogether ever since because it was causing too many leaks. Most of them were coming from Future publishers.

Check this topic I posted on the forum at that time: Combine nested publishers leak - #8 by _vladi.

I also filed a radar to Apple with this issue. I haven't got any useful or clear answer ever since.