【问题标题】:How to unzip a big zip file containing one file and get the progress in bytes with swift?如何解压缩包含一个文件的大 zip 文件并快速获取以字节为单位的进度?
【发布时间】:2015-07-25 18:24:24
【问题描述】:

我尝试解压缩一个仅包含一个项目(超过 100MB)的大 zip 文件,并希望在解压缩过程中显示进度。

我找到了可以根据解压缩文件的数量确定进度的解决方案,但在我的情况下,我里面只有一个大文件。所以我猜它必须由解压缩的字节数决定?

实际上我正在使用 SSZipArchive 和以下代码,它工作正常:

    var myZipFile:NSString="/Users/user/Library/Developer/CoreSimulator/Devices/mydevice/ziptest/testzip.zip";
    var DestPath:NSString="/Users/user/Library/Developer/CoreSimulator/Devices/mydevice/ziptest/";


    let unZipped = SSZipArchive.unzipFileAtPath(myZipFile as! String, toDestination: DestPath as! String);

我没有找到解决方案。

有人有提示、示例或示例链接吗?

更新: 下面的代码看起来可以按预期工作,但是当只有一个文件被解压缩时,处理程序只会被调用一次(在解压缩结束时):

func unzipFile(sZipFile: String, toDest: String){

        SSZipArchive.unzipFileAtPath(sZipFile, toDestination: toDest, progressHandler: {
            (entry, zipInfo, readByte, totalByte) -> Void in


            println("readByte : \(readByte)") // <- This will be only called once, at the end of unzipping. My 500MB Zipfile holds only one file. 
            println("totalByte : \(totalByte)")


            //Asynchrone task
            dispatch_async(dispatch_get_main_queue()) {
                println("readByte : \(readByte)")
                println("totalByte : \(totalByte)")

                //Change progress value

            }
            }, completionHandler: { (path, success, error) -> Void in
                if success {
                    //SUCCESSFUL!!
                } else {
                    println(error)
                }
        })

    }

更新 2:

正如“Martin R”在 SSArchive 中分析的那样,这是不可能的。 有没有其他方法可以解压文件并显示基于 kbytes 的进度?

更新 3:

在“roop”解释解决方案后,我更改了 SSZipArchive.m,如下所示。可能其他人也可以使用它:

FILE *fp = fopen((const char*)[fullPath UTF8String], "wb");
                while (fp) {
                    int readBytes = unzReadCurrentFile(zip, buffer, 4096);

                    if (readBytes > 0) {
                        fwrite(buffer, readBytes, 1, fp );
                        totalbytesread=totalbytesread+4096;
                        // Added by me
                        if (progressHandler)
                        {
                            progressHandler(strPath, fileInfo, currentFileNumber, totalbytesread);
                        }
                        // End added by me

                    } else {
                        break;
                    }
                }

【问题讨论】:

  • 你用的是什么解压库?
  • 我和别人一起用过 SSZipArchive
  • 如果我正确理解了 SSZipArchive 的源代码,则每个文件都会调用一次进度处理程序,并且在解压缩单个文件时不提供任何获取进度的选项。
  • 感谢您的确认。这就是我所怀疑的。有没有其他方法可以实现这一目标?我的意思是我不固定到 SSZipArchive。
  • @mcfly soft,我认为这可以在zlib的帮助下实现。

标签: ios swift streaming archive unzip


【解决方案1】:

SSZipArchive 已经六年没有更新了,你需要一个新的选择。

Zip:用于压缩和解压缩文件的 Swift 框架。

let filePath = Bundle.main.url(forResource: "file", withExtension: "zip")!
let documentsDirectory = FileManager.default.urls(for:.documentDirectory, in: .userDomainMask)[0]
try Zip.unzipFile(filePath, destination: documentsDirectory, overwrite: true, password: "password", progress: { (progress) -> () in
    print(progress)
}) // Unzip

let zipFilePath = documentsFolder.appendingPathComponent("archive.zip")
try Zip.zipFiles([filePath], zipFilePath: zipFilePath, password: "password", progress: { (progress) -> () in
    print(progress)
}) //Zip

【讨论】:

    【解决方案2】:

    据我了解,最明显的答案是修改 SSZipArchive 的内部代码。但我决定走不同的路,写了这个扩展。这很容易理解,但请不要犹豫,提出任何问题。

    另外,如果您认为我的解决方案有缺陷或者您知道如何改进它,我很乐意听到。

    这里有一个解决方案:

    import Foundation
    import SSZipArchive
    
    typealias ZippingProgressClosure = (_ zipBytes: Int64, _ totalBytes: Int64) -> ()
    private typealias ZipInfo = (contentSize: Int64, zipPath: String, progressHandler: ZippingProgressClosure)
    
    extension SSZipArchive
    {
        static func createZipFile(atPath destinationPath: String,
                                  withContentsOfDirectory contentPath: String,
                                  keepParentDirectory: Bool,
                                  withPassword password: String? = nil,
                                  byteProgressHandler: @escaping ZippingProgressClosure,
                                  completionHandler: @escaping ClosureWithSuccess)
        {
            DispatchQueue.global(qos: .background).async {
    
                var timer: Timer? = nil
                DispatchQueue.main.async {
    
                    //that's a custom function for folder's size calculation
                    let contentSize = FileManager.default.sizeOfFolder(contentPath) 
                    timer = Timer.scheduledTimer(timeInterval: 0.1,
                                                 target: self,
                                                 selector: #selector(progressUpdate(_:)),
                                                 userInfo: ZipInfo(contentSize: contentSize,
                                                                   zipPath: destinationPath,
                                                                   progressHandler: byteProgressHandler),
                                                 repeats: true)
                }
    
                let isSuccess = SSZipArchive.createZipFile(atPath: destinationPath,
                                                           withContentsOfDirectory: contentPath,
                                                           keepParentDirectory: keepParentDirectory,
                                                           withPassword: password,
                                                           andProgressHandler: nil)
    
                DispatchQueue.main.async {
                    timer?.invalidate()
                    timer = nil
                    completionHandler(isSuccess)
                }
            }
        }
    
        @objc private static func progressUpdate(_ sender: Timer)
        {
            guard let info = sender.userInfo as? ZipInfo,
                FileManager.default.fileExists(atPath: info.zipPath),
                let zipBytesObj = try? FileManager.default.attributesOfItem(atPath: info.zipPath)[FileAttributeKey.size],
                let zipBytes = zipBytesObj as? Int64 else {
                    return
            }
    
            info.progressHandler(zipBytes, info.contentSize)
        }
    }
    

    和方法就是这样使用的:

    SSZipArchive.createZipFile(atPath: destinationUrl.path,
                                   withContentsOfDirectory: fileUrl.path,
                                   keepParentDirectory: true,
                                   byteProgressHandler: { (zipped, expected) in
    
                                    //here's the progress code
        }) { (isSuccess) in
            //here's completion code
        }
    

    优点:您无需修改​​内部代码,这些代码将被 pod 更新覆盖

    缺点:如您所见,我以 0.1 秒的间隔更新文件大小信息。我不知道获取文件元数据是否会导致性能过载,我找不到任何相关信息。

    无论如何,我希望我能帮助别人:)

    【讨论】:

      【解决方案3】:

      要实现你想要的,你必须修改 SSZipArchive 的内部代码。

      SSZipArchive 使用 minizip 来提供压缩功能。您可以在此处查看 minizip 解压 API:unzip.h

      在 SSZipArchive.m 中,您可以从 fileInfo variable 获取解压缩文件的未压缩大小。

      可以看到正在读取解压后的内容here

       FILE *fp = fopen((const char*)[fullPath UTF8String], "wb");
       while (fp) {
           int readBytes = unzReadCurrentFile(zip, buffer, 4096);
           if (readBytes > 0) {
               fwrite(buffer, readBytes, 1, fp );
           } else {
               break;
           }
       }
      

      您将需要readBytes 和未压缩的文件大小来计算单个文件的进度。您可以向 SSZipArchive 添加新委托,以将这些数据发送回调用代码。

      【讨论】:

      • 非常感谢您的帮助。我想这是正确的答案,这就是我接受它的原因。我没有实际尝试,但我看到了解决方案。当然,如果有人可以将代码与委托粘贴到 SSZipArchive.m 中,我不会不高兴,因为我更熟悉 swift :-)
      • 另一个建议:如果你对 Obj-C 不满意,你可以直接使用 minizip(而不是使用 SSZipArchive)——Swift 可以直接链接到 C 代码。
      • 我实施了解决方案。 (有点不同),它的工作原理。我更新了我对问题的更改。非常感谢您再次提供帮助。
      • 看起来不错。只要您只处理一个文件的 zip,就应该这样做。一个小的修正:你可能想说totalbytesread=totalbytesread+readBytes; 这样totalbytesread 永远不会超过未压缩的大小(因此进度永远不会超过 100%)。
      【解决方案4】:

      你可以试试这个代码:

          SSZipArchive.unzipFileAtPath(filePath, toDestination: self.destinationPath, progressHandler: { 
      (entry, zipInfo, readByte, totalByte) -> Void in
            //Create UIProgressView
            //Its an exemple, you can create it with the storyboard...
            var progressBar : UIProgressView?
            progressBar = UIProgressView(progressViewStyle: .Bar)
            progressBar?.center = view.center
            progressBar?.frame = self.view.center
            progressBar?.progress = 0.0
            progressBar?.trackTintColor = UIColor.lightGrayColor();
            progressBar?.tintColor = UIColor.redColor();
            self.view.addSubview(progressBar)
      
            //Asynchrone task                
            dispatch_async(dispatch_get_main_queue()) {
                 println("readByte : \(readByte)")
                 println("totalByte : \(totalByte)")                               
      
                 //Change progress value
                 progressBar?.setProgress(Float(readByte/totalByte), animated: true)
                 //If progressView == 100% then hide it
                 if readByte == totalByte {
                     progressBar?.hidden = true
                 }
             }
      }, completionHandler: { (path, success, error) -> Void in
          if success {
              //SUCCESSFUL!!
          } else {
              println(error)
          }
      })
      

      希望对你有所帮助!

      是的

      【讨论】:

      • 非常感谢您的帮助。我会尽快检查。听起来很有希望。
      • 感谢您的帮助,但它无法正常工作。当我调试你的代码时,它只会调用一次progressHandler。这是在解压缩 Zipfile 的时候。就我而言,我在 zip 中只有一个文件,所以我猜它只会为每个解压缩文件调用处理程序。在我的情况下,这意味着它将在解压缩结束时调用,并且在解压缩过程中不会显示任何进度。有什么建议吗?
      • 我将我的实际代码粘贴到问题中。我通过删除 UIProgressView 简化了代码。我们也可以使用 println 在控制台中查看进度。
      • 没有。 println 有效,但它只会被调用一次。我正在寻找一种解决方案,在解压缩一个只有一个大文件的大型 zip 文件期间,我可以看到进度。如果您在一个 zipfile 中压缩了很多小文件,建议的解决方案将显示进度,但对于仅包含一个文件的 zipfile 则不会。
      猜你喜欢
      • 1970-01-01
      • 2018-08-02
      • 2013-10-17
      • 2021-11-19
      • 2022-07-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-29
      相关资源
      最近更新 更多