快速合并接收器接收值内存泄漏

时间:2020-04-24 08:50:20

标签: swift memory-leaks combine

我在处理Combine时遇到了困难。发布者完成后,我想更新一个值,但是每当更新该值时,内存就被分配并且永远不会消失。

每当我尝试分配图像时,都会泄漏。如果我不分配泄漏。

编辑:此处可复制的示例:https://github.com/peterwarbo/MemoryAllocation

这是我的代码:

final class CameraController: ObservableObject {

    private var storage = Set<AnyCancellable>()    
    var image: UIImage?

    func capture(_ image: UIImage) {

        PhotoLibrary.saveImageToTemporaryDirectory(image) // AnyPublisher<URL, Error>
            .zip(PhotoLibrary.saveImage(image, location: self.locationObserver.location) // AnyPublisher<UIImage, Error>)
            .sink(receiveCompletion: { [weak self] (completion) in
                switch completion {
                case let .failure(error):
                    Log.error(error)
                    self?.handleCaptureError(error)
                case .finished: break
                }
            }) { [weak self] (value) in
                print(value.1) // no leak
                self.image = value.1 // leak

            }
            .store(in: &self.storage)
     }
}

我也尝试过不使用sink

.receive(
    subscriber: Subscribers.Sink(
        receiveCompletion: { [weak self] completion in
            switch completion {
            case let .failure(error):
                Log.error(error)
                self?.handleCaptureError(error)
            case .finished: break
            }
        },
        receiveValue: { value in
            print(value.1) // no leak
            self.image = value.1 // leak            
        }
    )
)

3 个答案:

答案 0 :(得分:5)

您的代码存在一个明显的问题,就是每次调用capture时都会创建并存储一个新管道。这与组合方法相反。您可能根本不使用合并。使用Combine的方法是创建一个管道一次,然后让信息从异步异步地传到管道中。

您发布了一个示例项目,在其中使用Future来引入将图像向下传递到管道中的延迟。在您的项目中,用户反复从照片库中选择图像。再一次,在您的项目中,每次选择图像时,都会创建并存储一个新管道。我将示例重写如下:

import UIKit
import Combine

class ViewController: UIViewController, UINavigationControllerDelegate {
    let queue = DispatchQueue(label: "Queue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem)
    var image: UIImage?
    var storage: Set<AnyCancellable> = []
    let publisher = PassthroughSubject<UIImage, Never>()
    override func viewDidLoad() {
        super.viewDidLoad()
        self.publisher
            .flatMap {image in
                self.futureMaker(image: image)
            }
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: { (completion) in
            }) { (value) in
                print("finished processing image")
                self.image = value
            }
            .store(in: &self.storage)
    }
    @IBAction func didTapPickImage(_ sender: UIButton) {
        let picker = UIImagePickerController()
        picker.delegate = self
        present(picker, animated: true)
    }
    func futureMaker(image: UIImage) -> AnyPublisher<UIImage, Never> {
        Future<UIImage, Never> { promise in
            self.queue.asyncAfter(deadline: .now() + 0.5) {
                promise(.success(image))
            }
        }.eraseToAnyPublisher()
    }
}
extension ViewController: UIImagePickerControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        dismiss(animated: true)
        guard let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
        print("got image")
        self.publisher.send(image)
    }
}

请注意体系结构:我在viewDidLoad中创建了一次管道,每当图像到达时,我都会将其向下传递到 same 管道。可以肯定的是,由于我们要存储UIImage,因此会使用一些内存。但它不会以任何不受控制的方式增长,而是以最佳方式稳定下来。

enter image description here

我们在重复选择库中的所有图像后使用了8.4 MB。没问题!

enter image description here

此外,没有多余的大图像持续存在。查看通过在图像选择器中进行选择而产生的内存,一张图像仍然存在;这是我们8.4 MB的2.7 MB:

enter image description here

这正是我们的期望。

答案 1 :(得分:0)

仅通过代码读取...使用弱自我,而不是直接自我

}) { [weak self] (value) in
    print(value.1) // no leak
    self?.image = value.1     // << here !!
    self?.storage.removeAll() // just in case
}

我还要在主队列上添加投放,例如

PhotoLibrary.saveImageToTemporaryDirectory(image)
    .zip(PhotoLibrary.saveImage(image, location: self.locationObserver.location)
    .receive(on: DispatchQueue.main)          // << here !!
    .sink(receiveCompletion: { [weak self] (completion) in
    // ... other code here

答案 2 :(得分:0)

您正在使用 .store(in: &self.storage)

需要取消这个私有var storage = Set()

storage.cancel() storage.removeAll()

而且自己也需要软弱