ZIPFoundation: проблема с записью большого PNG в архив через провайдера данных - PullRequest
0 голосов
/ 02 мая 2018

ОБНОВЛЕНИЕ: я могу воспроизвести это, установив размер PNG, превышающий произвольное значение (то есть 700 x 700 pt терпит неудачу). Под произвольным значением пишет и читает нормально. Я не уверен точно, где эта линия.

В качестве формата файла документа я использую zip-архив. Я вижу неожиданные результаты при попытке прочитать в предварительном просмотре PNG из нескольких архивов страницу браузера файлов.

URL-адреса документов запрашиваются в фоновом режиме, а затем создается объект данных файла. Как только запрос завершается, в основном потоке вызывается обновление пользовательского интерфейса, а объекты данных файла выступают в качестве поставщика данных для представления сбора.

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
        }
    }
}

1 Ответ

0 голосов
/ 02 мая 2018

API-интерфейсы на основе замыканий в ZIPFoundation предназначены для предоставления / потребления кусочных данных. В зависимости от конечного размера ваших данных и настроенного размера чанка (необязательный параметр, по умолчанию 16 * 1024), закрытия поставщика / потребителя могут вызываться несколько раз.

Когда вы извлекаете запись через

let _ = try archive.extract(entry, consumer: { (data) in
    entryData = data
})

вы всегда перезаписываете entryData с последним фрагментом, закрываемым закрытием consumer (если конечный размер больше, чем размер фрагмента).

Вместо этого вы можете использовать

var entryData = Data()
let _ = try archive.extract(entry, consumer: { (data) in
    entryData.append(data)
})

, чтобы убедиться, что вся запись накапливается в объекте entryData.

Аналогичная вещь происходит в вашем Provider коде. Вместо того, чтобы всегда возвращать весь объект данных изображения, вы должны предоставлять блок (начиная с position с size) каждый раз, когда вызывается замыкание.

...