我有一组9张图片,我想将它们全部保存到用户的相机胶卷中。您可以使用UIImageWriteToSavedPhotosAlbum
执行此操作。我写了一个循环来保存每个图像。这个问题是由于某种原因,它会only save the first five。现在,顺序很重要,所以如果图像无法保存,我想重试并等到它成功,而不是有一些不可预测的种族。
所以,我实现了一个完成处理程序,并认为我可以像这样使用信号量:
func save(){
for i in (0...(self.imagesArray.count-1)).reversed(){
print("saving image at index ", i)
semaphore.wait()
let image = imagesArray[i]
self.saveImage(image)
}
}
func saveImage(_ image: UIImage){
UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}
func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
//due to some write limit, only 5 images get written at once.
if let error = error {
print("trying again")
self.saveImage(image)
} else {
print("successfully saved")
semaphore.signal()
}
}
我的代码的问题是它在第一次保存后被阻止,semaphore.signal永远不会被调用。我认为我的完成处理程序应该在主线程上调用,但是已经被semaphore.wait()阻止了。任何帮助赞赏。感谢
答案 0 :(得分:1)
正如其他人所指出的,你想避免等待主线程,冒着死锁的风险。因此,虽然您可以将其推送到全局队列,但另一种方法是使用众多机制之一来执行一系列异步任务。选项包括异步Operation
子类或承诺(例如PromiseKit)。
例如,要将图像保存任务包装在异步Operation
中并将其添加到OperationQueue
,您可以像这样定义图像保存操作:
class ImageSaveOperation: AsynchronousOperation {
let image: UIImage
let imageCompletionBlock: ((NSError?) -> Void)?
init(image: UIImage, imageCompletionBlock: ((NSError?) -> Void)? = nil) {
self.image = image
self.imageCompletionBlock = imageCompletionBlock
super.init()
}
override func main() {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}
func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
imageCompletionBlock?(error)
complete()
}
}
然后,假设您有一个数组images
,即[UIImage]
,那么您可以这样做:
let queue = OperationQueue()
queue.name = Bundle.main.bundleIdentifier! + ".imagesave"
queue.maxConcurrentOperationCount = 1
let operations = images.map {
return ImageSaveOperation(image: $0) { error in
if let error = error {
print(error.localizedDescription)
queue.cancelAllOperations()
}
}
}
let completion = BlockOperation {
print("all done")
}
operations.forEach { completion.addDependency($0) }
queue.addOperations(operations, waitUntilFinished: false)
OperationQueue.main.addOperation(completion)
显然,您可以自定义此选项以在出错时添加重试逻辑,但现在可能不需要这样做,因为“太忙”问题的根源是太多并发保存请求的结果,我们已将其消除。这只会留下不太可能通过重试解决的错误,所以我可能不会添加重试逻辑。 (错误更可能是权限失败,空间不足等。)但是,如果您真的想要,可以添加重试逻辑。更有可能的是,如果您有错误,您可能只想取消队列中的所有剩余操作,就像我上面一样。
注意,上面的子类AsynchronousOperation
,它只是Operation
子类,isAsynchronous
返回true
。例如:
/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
/// necessary and then ensuring that `completeOperation()` is called; or
/// override `cancel` method, calling `super.cancel()` and then cleaning-up
/// and ensuring `completeOperation()` is called.
public class AsynchronousOperation : Operation {
private let syncQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".opsync")
override public var isAsynchronous: Bool { return true }
private var _executing: Bool = false
override private(set) public var isExecuting: Bool {
get {
return syncQueue.sync { _executing }
}
set {
willChangeValue(forKey: "isExecuting")
syncQueue.sync { _executing = newValue }
didChangeValue(forKey: "isExecuting")
}
}
private var _finished: Bool = false
override private(set) public var isFinished: Bool {
get {
return syncQueue.sync { _finished }
}
set {
willChangeValue(forKey: "isFinished")
syncQueue.sync { _finished = newValue }
didChangeValue(forKey: "isFinished")
}
}
/// Complete the operation
///
/// This will result in the appropriate KVN of isFinished and isExecuting
public func complete() {
if isExecuting { isExecuting = false }
if !isFinished { isFinished = true }
}
override public func start() {
if isCancelled {
isFinished = true
return
}
isExecuting = true
main()
}
}
现在,我很欣赏操作队列(或承诺)对你的情况来说似乎有些过分,但它是一个有用的模式,你可以在任何有一系列异步任务的地方使用它。有关操作队列的更多信息,请随时参考Concurrency Programming Guide: Operation Queues。
答案 1 :(得分:0)
一年前我面临同样的问题
您应该尝试将代码放在Dispatach.global
队列中,这肯定会有帮助
原因:我真的不知道原因,我认为它是什么信号量,可能需要在后台线程中执行以同步等待和信号
答案 2 :(得分:0)
正如迈克改变提到的那样,使用Dispatch.global().async
帮助解决了这个问题。代码现在看起来像这样:
func save(){
for i in (0...(self.imagesArray.count-1)).reversed(){
DispatchQueue.global().async { [unowned self] in
self.semaphore.wait()
let image = self.imagesArray[i]
self.saveImage(image)
}
}
}
我怀疑问题是完成处理程序在主线程中执行,主线程已被最初调用的semaphore.wait()
锁定。因此,当完成时,semaphore.signal()
永远不会被调用。
这可以通过将任务运行为异步队列来解决。