2014年2月13日

在iOS應用程式中存取Zip檔案

在這資訊爆炸的時代中,無論是儲存檔案還是傳輸檔案,空間、流量總是相當地有限。適時地使用Zip檔案來縮減檔案大小,儼然成為應用程式開發者不可不知的技術。

以下介紹幾個比較簡單的壓縮、解壓縮方式。

使用SSZipArchive

SSZipArchive是一個第三方套件,提供了極為簡潔的壓縮、解壓縮API。 只要至SSZipArchive的github下載SSZipArchive,將SSZipArchive以及minizip兩個檔案夾加入專案中,並在專案中引入libz即可。

壓縮:

NSString *zippedFilePath = @"欲建立的zip檔檔案的絕對路徑";

//欲加入zip檔的檔案列表
NSArray *sourceFiles = [NSArray arrayWithObjects:
                       [[NSBundle mainBundle] pathForResource:@"檔案1" ofType:@"副檔名"],
                       [[NSBundle mainBundle] pathForResource:@"檔案2" ofType:@"副檔名"]
                       nil];


[SSZipArchive createZipFileAtPath:zippedPath withFilesAtPaths:sourceFiles];

解壓縮:

NSString *zipFilePath = @"zip檔案的絕對路徑";
NSString *destDirPath = @"解壓縮目的地資料夾的絕對路徑";
[SSZipArchive zipFilePath destDirPath];

如何?是不是一目了然呢?

然而,有些時候,我們並不想解壓縮整個zip檔,而是希望能夠只解壓縮其中一部份的檔案。

這時候就要使用另外一個第三方套件 - Objective-Zip

使用Objective-Zip

Objective-Zip提供了較為完整的壓縮、解壓縮API,讓應用程式開發者能夠存取zip檔案中各個檔案的資訊。 只要至Objective-Zip的github下載Objective-Zip,將ARCHelperZLibMiniZipObjective-Zip四個檔案夾加入專案中即可。

(補充:由於SSZipArchive與Objective-Zip皆使用到了minizip,若同時將SSZipArchive與Objective-Zip所需的資料夾都放入同專案中編譯,需避免因重複的檔案導致編譯失敗)

壓縮:

首先,建立一個ZipFile物件。mode後的參數使用ZipFileModeCreate,表示此ZipFile物件是用來建立壓縮檔。

ZipFile *zipFile= [[ZipFile alloc] initWithFileName:@"zip檔案檔名"
mode:ZipFileModeCreate];

接著,針對每一個需要加入zip檔中的檔案,建立一個ZipWriteStream物件。

其中接在writeFileInZipWithName後的參數,表示此檔案在zip檔中的路徑。 舉例來說,如:folder1/file1.txt,則表示此ZipWriteStream物件會將檔案寫入至zip檔中的folder1資料夾內,檔名為file1.txt的檔案。

ZipWriteStream *stream= [zipFile writeFileInZipWithName:@"folder1/file1.txt"
    compressionLevel:ZipCompressionLevelBest];

然後,再透過此ZipWriteStream物件的writeData方法,將欲寫入的檔案內容,以NSData形態傳入。 每寫完一個檔案後,皆需呼叫finishedWriting方法以完成寫入。

NSString* filepath = @"欲加入的來源檔案的絕對路徑";
NSData* filedata = [[NSFileManager defaultManager] contentsAtPath:filepath]
[stream writeData:filedata];
[stream finishedWriting];

當所有需要加入zip檔中的檔案皆已完成寫入時, 請務必呼叫ZipFile物件的close方法,完成建立zip檔的動作。

[zipFile close];

解壓縮:

建立一個ZipFile物件。mode後的參數使用ZipFileModeUnzip,表示此ZipFile物件是用來解壓縮檔用。 ZipFile *zipFile= [[ZipFile alloc] initWithFileName:@"zip檔案檔名" mode:ZipFileModeUnzip];

在Objective-Zip中,使用了FileInZipInfo物件,記錄zip檔中的某一個檔案的資訊。

而ZipFile物件的listFileInZipInfos方法,可以取得zip檔案中的所有FileInZipInfo:

NSArray *infos= [unzipFile listFileInZipInfos];
for (FileInZipInfo *info in infos) {
    //透過FileInZipInfo分別處理zip檔中的每一個檔案...
}

FileInZipInfo物件提供了以下幾個property存取示此檔案的相關資訊:

1. name: 此檔案在zip檔案中的路徑。
    如:folder1/file1.txt,則表示此檔案在zip檔案中的folder1資料夾內,檔名為file1.txt。

2. size: 此檔案壓縮過後的大小。

3. length: 此檔案未壓縮的大小。

透過FileInZipInfo解壓縮該檔案時,需針對該檔案建立一個ZipReadStream物件。

[zipFile locateFileInZip:info.name]; //info變數是上方例子for迴圈中所取得的FileInZipInfo物件
ZipReadStream *read= [unzipFile readCurrentFileInZip];

接著,將該檔案內容以ZipReadStream物件的readDataWithBuffer方法,讀入至NSMutableData物件。再使用NSData的writeToFile方法寫入至檔案系統中即可。

NSMutableData *data= [[NSMutableData alloc] initWithLength:info.length];
int bytesRead= [read readDataWithBuffer:data];
[read finishedReading];
[data writeToFile:@"此檔案解壓縮後的絕對路徑" atomically:NO];

完成所有解壓縮的處理後,請務必呼叫ZipFile物件的close

[zipFile close];

結語

使用zip檔案節省空間是很常見的。即使自己開發的應用程式本身不使用到zip,也可能需要能處理接受自別人的zip檔案。在iOS應用程式中存取Zip檔案並不難,也應是應用程式開發者需好好謹記在心的技術之一。