быстрый приемник комбайна получает значение утечки памяти - PullRequest
0 голосов
/ 24 апреля 2020

Мне тяжело иметь дело с Комбинатом. После завершения публикации я хочу обновить значение, но всякий раз, когда я обновляю это значение, память выделяется и никогда не уходит.

Всякий раз, когда я пытаюсь назначить изображение, возникает утечка. Если я не назначу никакой утечки.

РЕДАКТИРОВАТЬ: Воспроизводимый пример здесь: 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            
        }
    )
)

Ответы [ 2 ]

0 голосов
/ 28 апреля 2020

Очевидная проблема с вашим кодом заключается в том, что вы создаете и сохраняете новый конвейер каждый раз, когда вызывается capture. Это противоположно тому, как использовать Combine; с тем же успехом вы можете вообще не использовать Combine. Способ использования 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, и всякий раз, когда приходит изображение, я передаю его по тому же конвейеру. Конечно, используется некоторая память, потому что мы храним UIImage; но он не растет неконтролируемым образом, но выравнивается оптимальным образом.

enter image description here

Мы используем 8,4 МБ после выбора всех изображений в библиотеке неоднократно. Нет проблем!

enter image description here

Кроме того, излишки больших изображений не сохраняются. Если посмотреть на память, которая появляется при выборе средства выбора изображений, изображение one сохраняется; это 2,7 МБ из наших 8,4 МБ:

enter image description here

Это именно то, что мы ожидаем.

0 голосов
/ 26 апреля 2020

Просто при чтении кода ... используйте слабое "я", а не прямое "я",

}) { [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
...