iOS常用的持久化存儲方式
來自: http://www.henishuo.com/ios-persistent-storage/
前言
iOS中常用的持久化存儲方式有好幾種:
- 偏好設置(NSUserDefaults)
- plist文件存儲
- 歸檔
- SQLite3
- Core Data
這里不細講數據庫,只針對性地講講文件存儲、歸檔/解檔、偏好設置等。
在此之前,我們需要先講講沙盒(Sandbox)才能繼續講解。
沙盒
每個iOS應用都有自己的應用沙盒(應用沙盒就是文件系統目錄),與其他文件系統隔離。應用必須待在自己的沙盒里,其他應用不能訪問該沙盒。沙盒下的目錄如下:
- Application:存放程序源文件,上架前經過數字簽名,上架后不可修改
- Documents: 保存應?運行時生成的需要持久化的數據,iTunes同步設備時會備份該目錄。例如,游戲應用可將游戲存檔保存在該目錄
- tmp: 保存應?運行時所需的臨時數據,使?完畢后再將相應的文件從該目錄刪除。應用 沒有運行時,系統也可能會清除該目錄下的文件。iTunes同步設備時不會備份該目錄。
- Library/Caches: 保存應用運行時?成的需要持久化的數據,iTunes同步設備時不會備份 該目錄。?一般存儲體積大、不需要備份的非重要數據,比如網絡數據緩存存儲到Caches下
- Library/Preference: 保存應用的所有偏好設置,如iOS的Settings(設置) 應?會在該目錄中查找應?的設置信息。iTunes同步設備時會備份該目錄
NSUserDefaults
NSUserDefaults是個單例類,用于存儲少量數據。NSUserDefaults實際上對plist文件操作的封裝,更方便我們直接操作,一般用于存儲系統級別的偏好設置。比如我們經常將登錄后的用戶的一些設置通過NSUserDefaults存儲到plist文件中。
有很多App,他們也是將用戶的賬號和密碼存儲在偏好設置中。我們不講安全性問題,因此不討論存儲在偏好設置下是否安全。
使用起來非常簡單,如下:
// 寫入文件 - (void)saveUserName:(NSString *)userNamepassword:(NSString *)password { [[NSUserDefaults standardUserDefaults]setObject:userNameforKey:@"username"]; [[NSUserDefaults standardUserDefaults]setObject:passwordforKey:@"password"]; [[NSUserDefaults standardUserDefaults]synchronize]; } // 在用的時候,就可以讀取出來使用 NSString *userName = [[NSUserDefaults standardUserDefaults]objectForKey:@"username"]; NSString *password = [[NSUserDefaults standardUserDefaults]objectForKey:@"password"];
存儲到偏好設置的只有系統已經提供好的類型,比如基本類型、NSNumber、NSDictionary、NSArray等。對于NSObject及繼承于NSObject的類型,是不支持的。如下:
NSObject *obj = [[NSObject alloc]init]; [[NSUserDefaults standardUserDefaults]setObject:objforKey:@"obj"]; // 就會崩潰 Terminating appduetouncaughtexception 'NSInvalidArgumentException', reason: 'Attempt to insert non-property list object <NSObject: 0x7fb502680cb0> for key obj'
plist存儲
有的時候,我們需要將下載的數據存儲到文件中存儲起來,比如,有時候我們將下載起來的城市的數據存儲到本地,當更新城市的順序時,下次也能夠按照最后一次操作的順序來顯示出來。
// 數據存儲,是保存到手機里面, // Plist存儲,就是把某些數據寫到plist文件中 // plist存儲一般用來存儲數組和字典 // Plist存儲是蘋果特有,只有蘋果才能生成plist // plist不能存儲自定義對象,如NSObject、model等 NSDictionary *dict = @{@"age":@"18",@"name":@"USER"}; // 保存應用沙盒(app安裝到手機上的文件夾) // Caches文件夾 // 在某個范圍內容搜索文件夾的路徑 // directory:獲取哪個文件夾 // domainMask:在哪個范圍下獲取 NSUserDomainMask:在用戶的范圍內搜索 // expandTilde是否展開全路徑,YES:展開 NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0]; NSLog(@"%@",cachePath); // 拼接文件路徑 NSString *filePath = [cachePathstringByAppendingPathComponent:@"data.plist"]; // 獲取應用沙盒 NSString *homePath = NSHomeDirectory(); NSLog(@"%@",homePath); // File:文件全路徑 => 所有文件夾路徑 + 文件路徑 [dictwriteToFile:filePathatomically:YES]; // 將數據取出來 NSLog(@"%@", [NSDictionarydictionaryWithContentsOfFile:filePath]);
我們看看打印的結果:
2016-02-1722:14:43.055 iOSPersistentStorageDemo[25471:809758] /Users/huangyibiao/Library/Developer/CoreSimulator/Devices/CF3A5A4C-486F-4A72-957B-2AD94BD90EC1/data/Containers/Data/Application/65E8F814-45E5-420C-A174-822A7830748E/Library/Caches 2016-02-1722:14:43.055 iOSPersistentStorageDemo[25471:809758] /Users/huangyibiao/Library/Developer/CoreSimulator/Devices/CF3A5A4C-486F-4A72-957B-2AD94BD90EC1/data/Containers/Data/Application/65E8F814-45E5-420C-A174-822A7830748E 2016-02-1722:14:43.056 iOSPersistentStorageDemo[25471:809758] { age = 18; name = USER; }
注意:操作plist文件時,文件路徑一定要是全路徑。
歸檔(NSKeyedArchiver)
自定義對象應用范圍很廣,因為它對應著MVC中的Model層,即實體類。在程序中,我們會在Model層定義很多的entity,例如User、Teacher、Person等。
那么對自定義對象的歸檔顯得重要的多,因為很多情況下我們需要在Home鍵之后保存數據,在程序恢復時重新加載,那么,歸檔便是一個好的選擇。
下面我們自定義一個Person類:
// 要使對象可以歸檔,必須遵守NSCoding協議 @interfacePerson: NSObject<NSCoding> @property (nonatomic, assign) int age; @property (nonatomic, strong) NSString *name; @end @implementation Person // 什么時候調用:只要一個自定義對象歸檔的時候就會調用 - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoderencodeObject:self.nameforKey:@"name"]; [aCoderencodeInt:self.ageforKey:@"age"]; } - (id)initWithCoder:(NSCoder *)aDecoder { if (self = [super init]) { self.name = [aDecoderdecodeObjectForKey:@"name"]; self.age = [aDecoderdecodeIntForKey:@"age"]; } return self; } @end
如何將自定義對象歸檔和解檔:
- (void)savePerson { // 歸檔:plist存儲不能存儲自定義對象,此時可以使用歸檔來完成 Person *person = [[Person alloc]init]; person.age = 18; person.name = @"USER"; // 獲取tmp目錄路徑 NSString *tempPath = NSTemporaryDirectory(); // 拼接文件名 NSString *filePath = [tempPathstringByAppendingPathComponent:@"person.data"]; // 歸檔 [NSKeyedArchiverarchiveRootObject:persontoFile:filePath]; } - (void)readPerson { // 獲取tmp NSString *tempPath = NSTemporaryDirectory(); // 拼接文件名 NSString *filePath = [tempPathstringByAppendingPathComponent:@"person.data"]; // 解檔 Person *p = [NSKeyedUnarchiverunarchiveObjectWithFile:filePath]; NSLog(@"%@ %d",p.name,p.age); }
假設我們定義了一個自定義的view,這個view是用xib或者storybard來生成的,那么我們我一定如下方法時,就需要如下實現:
@implementation CustomView // 解析xib,storyboard文件時會調用 - (id)initWithCoder:(NSCoder *)aDecoder { // 什么時候調用[super initWithCoder:aDecoder]? // 只要父類遵守了NSCoding協議,就調用[super initWithCoder:aDecoder] if (self = [superinitWithCoder:aDecoder]) { NSLog(@"%s",__func__); } return self; } @end
如果想要學習如何通過runtime來實現自動歸檔、解檔,請閱讀文章: 通過runtime自動歸檔/解檔
最后
關于數據庫,這里不講解了,因為數據庫是一個很大方面的知識,不是三言兩語所能講通的。
關注我
關注 | 賬號 | 備注 |
---|---|---|
Swift/ObjC技術群一 | 324400294 | 群一若已滿,請申請群二 |
Swift/ObjC技術群二 | 494669518 | 群二若已滿,請申請群三 |
Swift/ObjC技術群三 | 461252383 | 群三若已滿,會有提示信息 |
關注微信公眾號 | iOSDevShares | 關注微信公眾號,會定期地推送好文章 |
關注新浪微博賬號 | 標哥Jacky | 關注微博,每次發布文章都會分享到新浪微博 |
關注標哥的GitHub | CoderJackyHuang | 這里有很多的Demo和開源組件 |
關于我 | 進一步了解標哥 | 如果覺得文章對您很有幫助,可捐助我! |
版權聲明:本文為【標哥的技術博客】原創出品,歡迎轉載,轉載時請注明出處!
</div>