ZIPFoundation:通过数据提供程序将大 PNG 写入存档时出现问题
ZIPFoundation: Issue with writing large PNG to archive via data provider
更新:我可以通过将 PNG 大小设置为超过任意值(即 700 x 700 pt 失败)来重现这一点。在任意值下读写正常。我不确定那条线到底在哪里。
我使用 zip 存档作为我的文档文件格式。尝试从文件浏览器页面的多个存档中读取 PNG 预览时,我看到了意外结果。
后台查询文档URL,创建文件数据对象。查询完成后,将在主线程上调用 UI 更新,文件数据对象充当集合视图的数据提供者。
PNG 被序列化为这样的数据:
let imageData = UIImagePNGRepresentation(image)
当读取数据时,相关条目被提取到内存中,然后反序列化到它们各自的对象中。
我看到的是文档缩略图只是间歇性地显示。 ZIPFoundation 是否有可能对于后台操作不是线程安全的?
我在一个更简单的测试项目中对此进行了模拟,它运行良好,但我没有阅读多个档案,也没有在后台调用它。我做错了什么(下面的代码)吗?
消费者闭包是否使用自己的线程(也许我在它完成之前返回数据)?
do {
let decoder = JSONDecoder()
let archive = Archive(url: URL, accessMode: .read)
// Metadata
if let metadataData = try archive?.readData(named: Document.MetadataFilename) {
self.metadata = try decoder.decode(Metadata.self, from: metadataData)
} else {
logDebug(self, "metadata not read")
}
// Preview
if let previewData = try archive?.readData(named: Document.PreviewFilename) {
if let image = UIImage(data: previewData) {
self.image = image
return
}
} else {
logDebug(self, "image not read")
}
} catch {
logError(self, "Loading of FileWrapper failed with error: \(error.localizedDescription)")
}
// Failure fall through
// Mark this as failed by using the x image
self.image = UIImage(named: "unavailable")
}
为了方便起见,我对存档的扩展:
/// Associates optional data with entry name
struct NamedData {
let name : String
let data : Data?
}
// MARK: - Private
extension Archive {
private func addData(_ entry: NamedData) throws {
let archive = self
let name = entry.name
let data = entry.data
do {
if let data = data {
try archive.addEntry(with: name, type: .file, uncompressedSize: UInt32(data.count), compressionMethod: .none, provider: { (position, size) -> Data in
return data
})
}
} catch {
throw error
}
}
private func removeData(_ entry: NamedData) throws {
let archive = self
let name = entry.name
do {
if let entry = archive[name] { try archive.remove(entry) }
} catch {
throw error
}
}
}
// MARK: - Public
extension Archive {
/// Update NamedData entries in the archive
func updateData(entries: [NamedData]) throws {
// Walk each entry and overwrite
do {
for entry in entries {
try removeData(entry)
try addData(entry)
}
} catch {
throw error
}
}
/// Add NamedData entries to the archive (updateData is the preferred
/// method since no harm comes from removing entries before adding them)
func addData(entries: [NamedData]) throws {
// Walk each entry and create
do {
for entry in entries {
try addData(entry)
}
} catch {
throw error
}
}
/// Read Data out of the entry using its name
func readData(named name: String) throws -> Data? {
let archive = self
// Get data from entry
do {
var entryData : Data? = nil
if let entry = archive[name] {
// _ = returned checksum
let _ = try archive.extract(entry, consumer: { (data) in
entryData = data
})
}
return entryData
} catch {
throw error
}
}
}
ZIPFoundation 中基于闭包的 API 旨在 provide/consume 分块数据。根据数据的最终大小和配置的块大小(可选参数,默认为 16*1024),provider/consumer 闭包可以被多次调用。
当您通过
提取条目时
let _ = try archive.extract(entry, consumer: { (data) in
entryData = data
})
你总是用 consumer
闭包提供的最新块覆盖 entryData
(如果最终大小大于块大小)。
您可以使用
var entryData = Data()
let _ = try archive.extract(entry, consumer: { (data) in
entryData.append(data)
})
确保整个条目都累积在 entryData
对象中。
您的 Provider
代码中也发生了类似的事情。每次调用闭包时,您都应该提供一个块(从 position
开始,size
),而不是总是返回整个图像数据对象。
我遇到了同样的问题,以下对我有用:
archive.addEntry(with: "entryName",
type: .file,
uncompressedSize: dataSize,
compressionMethod: .none,
provider: { (position, size) in
return data.subdata(in: (position ..< position + size))})
更新:我可以通过将 PNG 大小设置为超过任意值(即 700 x 700 pt 失败)来重现这一点。在任意值下读写正常。我不确定那条线到底在哪里。
我使用 zip 存档作为我的文档文件格式。尝试从文件浏览器页面的多个存档中读取 PNG 预览时,我看到了意外结果。
后台查询文档URL,创建文件数据对象。查询完成后,将在主线程上调用 UI 更新,文件数据对象充当集合视图的数据提供者。
PNG 被序列化为这样的数据:
let imageData = UIImagePNGRepresentation(image)
当读取数据时,相关条目被提取到内存中,然后反序列化到它们各自的对象中。
我看到的是文档缩略图只是间歇性地显示。 ZIPFoundation 是否有可能对于后台操作不是线程安全的?
我在一个更简单的测试项目中对此进行了模拟,它运行良好,但我没有阅读多个档案,也没有在后台调用它。我做错了什么(下面的代码)吗?
消费者闭包是否使用自己的线程(也许我在它完成之前返回数据)?
do {
let decoder = JSONDecoder()
let archive = Archive(url: URL, accessMode: .read)
// Metadata
if let metadataData = try archive?.readData(named: Document.MetadataFilename) {
self.metadata = try decoder.decode(Metadata.self, from: metadataData)
} else {
logDebug(self, "metadata not read")
}
// Preview
if let previewData = try archive?.readData(named: Document.PreviewFilename) {
if let image = UIImage(data: previewData) {
self.image = image
return
}
} else {
logDebug(self, "image not read")
}
} catch {
logError(self, "Loading of FileWrapper failed with error: \(error.localizedDescription)")
}
// Failure fall through
// Mark this as failed by using the x image
self.image = UIImage(named: "unavailable")
}
为了方便起见,我对存档的扩展:
/// Associates optional data with entry name
struct NamedData {
let name : String
let data : Data?
}
// MARK: - Private
extension Archive {
private func addData(_ entry: NamedData) throws {
let archive = self
let name = entry.name
let data = entry.data
do {
if let data = data {
try archive.addEntry(with: name, type: .file, uncompressedSize: UInt32(data.count), compressionMethod: .none, provider: { (position, size) -> Data in
return data
})
}
} catch {
throw error
}
}
private func removeData(_ entry: NamedData) throws {
let archive = self
let name = entry.name
do {
if let entry = archive[name] { try archive.remove(entry) }
} catch {
throw error
}
}
}
// MARK: - Public
extension Archive {
/// Update NamedData entries in the archive
func updateData(entries: [NamedData]) throws {
// Walk each entry and overwrite
do {
for entry in entries {
try removeData(entry)
try addData(entry)
}
} catch {
throw error
}
}
/// Add NamedData entries to the archive (updateData is the preferred
/// method since no harm comes from removing entries before adding them)
func addData(entries: [NamedData]) throws {
// Walk each entry and create
do {
for entry in entries {
try addData(entry)
}
} catch {
throw error
}
}
/// Read Data out of the entry using its name
func readData(named name: String) throws -> Data? {
let archive = self
// Get data from entry
do {
var entryData : Data? = nil
if let entry = archive[name] {
// _ = returned checksum
let _ = try archive.extract(entry, consumer: { (data) in
entryData = data
})
}
return entryData
} catch {
throw error
}
}
}
ZIPFoundation 中基于闭包的 API 旨在 provide/consume 分块数据。根据数据的最终大小和配置的块大小(可选参数,默认为 16*1024),provider/consumer 闭包可以被多次调用。
当您通过
提取条目时let _ = try archive.extract(entry, consumer: { (data) in
entryData = data
})
你总是用 consumer
闭包提供的最新块覆盖 entryData
(如果最终大小大于块大小)。
您可以使用
var entryData = Data()
let _ = try archive.extract(entry, consumer: { (data) in
entryData.append(data)
})
确保整个条目都累积在 entryData
对象中。
您的 Provider
代码中也发生了类似的事情。每次调用闭包时,您都应该提供一个块(从 position
开始,size
),而不是总是返回整个图像数据对象。
我遇到了同样的问题,以下对我有用:
archive.addEntry(with: "entryName",
type: .file,
uncompressedSize: dataSize,
compressionMethod: .none,
provider: { (position, size) in
return data.subdata(in: (position ..< position + size))})