swift 发布于 2022年07月06日
保存照片到系统相册
在我们开发 iOS APP 的时候,多少会遇到这样的需求,就是保存一张或者多张图片到系统相册中。这是一个很简单的功能,并且 iOS 也在很早就为开发者提供了相应的接口调用,就是 UIImageWriteToSavedPhotosAlbum
函数。
它的函数签名如下:
_ completionTarget: Any?,
_ completionSelector: Selector?,
_ contextInfo: UnsafeMutableRawPointer?)
这个函数接受三个参数,第一个参数 image
顾名思义,就是要保存到系统相册中的图片。 completionTarget
和 completionSelector
参数可以指定一个接受保存完成事件的一个示例和它的方法。
contextInfo
用于提供一个上下文信息,这里面的信息会通过参数传递给 completionSelector
函数。假如同时有多处调用这个方法,这个上下文信息就有用了,可以标识出本次完成回调代表的是哪一个调用。
好了,函数签名就介绍完了,本身并不复杂,如果你在搜索引擎中查找的话,可能会看到很多例子会这样写:
只传入了第一个 image 参数,后面三后参数都传入 nil
。 这样调用在大多数情况下也都没有问题。图片正常的保存到了系统相册,一行代码完成了我们的需求,看起来方便又好用。
但在某些情况下,这个调用方式就会有问题了。比如这样:
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
这里的 imageList
是一个图片数组,会包含多张图片,如果运行这样的代码,你就会发现你的图片保存会经常出错。 比如 imageList
里面有 10 张图片,可能在有些时候只能成功保存 4 张,后面的 6 张图片既没看到报错信息,也没成功。 但在另一些时候又能成功的把这 10 张图片都保存下来。
这种介于中间态的不确定状态是最让人头疼的。说它有问题,但不是每次都失败。说它没问题,但总有一些时候会失败,而且频率还不低。甚至会引起你对系统库本身稳定性的怀疑。
其实导致这个问题的原因,就是你少传入了第 2,3 个参数。 当然,如果说是系统库稳定性的问题,也不是完全没有道理。 不过与其说是稳定性,更提贴切的说法是这个函数的接口设计不够完善,容易让大家造成误解。
第 2,3 个参数用于指定一个接收完成事件的实例和方法,可以这样:
//...
for image in imageList {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.imageSaveFinished(image:error:context:)), nil)
}
}
func imageSaveFinished(image: UIImage, error: Error, context: UnsafeRawPointer) {
print(error)
}
这次指定了 imageSaveFinished
方法作为 UIImageWriteToSavedPhotosAlbum
函数的回调事件。 它接受三个参数, image
表示正在保存的图片, error
代表保存过程中发生的错误, context
代表上下文信息,前面我们提到过。
imageSaveFinished
用 print 方法打印出错误信息。 再重新运行一下项目,就会在控制台上看到类似这样的输出:
这个错误原因从字面上就可以看出来,是因为写入操作过于频繁,导致了写入失败。 因为看不到 UIImageWriteToSavedPhotosAlbum
的源码,所以它的内部机制不得而知,我们能得到的线索就是不能在同一个线程对它连续频繁调用。
从这个错误还可以得到一个信息,就是之所以会发生这个错误,是因为前面的写入操作还没有执行完成,就开始了下一个。那么我们是不是可以对调用做一个简单处理呢,等到上一个操作完成在进行下一个写入?
以现有的接口其实是可以做到的,还回到 imageSaveFinished
方法上来,我们可以在每次写入成功后,再调用 UIImageWriteToSavedPhotosAlbum
方法写入下一个图片,这样就不会发生连续写入图片导致的写入错误了:
if self.imageList.count > 0 {
if let image = self.imageList.first {
UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.imageSaveFinished(image:error:context:)), nil)
}
}
}
func imageSaveFinished(image: UIImage, error: Error, context: UnsafeRawPointer) {
self.imageList.removeFirst()
if self.imageList.count > 0 {
saveImage()
}
}
这次把 saveImage
方法改写了一下,每次调用,它只去 imageList
中第一个图片。 并且 imageList
也被声明成属性,可以跨方法访问。 然后在成功回调 imageSaveFinished
中,首先删除 imageList 中的第一张图片,也就这次保存成功的,然后判断 imageList
在删除后是否还有其他图片,如果有,那么继续调用 saveImage
方法保存。
这样,图片的保存就变成了顺序执行,只有上一张图片保存完成后,后面的图片才能开始保存。避免了频繁操作的问题。当然这里写的还是稍微简单,你还可以把它写的更健壮一些,比如在 imageSaveFinished
里面判断 error 是否为 nil 来界定回调发生时候图片是否真正的被保存成功。
但这个整体逻辑是没问题的,修改完成后在运行这个程序,你就会看到所有的批量图片保存操作都能成功的完成了。 再也不会出现之前的间歇性抽风问题了~
结束UIImageWriteToSavedPhotosAlbum
是 iOS 提供的一个非常方便的图片存储接口,大多数情况下它的使用很简单。当然对于保存多张图片的时候,还需要进行一些额外的处理,但总体还是很方便。在 iOS 10 以后的设备上使用它还需要注意一个细节,就是你需要在 Info.plist
中声明一个权限字段:
<string>保存图片</string>
这个字段用于给用户说明 APP 使用这个权限用来做什么,如果你声明这个字段,在 iOS 10 的设备上调用这个方法就会导致 Crash。
如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~
本站文章均为原创内容,如需转载请注明出处,谢谢。
![]() 发现更多精彩 swift-cafe |