swift 组合接收器 receiveValue 内存泄漏
swift combine sink receiveValue memory leak
我很难处理 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
}
)
)
只是通过代码阅读...使用弱自我,而不是直接自我,
}) { [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
代码的一个明显问题是每次调用 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
中创建了一次管道,每当图像到达时,我都会将它传递到 same 管道。可以肯定的是,使用了一些内存,因为我们正在存储一个 UIImage;但它不会以任何不受控制的方式增长,而是以最佳方式趋于稳定。
我们在重复选取库中的所有图像后使用了 8.4 MB。没问题!
此外,没有多余的大图像持久存在。查看在图像选择器中选择的内存,一个图像仍然存在;那是我们 8.4 MB 中的 2.7 MB:
这正是我们所期望的。
您正在使用.store(in: &self.storage)
需要取消这个 private var storage = Set()
storage.cancel()
storage.removeAll()
而且自己也要软弱
我很难处理 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
}
)
)
只是通过代码阅读...使用弱自我,而不是直接自我,
}) { [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
代码的一个明显问题是每次调用 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
中创建了一次管道,每当图像到达时,我都会将它传递到 same 管道。可以肯定的是,使用了一些内存,因为我们正在存储一个 UIImage;但它不会以任何不受控制的方式增长,而是以最佳方式趋于稳定。
我们在重复选取库中的所有图像后使用了 8.4 MB。没问题!
此外,没有多余的大图像持久存在。查看在图像选择器中选择的内存,一个图像仍然存在;那是我们 8.4 MB 中的 2.7 MB:
这正是我们所期望的。
您正在使用.store(in: &self.storage)
需要取消这个 private var storage = Set()
storage.cancel() storage.removeAll()
而且自己也要软弱