--- title: 'Objective-C 檔案、目錄' disqus: kyleAlien --- Objective-C 檔案、目錄 === ## Overview of Content [TOC] ## NSFileManager 檔案管理 `NSFileManager` 類可以用來管理、查詢、操作檔案(並不是指檔案內容,而是檔案本身) ```objectivec= #import <Foundation/NSFileManager.h> ``` ### 檔案路徑 - NSString * **`NSFileManager#currentDirectoryPath` 方法**:取得當前路徑 ```objectivec= void path_1(void) { NSFileManager *fm = [NSFileManager defaultManager]; // 取得當前路徑的最後 NSString *curPath = [fm currentDirectoryPath]; NSLog(@"Current path: %@", curPath); } ``` > ![](https://hackmd.io/_uploads/rkhJsfps2.png) * **`lastPathComponent` 方法**:取得當前路徑的後一個元素 ```objectivec= void path_2(void) { NSFileManager *fm = [NSFileManager defaultManager]; NSString *curPath = [fm currentDirectoryPath]; // 取得當前路徑的最後元素 NSString *lastComponent = [curPath lastPathComponent]; NSLog(@"lastComponent: %@", lastComponent); } ``` > ![](https://hackmd.io/_uploads/ryUcsf6s2.png) * **`stringByAppendingPathComponent`、`pathExtension` 方法**:組合路徑、取得當前組合路徑的副檔名 ```objectivec= void path_3(void) { NSFileManager *fm = [NSFileManager defaultManager]; NSString *curPath = [fm currentDirectoryPath]; NSLog(@"Current path: %@", curPath); NSString *fullPath = [curPath stringByAppendingPathComponent:@"Hello.123"]; NSLog(@"Full path: %@", fullPath); NSString *extDir = [fullPath pathExtension]; NSLog(@"Ext dir name: %@", extDir); } ``` > ![](https://hackmd.io/_uploads/B1mGpzpo3.png) ### 檔案 - 是否存在、屬性 * **`fileExistsAtPath` 方法**:可以判斷檔案是否存在 ```objectivec= void isFileExist(void) { NSFileManager *fm = [NSFileManager defaultManager]; BOOL isExist = [fm fileExistsAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello.txt"]; NSLog(@"isFileExist: %i\n", isExist); } ``` > ![](https://hackmd.io/_uploads/HkOQgB9jh.png) * **`fileExistsAtPath` 方法**:可以取得檔案是屬性(檔案所屬、ID、權限、類型... 等等) ```objectivec= void fileAttr(void) { NSFileManager *fm = [NSFileManager defaultManager]; NSDictionary *dic = [fm fileAttributesAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello.txt" traverseLink:YES]; NSLog(@"file attr: %@", dic); } ``` > ![](https://hackmd.io/_uploads/ByPalr5in.png) :::warning * 上面範例的方法已經遺棄,可以改用 `attributesOfItemAtPath` 方法 ```objectivec= void fileAttr2(void) { NSFileManager *fm = [NSFileManager defaultManager]; // 創建指標,並非物件 NSError *error; NSDictionary *attr = [fm attributesOfItemAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello.txt" error:&error]; if(attr == nil) { NSLog(@"Error file attr: %@", error); } else { NSLog(@"File attr: %@", attr); } } ``` > ![](https://hackmd.io/_uploads/BJQMzB5in.png) ::: ### 檔案 - 複製、比對、刪除、移動 * **`copyPath` 方法**:用來複製檔案 :::warning 該方法已經被遺棄,建議使用 `NSData` or `NSFileHandle` 類操作 ::: ```objectivec= void cpFile(void) { NSString *targetFile = @"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello.txt"; NSString *cpFile = @"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello_2.txt"; NSFileManager *fm = [NSFileManager defaultManager]; BOOL isSuccess = [fm copyPath:targetFile toPath: cpFile handler:nil]; if(!isSuccess) { NSLog(@"copy file failure."); return; } NSLog(@"copy file success."); } ``` > ![](https://hackmd.io/_uploads/SyZwVHqon.png) * **`contentsEqualAtPath` 方法**:比對檔案的大小、內容,如果都相同則返回 True ```objectivec= void compareFile(void) { // 比較大小、內容 NSFileManager *fm = [NSFileManager defaultManager]; BOOL isSame = [fm contentsEqualAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello.txt" andPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello_2.txt"]; if(!isSame) { NSLog(@"The files not same."); return; } NSLog(@"The files same."); } ``` > ![](https://hackmd.io/_uploads/HJ9h4S5ih.png) * **`removeItemAtPath` 方法**:指定移除位置的檔案 ```objectivec= void deleteFile(void) { NSFileManager *fm = [NSFileManager defaultManager]; // 創建指標,並非物件 NSError *error; BOOL isRemoved = [fm removeItemAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello_2.txt" error:&error]; if(isRemoved) { NSLog(@"File deleted successfully."); } else { NSLog(@"Error deleting file: %@", error); } } ``` > ![](https://hackmd.io/_uploads/r1J_Sr5jh.png) * **`moveItemAtPath` 方法**:移動指定檔案到指定目錄 :::success 在移動的同時還 **可以順便換檔案名** ::: ```objectivec= void moveFile(void) { NSFileManager *fm = [NSFileManager defaultManager]; // 創建指標,並非物件 NSError *error; // 移動還可以順便換檔案名 BOOL isMoved = [fm moveItemAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Test22.txt" toPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/123/Yes123.txt" error:&error]; if(isMoved) { NSLog(@"File move successfully."); } else { NSLog(@"Error move file: %@", error); } } ``` > ![](https://hackmd.io/_uploads/SJurLSqih.png) ### 目錄 - 取得當前目錄、切換目錄 * **`currentDirectoryPath` 方法**:取得當前目錄 ```objectivec= void returnCurrentDirectory(void) { NSFileManager *fm = [NSFileManager defaultManager]; NSString * cPath = [fm currentDirectoryPath]; NSLog(@"Current diectory: %@", cPath); } ``` > ![](https://hackmd.io/_uploads/rJ5sPrqoh.png) * **`changeCurrentDirectoryPath` 方法**:移動到指定目錄 ```objectivec= void cdDirectory(void) { NSFileManager *fm = [NSFileManager defaultManager]; BOOL isSuccess = [fm changeCurrentDirectoryPath:@"/Users/Hy-KylePan/Documents/iOS"]; if(!isSuccess) { NSLog(@"Change diectory failure."); return; } NSString * cPath = [fm currentDirectoryPath]; NSLog(@"Current diectory: %@", cPath); } ``` > ![](https://hackmd.io/_uploads/BJE4dHqs2.png) ### 目錄 - 創建、刪除目錄 * **`createDirectoryAtPath` 方法**:在指定路徑之下創建目錄 ```objectivec= void createDirectory(void) { NSFileManager *fm = [NSFileManager defaultManager]; // 創建指標,並非物件 NSError *error; BOOL isSuccess = [fm createDirectoryAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Yoyo" withIntermediateDirectories:YES attributes:nil error:&error]; if(!isSuccess) { NSLog(@"Create diectory failure. %@", error); return; } NSLog(@"Create diectory success."); } ``` > ![](https://hackmd.io/_uploads/rkD8tr5i3.png) * **`removeItemAtPath` 方法**:移除指定路徑之下的目錄 > 這個方法與移除檔案相同,也就是該方法可以移除檔案、目錄 ```objectivec= NSFileManager *fm = [NSFileManager defaultManager]; // 創建指標,並非物件 NSError *error; BOOL isSuccess = [fm removeItemAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Yoyo" error:&error]; if(!isSuccess) { NSLog(@"Delect diectory failure. %@", error); return; } NSLog(@"Delect diectory success."); } ``` ### 目錄 - 遍歷目錄 * **`enumeratorAtPath` 方法**:該方法可以取得指定目錄下的 **所有檔案**(包括子檔案、目錄),並以 `NSDirectoryEnumerator` 的方式返回 ```objectivec= void recursiveList(void) { NSFileManager *fm = [NSFileManager defaultManager]; // 移動到指定目錄 [fm changeCurrentDirectoryPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest"]; NSDirectoryEnumerator *enumItems = [fm enumeratorAtPath:[fm currentDirectoryPath]]; // 會往內部一直遞迴搜尋 NSString *path; while ((path = [enumItems nextObject]) != nil) { NSLog(@"path: %@", path); } } ``` :::info 可以看到這裡使用遞迴的方式遍歷指定目錄下的所有檔案(用 Tree 方式訪問) ::: > ![](https://hackmd.io/_uploads/BydI5r9o3.png) * 如果只想訪問指定目錄下的檔案資料夾(不包括所有子檔案、資料夾) ```objectivec= void showDirectoryList(void) { NSFileManager *fm = [NSFileManager defaultManager]; NSString *curPath = [fm currentDirectoryPath]; NSLog(@"Current path: %@", curPath); NSArray *array = [fm directoryContentsAtPath:curPath]; // 會往內部一直遞迴搜尋 NSString *path; for(path in array) { NSLog(@"path: %@", path); } } ``` > ![](https://hackmd.io/_uploads/Bk5LiH5s3.png) :::warning * 由於以上方法已遺棄,可以改用 `` 方法 ```objectivec= void showDirectoryList2(void) { NSFileManager *fm = [NSFileManager defaultManager]; NSString *curPath = [fm currentDirectoryPath]; NSLog(@"Current path: %@", curPath); // 創建指標,並非物件 NSError *error; NSArray *array = [fm contentsOfDirectoryAtPath:curPath error:&error]; if(error != nil) { NSLog(@"Get directory failure: %@", error); return; } // 會往內部一直遞迴搜尋 NSString *path; for(path in array) { NSLog(@"path: %@", path); } } ``` > ![](https://hackmd.io/_uploads/ryYmnS9sn.png) ::: ## NSData 檔案內容 每個檔案中放置的內容 (或是要放置進檔案的內容) 在 Objective-C 中是使用 `NSData` 來表示 ```objectivec= #import <Foundation/NSData.h> ``` ### 讀取檔案資料 * 範例: 1. 透過 `NSData#contentsAtPath` 讀取檔案的資料為 `NSData` 2. 再透過 `NSString#initWithData` 讀取 NSData 的檔案並顯示 ```objectivec= void readFile(void) { NSFileManager *fm = [NSFileManager defaultManager]; NSData *data = [fm contentsAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello.txt"]; if(data == nil) { NSLog(@"Error read file"); } else { NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"Read file: %@", content); } } ``` > ![](https://hackmd.io/_uploads/rymE_XTjn.png) ### 將資料寫入檔案 * 範例: 1. 透過 `NSString#stringWithContentsOfFile` 建立資料(`NSData`) 2. 再透過 `NSFileManager#createFileAtPath` 創建檔案並寫入 NSData 數據到檔案中 ```objectivec= void writeFile(void) { NSFileManager *fm = [NSFileManager defaultManager]; // 透過 NSString,創建 NSData NSString *myString = @"Hello, World!"; NSData *data = [myString dataUsingEncoding:NSUTF8StringEncoding]; if(data == nil) { NSLog(@"Error open file"); } else { BOOL isSuccess = [fm createFileAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/World.txt" contents:data attributes:nil]; if(!isSuccess) { NSLog(@"Write file error"); return; } NSString *content = [NSString stringWithContentsOfFile:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/World.txt" encoding:NSUTF8StringEncoding error:nil]; NSLog(@"Read, write file: %@", content); } } ``` > ![](https://hackmd.io/_uploads/B1GZF7ain.png) ## NSFileHandle 檔案內容控制 `NSData` 可以讀取、寫入資料,但並不能控制檔案內容;如果要控制檔案內容則必須使用 `NSFileHandle` (可以做到更細緻的操作) ```objectivec= #import <Foundation/NSFileHandle.h> ``` ### 操作讀取檔案 - fileHandleForReadingAtPath * 使用 `NSFileHandle#fileHandleForReadingAtPath` 可以用來控制指定檔案的內容 ```objectivec= NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:<路徑>]; ``` * **`readDataToEndOfFile` 方法**:使用該方法可以讀取全部檔案內容 ```objectivec= void handleReadFile(void) { NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello.txt"]; if(handle == nil) { NSLog(@"handle read file failure."); } else { // 取讀取全部內容 NSData *data = [handle readDataToEndOfFile]; NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"handle read file success: %@", content); } } ``` > ![](https://hackmd.io/_uploads/BJz6im6i3.png) * **`readDataOfLength` 方法**:使用該方法可以讀取指定檔案的指定 Byte 數 ```objectivec= void handleReadFile2(void) { NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:@"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/Hello.txt"]; if(handle == nil) { NSLog(@"handle read file failure."); } else { // 取讀取指定 byte 數量 NSData *data = [handle readDataOfLength:3]; NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"handle read file success: %@", content); } } ``` > ![](https://hackmd.io/_uploads/SyTn3Qai3.png) ### 操作寫入檔案 - fileHandleForWritingAtPath * 使用 `NSFileHandle#fileHandleForWritingAtPath` 可以用來控制指定檔案的內容 ```objectivec= NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:<路徑>]; ``` **`writeData` 方法**:透過該方法可以對檔案寫入資料 ```objectivec= void handleWriteFile(void) { NSString *filePath = @"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/World.txt"; NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:filePath]; if (handle == nil) { NSLog(@"Handle read file failure."); } else { // 創建帶有文字內容的 NSData 物件 NSString *content = @"Hello, this is the content to write to the file."; NSData *data = [content dataUsingEncoding:NSUTF8StringEncoding]; [handle writeData:data]; // 用於確認寫入的內容 NSString *readContent = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; NSLog(@"Update file: %@", readContent); } } ``` > ![](https://hackmd.io/_uploads/HymY0Qain.png) <!-- ### 操作更新檔案 --> ### 檔案內容偏移量控制 * **`offsetInFile` 方法**:該方法可以讀取當前檔案的偏移量(也就是你讀取到哪裡) ```objectivec= void fileOffect(void) { NSString *filePath = @"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/World.txt"; NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:filePath]; // 開始讀取前的偏移量 unsigned long long offect = [handle offsetInFile]; NSLog(@"Before read, file offect: %lli", offect); // 讀到結尾 [handle readDataToEndOfFile]; // 開始讀取後的偏移量 offect = [handle offsetInFile]; NSLog(@"After read, file offect: %lli", offect); } ``` > ![](https://hackmd.io/_uploads/rJDbWVpsn.png) * **`seekToFileOffset` 方法**:該方法可以直接控制偏移量 ```objectivec= void fileOffestSeek(void) { NSString *filePath = @"/Users/Hy-KylePan/Documents/iOS/fileTest/fileTest/World.txt"; NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:filePath]; // 控制偏移量,位移 20 byte [handle seekToFileOffset:20]; // 從 20 byte 之後開始讀取,讀至結尾 NSData *data = [handle readDataToEndOfFile]; NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"After seek 20, content: %@", content); [handle closeFile]; } ``` > ![](https://hackmd.io/_uploads/B1roWN6sn.png) ## 其他 ### 暫存資料夾 * 使用 `NSFileManager#temporaryDirectory` 方法,就可以取得暫存的資料夾空間 ```objectivec= void showTempDirectory(void) { NSFileManager *fm = [NSFileManager defaultManager]; NSURL *url = [fm temporaryDirectory]; NSLog(@"temp directory: %@", url); } ``` > ![](https://hackmd.io/_uploads/rJy0f46jn.png) ## Appendix & FAQ :::info ::: ###### tags: `iOS`