Search code examples
objective-ccocoaziparchive

Creating a ZIP archive from a Cocoa application


Are there Objective-C classes that are equivalent to the ones contained in the Java package java.util.zip?
Is to execute a CLI command the only alternative?


Solution

  • As of iOS8/OSX10.10 there is a built-in way to create zip archives using NSFileCoordinatorReadingOptions.ForUploading. A simple example of create zip archives without any non-Cocoa dependencies:

    public extension NSURL {
    
        /// Creates a zip archive of the file/folder represented by this URL and returns a references to the zipped file
        ///
        /// - parameter dest: the destination URL; if nil, the destination will be this URL with ".zip" appended
        func zip(dest: NSURL? = nil) throws -> NSURL {
            let destURL = dest ?? self.URLByAppendingPathExtension("zip")
    
            let fm = NSFileManager.defaultManager()
            var isDir: ObjCBool = false
    
            let srcDir: NSURL
            let srcDirIsTemporary: Bool
            if let path = self.path where self.fileURL && fm.fileExistsAtPath(path, isDirectory: &isDir) && isDir.boolValue == true {
                // this URL is a directory: just zip it in-place
                srcDir = self
                srcDirIsTemporary = false
            } else {
                // otherwise we need to copy the simple file to a temporary directory in order for
                // NSFileCoordinatorReadingOptions.ForUploading to actually zip it up
                srcDir = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(NSUUID().UUIDString)
                try fm.createDirectoryAtURL(srcDir, withIntermediateDirectories: true, attributes: nil)
                let tmpURL = srcDir.URLByAppendingPathComponent(self.lastPathComponent ?? "file")
                try fm.copyItemAtURL(self, toURL: tmpURL)
                srcDirIsTemporary = true
            }
    
            let coord = NSFileCoordinator()
            var error: NSError?
    
            // coordinateReadingItemAtURL is invoked synchronously, but the passed in zippedURL is only valid 
            // for the duration of the block, so it needs to be copied out
            coord.coordinateReadingItemAtURL(srcDir, options: NSFileCoordinatorReadingOptions.ForUploading, error: &error) { (zippedURL: NSURL) -> Void in
                do {
                    try fm.copyItemAtURL(zippedURL, toURL: destURL)
                } catch let err {
                    error = err as NSError
                }
            }
    
            if srcDirIsTemporary { try fm.removeItemAtURL(srcDir) }
            if let error = error { throw error }
            return destURL
        }
    }
    
    public extension NSData {
        /// Creates a zip archive of this data via a temporary file and returns the zipped contents
        func zip() throws -> NSData {
            let tmpURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(NSUUID().UUIDString)
            try self.writeToURL(tmpURL, options: NSDataWritingOptions.DataWritingAtomic)
            let zipURL = try tmpURL.zip()
            let fm = NSFileManager.defaultManager()
            let zippedData = try NSData(contentsOfURL: zipURL, options: NSDataReadingOptions())
            try fm.removeItemAtURL(tmpURL) // clean up
            try fm.removeItemAtURL(zipURL)
            return zippedData
        }
    }