沙盒詳解
1、IOS沙盒機制
IOS應用程式隻能在為該改程式建立的檔案系統中讀取檔案,不可以去其它地方通路,此區域被成為沙盒,是以所有的非代碼檔案都要儲存在此,例如圖像,圖示,聲音,映像,屬性清單,文本檔案等。
1.1、每個應用程式都有自己的存儲空間
1.2、應用程式不能翻過自己的圍牆去通路别的存儲空間的内容
1.3、應用程式請求的資料都要通過權限檢測,假如不符合條件的話,不會被放行。
通過這張圖隻能從表層上了解sandbox是一種安全體系,應用程式的所有操作都要通過這個體系來執行,其中核心内容是:sandbox對應用程式執行各種操作的權限限制。
2、打開模拟器沙盒目錄
下面看看模拟器的沙盒檔案夾在mac電腦上的什麼位置。
檔案都在個人使用者名檔案夾下的一個隐藏檔案夾裡,中文叫資源庫,他的目錄其實是Library。
2.1 方法1、可以設定顯示隐藏檔案,然後在Finder下直接打開。設定檢視隐藏檔案的方法如下:打開終端,輸入命名
顯示Mac隐藏檔案的指令:defaults write com.apple.finder AppleShowAllFiles -bool true
隐藏Mac隐藏檔案的指令:defaults write com.apple.finder AppleShowAllFiles -bool false
輸完單擊Enter鍵,退出終端,
重新啟動Finder就可以了重新開機Finder:滑鼠單擊視窗左上角的蘋果标志-->強制退出-->Finder-->
現在能看到資源庫檔案夾了。
打開資源庫後找到/Application Support/iPhone Simulator/檔案夾。這裡面就是模拟器的各個程式的沙盒目錄了。2.2 方法2、這種方法更友善,在Finder上點->前往->前往檔案夾,輸入/Users/username/Library/Application Support/iPhone Simulator/ 前往。
username這裡寫你的使用者名。
3、目錄結構
預設情況下,每個沙盒含有3個檔案夾:Documents, Library 和 tmp。因為應用的沙盒機制,應用隻能在幾個目錄下讀寫檔案
Documents:蘋果建議将程式中建立的或在程式中浏覽到的檔案資料儲存在該目錄下,iTunes備份和恢複的時候會包括此目錄
Library:存儲程式的預設設定或其它狀态資訊;
Library/Caches:存放緩存檔案,iTunes不會備份此目錄,此目錄下檔案不會在應用退出删除
tmp:提供一個即時建立臨時檔案的地方。
iTunes在與iPhone同步時,備份所有的Documents和Library檔案。
iPhone在重新開機時,會丢棄所有的tmp檔案。
我們建立一個IosSandbox的項目來展開沙盒和檔案讀寫等操作的練習。
建立後找到模拟器上對應的目錄,
這是目錄全展開了。
這是上面提到的三個目錄 :Documents、Library、 tmp
下篇介紹目錄路徑擷取和檔案操作
我們看看如何擷取應用程式沙盒目錄。包括真機的沙盒的目錄。
1、擷取程式的Home目錄
|
列印結果:
|
那在真機上的目錄有是怎麼樣的呢?我們看看
2012-06-17 14:25:47.059 IosSandbox[4281:f803] /var/mobile/Applications/3B8EC78A-5EEE-4C2F-B0CB-4C3F02B996D2
可見,真機上的目錄是/var/mobile/Applications/這個目錄下的,和模拟器不一樣。這個是Home目錄,其他的子目錄和模拟器一樣。
2、擷取document目錄
|
列印結果
|
3、擷取Cache目錄
|
|
4、擷取Library目錄
|
|
5、擷取Tmp目錄
|
|
6、寫入檔案
|
注:我們在真機上也運作一下,把檔案寫入,下一步從真機上把内容讀取出來。
寫入輸入 array ,裡面是兩個字元串,一會我們讀出來列印。
寫入我們在程式沙盒目錄下看到檔案 testFile.txt
打開檔案看到的内容是這樣的,是個xml格式的plist檔案,資料格式儲存了内容。
|
7、讀取檔案
|
列印結果:
把上面的檔案解析後,把内容列印出來了。
|
真機上讀取并列印檔案路徑:
2012-06-17 14:25:47.059 IosSandbox[4281:f803] /var/mobile/Applications/3B8EC78A-5EEE-4C2F-B0CB-4C3F02B996D2/Documents/testFile.txt
(
"\U5185\U5bb9",
content
)
真機上也能寫入和列印。
我們看看NSFileManager如何使用。包括建立檔案,目錄,删除,周遊目錄等。
1、在Documents裡建立目錄
建立一個叫test的目錄,先找到Documents的目錄,
|
啟動程式,這時候目錄就建立了:
2、在test目錄下建立檔案
建立檔案怎麼辦呢?接着上面的代碼 testPath 要用stringByAppendingPathComponent拼接上你要生成的檔案名,比如test00.txt。這樣才能在test下寫入檔案。
testDirectory是上面代碼生成的路徑哦,不要忘了。我往test檔案夾裡寫入三個檔案,test00.txt ,test22.txt,text.33.txt。内容都是寫入内容,write String。
實作代碼如下:
|
看下面的圖,三個檔案都出來了,内容也對。
在Documents目錄下建立就更簡單了,不用加test就ok了
3、擷取目錄列裡所有檔案名
兩種方法擷取:subpathsOfDirectoryAtPath 和 subpathsAtPath
|
擷取上面剛才test檔案夾裡的檔案名
|
兩個方法都可以,隐藏的檔案也列印出來了。
4、fileManager使用操作目前目錄
|
這樣就建立了testFileNSFileManager.txt并把三個hello world寫入檔案了
changeCurrentDirectoryPath目錄更改到目前操作目錄時,做檔案讀寫就很友善了,不用加上全路徑
5、删除檔案
接上面的代碼,remove就ok了。
[fileManager removeItemAtPath:fileName error:nil]; |
6、混合資料的讀寫
用NSMutableData建立混合資料,然後寫到檔案裡。并按資料的類型把資料讀出來
6.1寫入資料:
|
我們看看資料怎麼樣了:
我們看到後面的是亂碼,那是中文被轉成了NSData後,還有int float的二進制
6.2讀取剛才寫入的資料:
|
列印出來的結果:
2012-06-17 23:51:14.723 IosSandbox[1285:f803] stringData:nihao hello! intData:1234332 floatData:3.140000
這裡把寫入的漢字改成了 hello。因為[temp length]算長度是,把中文算成一位了,出來的結果有誤。
屬性清單
屬性清單檔案是一種XML檔案,Foundation架構中的數組和字典等都可以于屬性清單檔案互相轉換。
NSArray類常用讀寫屬性清單檔案的方法:
+arrayWithContentsOfFile:類級構造方法,用于從屬性清單檔案中讀取資料,建立NSArray對象。
-initWithContentsOfFile:執行個體構造方法,用于從屬性清單檔案中讀取資料,建立NSArray對象。
-writeToFile:atomically:該方法把NSArray對象寫入到屬性清單檔案中,第一個參數是檔案名,第二個參數為是否使用輔助檔案,如果為YES,則先寫入到一個輔助檔案,然後輔助檔案再重新命名為目标檔案,如果為NO,則直接寫入到目标檔案。
NSDictionary類常用讀寫屬性清單檔案的方法:
+dictionaryWithContentsOfFile:類級構造方法,用于從屬性清單檔案中讀取資料,建立NSDictionary對象。
-initWithContentsOfFile:執行個體構造方法,用于從屬性清單檔案中讀取資料,建立NSDictionary對象。
-writeToFile:atomically:該方法将NSDictionary對象寫入到屬性清單檔案中。
屬性清單檔案資料持久化具體方法,可參考以下實作方式:
假如在項目中手工建立了一個Contacts.plist檔案,并在該檔案中添加了幾條資料,如下圖所示。
當然也可以通過代碼直接建立plist檔案。
接下來需要做的是将項目資源的Contacts.plist檔案中資料複制到沙箱Documents目錄下。
//對檔案進行預處理,判斷在Documents目錄下是否存在plist檔案,如果不存在則從資源目錄下複制一個。
-(void)createEditableCopyOfDatabaseIfNeeded
{
NSFileManager *fileManager=[NSFileManager defaultManager];
NSString *writableDBPath=[self applicationDocumentsDirectoryFile];
BOOL dbexits=[fileManager fileExistsAtPath:writableDBPath];
if (!dbexits) {
NSString *defaultDBPath=[[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Contacts.plist"];
NSError *error;
BOOL success=[fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
if (!success) {
NSAssert1(0,@"錯誤寫入檔案:‘%@’",[error localizedDescription]);
}
}
}
//擷取放置在沙箱Documents目錄下的檔案的完整路徑
-(NSString *)applicationDocumentsDirectoryFile
{
NSString *documentDirectory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *path=[documentDirectory stringByAppendingPathComponent:@"Contacts.plist"];
return path;
}
createEditableCopyOfDatabaseIfNeeded方法中:
NSFileManager的copyItemAtPath:toPath:error:方法實作檔案複制。
NSAssert1是Foundation架構提供的宏,它在斷言失敗的情況下抛出異常,類似的還有NSAssert和NSAssert2等。
applicationDocumentsDirectoryFile方法中:
stringByAppendingPathComponent:能夠在目錄後面追加檔案名,傳回完整的檔案路徑。
沙箱Documents目錄下成功生成plist檔案之後,就可以進行增、删、改、查操作了。可參考如下代碼:
NSString *path=[self applicationDocumentsDirectoryFile];
//将屬性清單檔案内容讀取到array變量中,也就是擷取了屬性清單檔案中全部的資料集合
NSMutableArray *array=[[NSMutableArray alloc]initWithContentsOfFile:path];
//向array中添加一條新記錄
NSDictionary *newContact=[NSDictionary dictionaryWithObjects:@[contact.Title,contact.Type] forKeys:@[@"Title",@"Type"]];
[array addObject:newContact];
//删除array中一條記錄
[array removeObjectAtIndex:0];
//删除array中全部記錄
[array removeAllObjects];
for (NSDictionary* dict in array) {
//通過for循環,找到需要修改的資料項,進行修改資料
[dict setValue:@"Test" forKey:@"Title"];
}
//将array重新寫入屬性清單檔案中
[array writeToFile:path atomically:YES];
注:完成後,需要選擇Product->Clean菜單項清除一些再編譯。
歸檔
iOS開發UI篇—ios應用資料存儲方式(歸檔)
一、簡單說明
在使用plist進行資料存儲和讀取,隻适用于系統自帶的一些常用類型才能用,且必須先擷取路徑相對麻煩;
偏好設定(将所有的東西都儲存在同一個檔案夾下面,且主要用于存儲應用的設定資訊)
歸檔:因為前兩者都有一個緻命的缺陷,隻能存儲常用的類型。歸檔可以實作把自定義的對象存放在檔案中。
二、代碼示例
1.檔案結構
2.代碼示例
YYViewController.m檔案
1 //
2 // YYViewController.m
3 // 02-歸檔
4 //
5 // Created by apple on 14-6-7.
6 // Copyright (c) 2015年 itcase. All rights reserved.
7 //
8
9 #import "YYViewController.h"
10 #import "YYPerson.h"
11
12 @interface YYViewController ()
13 - (IBAction)saveBtnOnclick:(id)sender;
14 - (IBAction)readBtnOnclick:(id)sender;
15
16 @end
17
18 @implementation YYViewController
19
20 - (void)viewDidLoad
21 {
22 [super viewDidLoad];
23 }
24
25
26 - (IBAction)saveBtnOnclick:(id)sender {
27 //1.建立對象
28 YYPerson *p=[[YYPerson alloc]init];
29 p.name=@"iCocos";
30 p.age=23;
31 p.height=1.7;
32
33 //2.擷取檔案路徑
34 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
35 NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"];
36 NSLog(@"path=%@",path);
37
38 //3.将自定義的對象儲存到檔案中
39 [NSKeyedArchiver archiveRootObject:p toFile:path];
40
41 }
42
43 - (IBAction)readBtnOnclick:(id)sender {
44 //1.擷取檔案路徑
45 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
46 NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"];
47 NSLog(@"path=%@",path);
48
49 //2.從檔案中讀取對象
50 YYPerson *p=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
51 NSLog(@"%@,%d,%.1f",p.name,p.age,p.height);
52 }
53 @end
建立一個person類
YYPerson.h檔案
1 //
2 // YYPerson.h
3 // 02-歸檔
4 //
5 // Created by apple on 14-6-7.
6 // Copyright (c) 2014年 itcase. All rights reserved.
7 //
8
9 #import <Foundation/Foundation.h>
10
11 // 如果想将一個自定義對象儲存到檔案中必須實作NSCoding協定
12 @interface YYPerson : NSObject<NSCoding>
13
14 //姓名
15 @property(nonatomic,copy)NSString *name;
16 //年齡
17 @property(nonatomic,assign)int age;
18 //身高
19 @property(nonatomic,assign)double height;
20 @end
YYPerson.m檔案
1 //
2 // YYPerson.m
3 // 02-歸檔
4 //
5 // Created by apple on 14-6-7.
6 // Copyright (c) 2014年 itcase. All rights reserved.
7 //
8
9 #import "YYPerson.h"
10
11 @implementation YYPerson
12
13 // 當将一個自定義對象儲存到檔案的時候就會調用該方法
14 // 在該方法中說明如何存儲自定義對象的屬性
15 // 也就說在該方法中說清楚存儲自定義對象的哪些屬性
16 -(void)encodeWithCoder:(NSCoder *)aCoder
17 {
18 NSLog(@"調用了encodeWithCoder:方法");
19 [aCoder encodeObject:self.name forKey:@"name"];
20 [aCoder encodeInteger:self.age forKey:@"age"];
21 [aCoder encodeDouble:self.height forKey:@"height"];
22 }
23
24 // 當從檔案中讀取一個對象的時候就會調用該方法
25 // 在該方法中說明如何讀取儲存在檔案中的對象
26 // 也就是說在該方法中說清楚怎麼讀取檔案中的對象
27 -(id)initWithCoder:(NSCoder *)aDecoder
28 {
29 NSLog(@"調用了initWithCoder:方法");
30 //注意:在構造方法中需要先初始化父類的方法
31 if (self=[super init]) {
32 self.name=[aDecoder decodeObjectForKey:@"name"];
33 self.age=[aDecoder decodeIntegerForKey:@"age"];
34 self.height=[aDecoder decodeDoubleForKey:@"height"];
35 }
36 return self;
37 }
38 @end
3.列印效果和兩個重要的錯誤提示
點選儲存按鈕和讀取按鈕,成功列印結果如下:
關于不實作兩個協定方法的錯誤提示:
-(void)encodeWithCoder:(NSCoder *)aCoder方法:
-(id)initWithCoder:(NSCoder *)aDecoder方法:
三、繼承類中的使用
建立一個學生類,讓這個類繼承自Preson這個類,增加一個體重的屬性。
YYstudent.h檔案
1 //
2 // YYstudent.h
3 // 02-歸檔
4 //
5 // Created by apple on 14-6-7.
6 // Copyright (c) 2014年 itcase. All rights reserved.
7 //
8
9 #import "YYPerson.h"
10
11 @interface YYstudent : YYPerson
12 //增加一個體重屬性
13 @property(nonatomic,assign) double weight;
14 @end
YYstudent.m檔案
1 //
2 // YYstudent.m
3 // 02-歸檔
4 //
5 // Created by apple on 14-6-7.
6 // Copyright (c) 2014年 itcase. All rights reserved.
7 //
8
9 #import "YYstudent.h"
10
11 @implementation YYstudent
12
13 //在子類中重寫這兩個方法
14 - (void)encodeWithCoder:(NSCoder *)aCoder
15 {
16 [super encodeWithCoder:aCoder];
17 NSLog(@"調用了YYStudent encodeWithCoder");
18 [aCoder encodeFloat:self.weight forKey:@"weight"];
19 }
20
21 - (id)initWithCoder:(NSCoder *)aDecoder
22 {
23 if (self = [super initWithCoder:aDecoder]) {
24 NSLog(@"調用了YYstudent initWithCoder");
25 self.weight = [aDecoder decodeFloatForKey:@"weight"];
26 }
27 return self;
28 }
29 @end
YYViewController.m檔案
1 //
2 // YYViewController.m
3 // 02-歸檔
4 //
5 // Created by apple on 14-6-7.
6 // Copyright (c) 2014年 itcase. All rights reserved.
7 //
8
9 #import "YYViewController.h"
10 #import "YYPerson.h"
11 #import "YYstudent.h"
12
13 @interface YYViewController ()
14 - (IBAction)saveBtnOnclick:(id)sender;
15 - (IBAction)readBtnOnclick:(id)sender;
16
17 @end
18
19 @implementation YYViewController
20
21 - (void)viewDidLoad
22 {
23 [super viewDidLoad];
24 }
25
26
27 - (IBAction)saveBtnOnclick:(id)sender {
28 //1.建立對象
29 // YYPerson *p=[[YYPerson alloc]init];
30 // p.name=@"文頂頂";
31 // p.age=23;
32 // p.height=1.7;
33
34 YYstudent *s=[[YYstudent alloc]init];
35 s.name=@"wendingding";
36 s.age=23;
37 s.height=1.7;
38 s.weight=62;
39 //2.擷取檔案路徑
40 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
41 NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"];
42 NSLog(@"path=%@",path);
43
44 //3.将自定義的對象儲存到檔案中
45 // [NSKeyedArchiver archiveRootObject:p toFile:path];
46 [NSKeyedArchiver archiveRootObject:s toFile:path];
47
48 }
49
50 - (IBAction)readBtnOnclick:(id)sender {
51 //1.擷取檔案路徑
52 NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
53 NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"];
54 NSLog(@"path=%@",path);
55
56 //2.從檔案中讀取對象
57 // YYPerson *p=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
58 // NSLog(@"%@,%d,%.1f",p.name,p.age,p.height);
59 YYstudent *s=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
60 NSLog(@"%@,%d,%.1f,%f",s.name,s.age,s.height,s.weight);
61 }
62 @end
點選儲存按鈕和讀取按鈕後的列印輸出:
四、重要說明
1.儲存資料過程:
//1.建立對象
YYstudent *s=[[YYstudent alloc]init];
s.name=@"wendingding";
s.age=23;
s.height=1.7;
s.weight=62;
//2.擷取檔案路徑
NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"];
NSLog(@"path=%@",path);
//3.将自定義的對象儲存到檔案中
[NSKeyedArchiver archiveRootObject:s toFile:path];
2.讀取資料過程:
//1.擷取檔案路徑
NSString *docPath=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)lastObject];
NSString *path=[docPath stringByAppendingPathComponent:@"person.yangyang"];
//2.從檔案中讀取對象
YYstudent *s=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
3.遵守NSCoding協定,并實作該協定中的兩個方法。
4.如果是繼承,則子類一定要重寫那兩個方法。因為person的子類在存取的時候,會去子類中去找調用的方法,沒找到那麼它就去父類中找,是以最後儲存和讀取的時候新增加的屬性會被忽略。需要先調用父類的方法,先初始化父類的,再初始化子類的。
5.儲存資料的檔案的字尾名可以随意命名。
6.通過plist儲存的資料是直接顯示的,不安全。通過歸檔方法儲存的資料在檔案中打開是亂碼的,更安全。
偏好設定
一、簡單介紹
很多iOS應用都支援偏好設定,比如儲存使用者名、密碼、字型大小等設定,iOS提供了一套标準的解決方案來為應用加入偏好設定功能
每個應用都有個NSUserDefaults執行個體,通過它來存取偏好設定。比如,儲存使用者名、字型大小、是否自動登入
存儲位置:
存儲形式:
1.storyboard
2.代碼
1 //
2 // YYViewController.m
3 // 01-偏好設定
4 //
5 // Created by apple on 14-6-7.
6 // Copyright (c) 2014年 itcase. All rights reserved.
7 //
8
9 #import "YYViewController.h"
10 //偏好設定
11 @interface YYViewController ()
12 /**
13 *儲存資料
14 */
15 - (IBAction)saveData:(id)sender;
16 /**
17 * 讀取資料
18 */
19 - (IBAction)readData:(id)sender;
20
21 @end
22
23 @implementation YYViewController
24
25 - (IBAction)saveData:(id)sender {
26 //1.擷取NSUserDefaults對象
27 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
28
29 //2儲存資料(如果設定資料之後沒有同步, 會在将來某一時間點自動将資料儲存到Preferences檔案夾下面)
30 [defaults setObject:@"yangyong" forKey:@"name"];
31 [defaults setInteger:23 forKey:@"age"];
32 [defaults setDouble:1.73f forKey:@"height"];
33 [defaults setObject:@"man" forKey:@"gender"];
34
35 //3.強制讓資料立刻儲存
36 [defaults synchronize];
37 }
38
39 - (IBAction)readData:(id)sender {
40 //1.擷取NSUserDefaults對象
41 NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
42 //讀取儲存的資料
43 NSString *name=[defaults objectForKey:@"name"];
44 NSString *gender=[defaults objectForKey:@"gender"];
45 NSInteger age=[defaults integerForKey:@"age"];
46 double height=[defaults doubleForKey:@"height"];
47 //列印資料
48 NSLog(@"name=%@,gender=%@,age=%d,height=%.1f",name,gender,age,height);
49 }
50 @end
3.點選儲存資料,讀取資料按鈕列印如下
三、補充說明
1.儲存資料
//1.擷取NSUserDefaults對象
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
//2儲存資料
[defaults setObject:@"yangyong" forKey:@"name"];
[defaults setInteger:23 forKey:@"age"];
[defaults setDouble:1.73f forKey:@"height"];
[defaults setObject:@"man" forKey:@"gender"];
//3.強制讓資料立刻儲存
[defaults synchronize];
2.讀取資料
//1.擷取NSUserDefaults對象
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
//2.讀取儲存的資料
NSString *name=[defaults objectForKey:@"name"];
NSString *gender=[defaults objectForKey:@"gender"];
NSInteger age=[defaults integerForKey:@"age"];
double height=[defaults doubleForKey:@"height"];
3.重要說明
(1)偏好設定是專門用來儲存應用程式的配置資訊的, 一般情況不要在偏好設定中儲存其他資料。如果利用系統的偏好設定來存儲資料, 預設就是存儲在Preferences檔案夾下面的,偏好設定會将所有的資料都儲存到同一個檔案中。
(2)使用偏好設定對資料進行儲存之後, 它儲存到系統的時間是不确定的,會在将來某一時間點自動将資料儲存到Preferences檔案夾下面,如果需要即刻将資料存儲,可以使用[defaults synchronize];
(3)注意點:所有的資訊都寫在一個檔案中,對比簡單的plist可以儲存和讀取基本的資料類型。
(4)步驟:擷取NSuserDefaults,儲存(讀取)資料
SQLite3詳解
SQLite是嵌入式的和輕量級的SQL資料庫。SQLite是由C實作的。廣泛用于包括浏覽器(支援HTML5的大部分浏覽器,IE除外)、iOS、Android以及一些便攜需求的小型web應用系統。
1 使用原因:存儲、檢索資訊
2 SQLite是MySQL精簡版。但無需伺服器就能進行。
3 兩個限制:1)必須手動建立資料庫 2)沒有面向對象的接口。
4 如何手動建立資料庫。
使用SQLite前的準備
使用SQLite是很多做iOS開發中第一次面對C的情況,包括我。因為SQLite是C寫的,Objective-C可以直接使用C代碼。在SQLite前,一般都會使用Cocoa Touch架構,都是基于Objective-C的。
首先,添加framework:libsqlite3.0.dylib
需要在對應檔案的頭檔案中加入:
#import "sqlite3.h" |
并在Frameworks中加入所需的庫,否則會報錯:
Undefined symbols: "_sqlite3_open", referenced from: |
加入庫的方法是:
或者點選 你的應用程式名,(最上面帶圖示的那個)中間視圖會出現frameworks的圖表,左下腳有一個加号,就是添加新的frameworks的地方了,在搜尋裡輸入需要的......,添加進去就OK了!
選擇sqlite庫:
選擇完的效果:
1 static NoteDAO *sharedManager = nil;
2
3 + (NoteDAO*)sharedManager
4 {
5 static dispatch_once_t once;
6 dispatch_once(&once, ^{
7
8 sharedManager = [[self alloc] init];
9 [sharedManager createEditableCopyOfDatabaseIfNeeded];
10
11
12 });
13 return sharedManager;
14 }
15
16 //打開資料庫
17 - (void)createEditableCopyOfDatabaseIfNeeded {
18
19 NSString *writableDBPath = [self applicationDocumentsDirectoryFile];
20
21 if (sqlite3_open([writableDBPath UTF8String], &db) != SQLITE_OK) {
22 sqlite3_close(db);
23 NSAssert(NO,@"資料庫打開失敗。");
24 } else {
25 char *err;
26 NSString *createSQL = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS Note (cdate TEXT PRIMARY KEY, content TEXT);"];
27 if (sqlite3_exec(db,[createSQL UTF8String],NULL,NULL,&err) != SQLITE_OK) {
28 sqlite3_close(db);
29 NSAssert1(NO, @"建表失敗, %s", err);
30 }
31 sqlite3_close(db);
32 }
33 }
34
35 - (NSString *)applicationDocumentsDirectoryFile {
36 NSString *documentDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
37 NSString *path = [documentDirectory stringByAppendingPathComponent:DBFILE_NAME];
38
39 return path;
40 }
41
42
43 //插入Note方法
44 -(int) create:(Note*)model
45 {
46
47 NSString *path = [self applicationDocumentsDirectoryFile];
48
49 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) {
50 sqlite3_close(db);
51 NSAssert(NO,@"資料庫打開失敗。");
52 } else {
53
54 NSString *sqlStr = @"INSERT OR REPLACE INTO note (cdate, content) VALUES (?,?)";
55
56 sqlite3_stmt *statement;
57 //預處理過程
58 if (sqlite3_prepare_v2(db, [sqlStr UTF8String], -1, &statement, NULL) == SQLITE_OK) {
59 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
60 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
61 NSString *nsdate = [dateFormatter stringFromDate:model.date];
62
63 //綁定參數開始
64 sqlite3_bind_text(statement, 1, [nsdate UTF8String], -1, NULL);
65 sqlite3_bind_text(statement, 2, [model.content UTF8String], -1, NULL);
66
67 //執行插入
68 if (sqlite3_step(statement) != SQLITE_DONE) {
69 NSAssert(NO, @"插入資料失敗。");
70 }
71 }
72
73 sqlite3_finalize(statement);
74 sqlite3_close(db);
75 }
76
77 return 0;
78 }
79
80 //删除Note方法
81 -(int) remove:(Note*)model
82 {
83 NSString *path = [self applicationDocumentsDirectoryFile];
84
85 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) {
86 sqlite3_close(db);
87 NSAssert(NO,@"資料庫打開失敗。");
88 } else {
89
90 NSString *sqlStr = @"DELETE from note where cdate =?";
91
92 sqlite3_stmt *statement;
93 //預處理過程
94 if (sqlite3_prepare_v2(db, [sqlStr UTF8String], -1, &statement, NULL) == SQLITE_OK) {
95 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
96 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
97 NSString *nsdate = [dateFormatter stringFromDate:model.date];
98
99 //綁定參數開始
100 sqlite3_bind_text(statement, 1, [nsdate UTF8String], -1, NULL);
101 //執行
102 if (sqlite3_step(statement) != SQLITE_DONE) {
103 NSAssert(NO, @"删除資料失敗。");
104 }
105 }
106
107 sqlite3_finalize(statement);
108 sqlite3_close(db);
109 }
110
111 return 0;
112 }
113
114 //修改Note方法
115 -(int) modify:(Note*)model
116 {
117
118 NSString *path = [self applicationDocumentsDirectoryFile];
119
120 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) {
121 sqlite3_close(db);
122 NSAssert(NO,@"資料庫打開失敗。");
123 } else {
124
125 NSString *sqlStr = @"UPDATE note set content=? where cdate =?";
126
127 sqlite3_stmt *statement;
128 //預處理過程
129 if (sqlite3_prepare_v2(db, [sqlStr UTF8String], -1, &statement, NULL) == SQLITE_OK) {
130
131 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
132 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
133 NSString *nsdate = [dateFormatter stringFromDate:model.date];
134
135 //綁定參數開始
136 sqlite3_bind_text(statement, 1, [model.content UTF8String], -1, NULL);
137 sqlite3_bind_text(statement, 2, [nsdate UTF8String], -1, NULL);
138 //執行
139 if (sqlite3_step(statement) != SQLITE_DONE) {
140 NSAssert(NO, @"修改資料失敗。");
141 }
142 }
143
144 sqlite3_finalize(statement);
145 sqlite3_close(db);
146 }
147 return 0;
148 }
149
150 //查詢所有資料方法
151 -(NSMutableArray*) findAll
152 {
153
154 NSString *path = [self applicationDocumentsDirectoryFile];
155 NSMutableArray *listData = [[NSMutableArray alloc] init];
156
157 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) {
158 sqlite3_close(db);
159 NSAssert(NO,@"資料庫打開失敗。");
160 } else {
161
162 NSString *qsql = @"SELECT cdate,content FROM Note";
163
164 sqlite3_stmt *statement;
165 //預處理過程
166 if (sqlite3_prepare_v2(db, [qsql UTF8String], -1, &statement, NULL) == SQLITE_OK) {
167
168 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
169 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
170
171 //執行
172 while (sqlite3_step(statement) == SQLITE_ROW) {
173 char *cdate = (char *) sqlite3_column_text(statement, 0);
174 NSString *nscdate = [[NSString alloc] initWithUTF8String: cdate];
175
176 char *content = (char *) sqlite3_column_text(statement, 1);
177 NSString * nscontent = [[NSString alloc] initWithUTF8String: content];
178
179 Note* note = [[Note alloc] init];
180 note.date = [dateFormatter dateFromString:nscdate];
181 note.content = nscontent;
182
183 [listData addObject:note];
184
185 }
186 }
187
188 sqlite3_finalize(statement);
189 sqlite3_close(db);
190
191 }
192 return listData;
193 }
194
195 //按照主鍵查詢資料方法
196 -(Note*) findById:(Note*)model
197 {
198
199 NSString *path = [self applicationDocumentsDirectoryFile];
200
201 if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) {
202 sqlite3_close(db);
203 NSAssert(NO,@"資料庫打開失敗。");
204 } else {
205
206 NSString *qsql = @"SELECT cdate,content FROM Note where cdate =?";
207
208 sqlite3_stmt *statement;
209 //預處理過程
210 if (sqlite3_prepare_v2(db, [qsql UTF8String], -1, &statement, NULL) == SQLITE_OK) {
211 //準備參數
212 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
213 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
214 NSString *nsdate = [dateFormatter stringFromDate:model.date];
215 //綁定參數開始
216 sqlite3_bind_text(statement, 1, [nsdate UTF8String], -1, NULL);
217
218 //執行
219 if (sqlite3_step(statement) == SQLITE_ROW) {
220 char *cdate = (char *) sqlite3_column_text(statement, 0);
221 NSString *nscdate = [[NSString alloc] initWithUTF8String: cdate];
222
223 char *content = (char *) sqlite3_column_text(statement, 1);
224 NSString * nscontent = [[NSString alloc] initWithUTF8String: content];
225
226 Note* note = [[Note alloc] init];
227 note.date = [dateFormatter dateFromString:nscdate];
228 note.content = nscontent;
229
230 sqlite3_finalize(statement);
231 sqlite3_close(db);
232
233 return note;
234 }
235 }
236
237 sqlite3_finalize(statement);
238 sqlite3_close(db);
239
240 }
241 return nil;
242 }
注:SQLite中也沒有定義日期時間類型,日期時間可以用TEXT,REAL,orINTEGER存儲
TEXT:存儲為字元串("YYYY-MM-DDHH:MM:SS.SSS").
REAL:asJuliandaynumbers,thenumberofdayssincenooninGreenwichonNovember24,4714B.C.accordingtotheprolepticGregoriancalendar.
INTEGER:asUnixTime,thenumberofsecondssince1970-01-0100:00:00UTC.
SQLiteTypeAffinity(類型檢測)
用于自動檢測值的類型,以下列舉Affinity如何決定類型的規則
(1)如果類型聲明中有int,則使用INTEGERaffinity.
(2)如果類型聲明中有"CHAR","CLOB",or"TEXT",則使用Textaffinity
(3)如果類型聲明中有BLOB或沒有指定類型,則使用affinityNONE
http://mobile.51cto.com/iphone-321872.htm
(4)如果類型聲明中有"REAL","FLOA",or"DOUB",則使用REALaffinity
(5)否則使用Numericaffinity
類型比較NULL
memcmp函數原型
intmemcmp(constvoid*ptr1,constvoid*ptr2,size_tnum);
比較兩個指針指向記憶體的前num個byte
比較之前的類型轉換
l(INTEGER,REALorNUMERIC)和(TEXTorNONE)比較,則TEXT,NONE會被轉換成NUMERIC
lTEXT和NONE比較,則NONE會被轉換成TEXT
其他情況直接比較。
iOS SQLite3具有的資料類型
NULL:NULLvalue
Integer:值是signedinteger類型,大小可以是1,2,3,4,6,8bytes
REAL:浮點類型
TEXT:以UTF-8,UTF-16BEorUTF-16LE編碼存儲的字元類型
BLOB:二進制資料
其它資料類型說明
在Cocoa環境下,如果你想使用資料庫(如sqlite),你可以使用sql語句的方式通過相關的工具類進行資料庫的直接操作。當然你也可以通過别人封裝之後的一些簡單架構,使得你的操作更加簡單(如FMDB BNRPersistence)。
Cocoa架構本身提供了CoreData這個API可友善的讓開發者通過操作對象的方式在操作資料庫。CoreData是一個對象圖(object graph)以及持久化的管理架構。我們可以通過CoreData創對象,設定好象之間的關系,然後将其持久化(我們甚至可以使用記憶體資料庫),或者從硬碟上将持久化後的資料加載到記憶體中。對象圖,我們可以建立一個個的對象,并維持不同對象之間的關系,一對一,一對多等。
CoreData有大量的特性,諸如支援Redo,Undo的功能,這些很多Document based的程式中顯得非常的有用。提供資料model結構變化輕量級的遷移方案。CoreData還通過Binding特性和控件的緊密結合,這樣使得隻需要少量的代碼便可以完成強大的功能,下面是一個例子
http://www.timisted.net/blog/archive/multiple-windows-with-core-data/
存儲方式
Core Data可以将資料存儲為XML,二進制檔案或SQLite檔案。在Mac OS X 10.5 Leopard及以後的版本中,開發者也可以通過繼承NSPersistentStore類以建立自定義的存儲格式。每種方法都有其優缺點,例如XML的可讀性,SQLite的節約空間等。
Core Data的這一方面類似于原始的Enterprise Objects Framework(EOF)系統,但EOF中開發者可以使用相對簡潔的查詢方式,而在Core Data中,隻能使用一個文法類似SQL子集的查詢語言,稱為Predicate。Core Data是标準化的,可以自由的讀寫Xcode資料模型檔案(通常是.xcdatamodel檔案)。
與EOF不同,Core Data目前沒有設計多使用者或多線程通路模式。模型遷移通常也需要代碼,若其它開發者依賴于某個資料模型,則該資料模型的設計者可能在模型發生改變時需要與新資料模型一起提供版本轉換代碼。
操作簡介
Core Data由相對龐大的類繼承體系組成,但開發者需要關注的接口隻是其中的一個相對小的子集。
一般需要定義以下Core Data的三個必備
NSPersistentStoreCoordinator *persistentStoreCoordinator;
NSManagedObjectModel *managedObjectModel;
NSManagedObjectContext *managedObjectContext;
以及使用時需要用到的
NSFetchedResultsController *fetchedResultsController;
使用步驟:
還記得我們每次使用CoreData的時候系統都會給我們建立一些代碼嗎?
1 #pragma mark - Core Data 堆棧
2 //傳回 被管理的對象上下文
3 - (NSManagedObjectContext *)managedObjectContext
4 {
5 if (_managedObjectContext) {
6 return _managedObjectContext;
7 }
8
9 NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
10 if (coordinator) {
11 _managedObjectContext = [[NSManagedObjectContext alloc] init];
12 [_managedObjectContext setPersistentStoreCoordinator:coordinator];
13 }
14 return _managedObjectContext;
15 }
16
17 // 傳回 持久化存儲協調者
18 - (NSPersistentStoreCoordinator *)persistentStoreCoordinator
19 {
20 if (_persistentStoreCoordinator) {
21 return _persistentStoreCoordinator;
22 }
23
24 NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataNotes.sqlite"];
25
26 _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
27
28 [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
29 configuration:nil
30 URL:storeURL
31 options:nil
32 error:nil];
33
34 return _persistentStoreCoordinator;
35 }
36
37 // 傳回 被管理的對象模型
38 - (NSManagedObjectModel *)managedObjectModel
39 {
40 if (_managedObjectModel) {
41 return _managedObjectModel;
42 }
43 NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataNotes" withExtension:@"momd"];
44 _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
45 return _managedObjectModel;
46 }
47
48 #pragma mark - 應用程式沙箱
49 // 傳回應用程式Docment目錄的NSURL類型
50 - (NSURL *)applicationDocumentsDirectory
51 {
52 return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
53 }
下面是使用Core的示例代碼
1 static NoteDAO *sharedManager = nil;
2
3 + (NoteDAO*)sharedManager
4 {
5 static dispatch_once_t once;
6 dispatch_once(&once, ^{
7
8 sharedManager = [[self alloc] init];
9 [sharedManager managedObjectContext];
10
11 });
12 return sharedManager;
13 }
14
15
16 //插入Note方法
17 -(int) create:(Note*)model
18 {
19
20 NSManagedObjectContext *cxt = [self managedObjectContext];
21
22 NoteManagedObject *note = [NSEntityDescription insertNewObjectForEntityForName:@"Note" inManagedObjectContext:cxt];
23 [note setValue: model.content forKey:@"content"];
24 [note setValue: model.date forKey:@"date"];
25
26 note.date = model.date;
27 note.content = model.content;
28
29 NSError *savingError = nil;
30 if ([self.managedObjectContext save:&savingError]){
31 NSLog(@"插入資料成功");
32 } else {
33 NSLog(@"插入資料失敗");
34 return -1;
35 }
36
37 return 0;
38 }
39
40 //删除Note方法
41 -(int) remove:(Note*)model
42 {
43
44 NSManagedObjectContext *cxt = [self managedObjectContext];
45
46 NSEntityDescription *entityDescription = [NSEntityDescription
47 entityForName:@"Note" inManagedObjectContext:cxt];
48
49 NSFetchRequest *request = [[NSFetchRequest alloc] init];
50 [request setEntity:entityDescription];
51
52 NSPredicate *predicate = [NSPredicate predicateWithFormat:
53 @"date = %@", model.date];
54 [request setPredicate:predicate];
55
56 NSError *error = nil;
57 NSArray *listData = [cxt executeFetchRequest:request error:&error];
58 if ([listData count] > 0) {
59 NoteManagedObject *note = [listData lastObject];
60 [self.managedObjectContext deleteObject:note];
61
62 NSError *savingError = nil;
63 if ([self.managedObjectContext save:&savingError]){
64 NSLog(@"删除資料成功");
65 } else {
66 NSLog(@"删除資料失敗");
67 return -1;
68 }
69 }
70
71 return 0;
72 }
73
74 //修改Note方法
75 -(int) modify:(Note*)model
76 {
77 NSManagedObjectContext *cxt = [self managedObjectContext];
78
79 NSEntityDescription *entityDescription = [NSEntityDescription
80 entityForName:@"Note" inManagedObjectContext:cxt];
81
82 NSFetchRequest *request = [[NSFetchRequest alloc] init];
83 [request setEntity:entityDescription];
84
85 NSPredicate *predicate = [NSPredicate predicateWithFormat:
86 @"date = %@", model.date];
87 [request setPredicate:predicate];
88
89 NSError *error = nil;
90 NSArray *listData = [cxt executeFetchRequest:request error:&error];
91 if ([listData count] > 0) {
92 NoteManagedObject *note = [listData lastObject];
93 note.content = model.content;
94
95 NSError *savingError = nil;
96 if ([self.managedObjectContext save:&savingError]){
97 NSLog(@"修改資料成功");
98 } else {
99 NSLog(@"修改資料失敗");
100 return -1;
101 }
102 }
103 return 0;
104 }
105
106 //查詢所有資料方法
107 -(NSMutableArray*) findAll
108 {
109 NSManagedObjectContext *cxt = [self managedObjectContext];
110
111 NSEntityDescription *entityDescription = [NSEntityDescription
112 entityForName:@"Note" inManagedObjectContext:cxt];
113
114 NSFetchRequest *request = [[NSFetchRequest alloc] init];
115 [request setEntity:entityDescription];
116
117 NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES];
118 [request setSortDescriptors:@[sortDescriptor]];
119
120 NSError *error = nil;
121 NSArray *listData = [cxt executeFetchRequest:request error:&error];
122
123 NSMutableArray *resListData = [[NSMutableArray alloc] init];
124
125 for (NoteManagedObject *mo in listData) {
126 Note *note = [[Note alloc] init];
127 note.date = mo.date;
128 note.content = mo.content;
129 [resListData addObject:note];
130 }
131
132 return resListData;
133 }
134
135 //按照主鍵查詢資料方法
136 -(Note*) findById:(Note*)model
137 {
138 NSManagedObjectContext *cxt = [self managedObjectContext];
139
140 NSEntityDescription *entityDescription = [NSEntityDescription
141 entityForName:@"Note" inManagedObjectContext:cxt];
142
143 NSFetchRequest *request = [[NSFetchRequest alloc] init];
144 [request setEntity:entityDescription];
145
146 NSPredicate *predicate = [NSPredicate predicateWithFormat:
147 @"date = %@",model.date];
148 [request setPredicate:predicate];
149
150 NSError *error = nil;
151 NSArray *listData = [cxt executeFetchRequest:request error:&error];
152
153 if ([listData count] > 0) {
154 NoteManagedObject *mo = [listData lastObject];
155
156 Note *note = [[Note alloc] init];
157 note.date = mo.date;
158 note.content = mo.content;
159
160 return note;
161 }
162 return nil;
163 }
CoreData進階常識
關于CoreData貌似實際開發中很少用到,基本上是個有九個公司不會使用它,因為都說是性能不好,但是作為一個程式員,了解及其使用時必須了,
下面是我從一位大神那裡搬過來的一下Core詳細介紹,相信以後總有一天會幫我解決不少學習CoreData中的問題!
一、技術概覽
1. Core Data 功能初窺
對于處理諸如對象生命周期管理、對象圖管理等日常任務,Core Data架構提供了廣泛且自動化的解決方案。它有以下特性。
(注:對象圖-Object graph的解釋:在面向對象程式設計中,對象之間有各種關系,例如對象直接引用另外的對象,或是通過引用鍊間接的引用其他對象,這些關系組成了網狀的結構。 我們把這些對象(和它們之間的聯系)成為對象圖。 對象圖可大可小,有繁有簡。 隻包含單個字元串對象的數組就是一個簡單的代表;而包含了application對象,引用windows, menus和相關視圖對象、其他對象這樣的結構就是複雜對象圖的例子——這是在說mainwindow.xib。
有時,你可能想要把這樣的對象圖轉化形式,讓它們可以被儲存到檔案中,以使其他的程序或其他的機器可以再次将儲存的内容讀出,重購對象。 這樣的過程常被成之為“歸檔”(Archiving)。
有些對象圖是不完整的——通常稱之為局部對象圖(partial object graphs)。局部對象圖包含了“占位符”(Placeholder)對象,所謂”占位符“,就是一些暫時無内容的對象,它們将再後期被具體化。一個典 型的例子就是nib檔案中包含的File's Owner對象。
1) 對于key-value coding 和key-value observing完整且自動化的支援
除了為屬性整合KVC和KVO的通路方法外, Core Data還整合了适當的集合通路方法來處理多值關系。
2) 自動驗證屬性(property)值
Core Data中的managed object擴充了标準的KVC 驗證方法,以保證單個的數值在可接受的範圍之内,進而使組合的值有意義。(需校準翻譯)
3) 支援跟蹤修改和撤銷操作
對于撤銷和重做的功能,除過用基本的文本編輯外,Core Data還提供内置的管理方式。
4) 關系的維護
Core Data管理資料的變化傳播,包括維護對象間關系的一緻性。
5) 在記憶體中和界面上分組、過濾、組織資料
6) 自動支援對象存儲在外部資料倉庫的功能
7) 建立複雜請求
你不需要動手去寫複雜的SQL語句,就可以建立複雜的資料請求。方法是在“擷取請求”(fetch request)中關聯NSPredicate(又看到這個東東了,之前用它做過正則)。NSPrdicate支援基本的功能、相關子查詢和其他進階的 SQL特性。它還支援正确的Unicode編碼(不太懂,請高人指點), 區域感覺查詢(據說就是根據區域、語言設定調整查詢的行為)、排序和正規表達式。
8) 延遲操作(原文為Futures(faulting)直譯為期貨,這裡個人感覺就是延遲操作的形象說法。請高人指教)。
Core Data 使用延遲加載(lazy loading)的方式減少記憶體負載。 它還支援部分實體化延遲加載,和“寫時拷貝”的資料共享機制。(寫時拷貝,說的是在複制對象的時候,實際上不生成新的空間,而是讓對象共享一塊存儲區域, 在其内容發生改變的時候再配置設定)。
9) 合并的政策
Core Data 内置了版本跟蹤和樂觀鎖定(optimistic locking)來支援多使用者寫入沖突的解決。
注:樂觀鎖,假定資料一般不出現沖突,是以在資料送出更新的時候,才對資料的沖突進行檢測,如果沖突了,就傳回沖突資訊。
10) 資料遷移
就開發工作和運作時資源來說,處理資料庫架構的改變總是很複雜。Core Data的schema migration工具可以簡化應對資料庫結構變化的任務, 而且在某些情況下,允許你執行高效率的資料庫原地遷移工作。
11) 可選擇針對程式Controller層的內建,來支援UI的顯示同步
Core Data在iPhone OS之上 提供NSFetchedResultsController對象來做相關工作,在Mac OS X上,我們用Cocoa提供的綁定(Binding)機制來完成。
2. 為何要使用Core Data
使用Core Data有很多原因,其中最簡單的一條就是:它能讓你為Model層寫的代碼的行數減少為原來的50%到70%。 這歸功于之前提到的Core Data的特性。更妙的是,對于上述特性你也既不用去測試,也不用花功夫去優化。
Core Data擁有成熟的代碼,這些代碼通過單元測試來保證品質。應用Core Data的程式每天被世界上幾百萬使用者使用。通過了幾個版本的釋出,已經被高度優化。 它能利用Model層的資訊和運作時的特性,而不通過程式層的代碼實作。 除了提供強大的安全支援和錯誤處理外,它還提供了最優的記憶體擴充性,可實作有競争力的解決方案。不使用Core Data的話,你需要花很長時間來起草自己的方案,解決各種問題,這樣做效率不高。
除了Core Data本身的優點之外,使用它還有其他的好處: 它很容易和Mac OS X系統的Tool chain內建;利用Model設計工具可以按圖形化方式輕松建立資料庫的結構;你可以用Instruments的相關模闆來測試Core Data的效率并debug。 在Mac OS X的桌面程式中,Core Data還和Interface Builder內建(打開Inspector可以看到有binding的選項,這個東東iPhone上木有。。。),按照model來建立UI變的更簡單 了。 這些功能能更進一步的幫助你縮短設計、開發、測試程式的周期。
3. Core Data不是。。。
看了前面的介紹之後,我們還需要了解一下關于Core Data常見的誤解:
1) Core Data不是一個關系型資料庫,也不是關系型資料庫管理系統(RDBMS)。
Core Data 為資料變更管理、對象存儲、對象讀取恢複的功能提供了支援。 它可以使用SQLite作為持久化存儲的類型。 它本身并不是一個資料庫(這點很重要,比如,你可以使用Core Data來記錄資料變更,管理資料,但并不能用它向檔案記憶體儲資料)。
2) Core Data不是銀彈
它并不能取代你寫代碼的工作。雖然可以純粹使用XCode的資料模組化工具和Interface Builder來編寫複雜程式,但在更多的程式中,你都自己動手寫代碼。
3) Core Data并不依賴于Cocoa Bindings
Core Data + Cocoa Binding = 減少代碼數量。但Core Data完全可以在沒有bindings的條件下使用。例如,可以編寫一個沒有UI,但包含Core Data的程式。
二、Core Data基礎
1. Core Data基本架構
在大部分程式中,你要能通過某種方式打開一個包含對象歸檔的檔案, 這個檔案内至少要有一個根對象的引用。另外,還得能将所有的對象歸檔到檔案中,如果你想要實作撤銷的功能,就還要記錄對象的更改情況。例如,在 Employee的示例程式中,你要能打開一個包含有employee和department對象歸檔的檔案,而且這個檔案至少包含了一個根對象——這 裡,是一個包含所有employee的數組——請參考例圖Figure 1。 相應的,你還要能将程式中的employee、department對象歸檔到檔案中去。
Figure 1 按照Core Data文檔結構管理的對象示意圖
使用Core Data的架構,大多數的功能都可以自動實作,因為我們有managed object context(管理對象的上下文,有時直接叫"Context")。managed object context就像是一個關卡,通過它可以通路架構底層的對象——這些對象的集合我們稱之為"persistence stack"(資料持久棧)。 managed object context作為程式中對象和外部的資料存儲的中轉站。棧的底部是persistence object stores(持久化資料存儲),請看Figure 2的示意圖。
Figure 2 使用Core Data的文檔管理示意圖
Core Data的使用并不限制在基于文檔的程式中(document-based application)。你也能建立一個包含Core Data 的Utility程式(請檢視Core Data Utility tutorial文檔)。當然其他類型的程式也都可以使用Core Data。
被管理對象和上下文(Managed Objects and Contexts)
你可以把被管理對象上下文想象成一個”聰明“的便箋簿。當你從資料持久層擷取對象時,就把這些臨時的資料拷貝拿到寫在自己的便箋簿上(當然,在便箋上對象 會 “恢複”以前的對象圖結構)。然後你就可以随心所欲的修改這些值了(本子是你的,随便畫都可以),除非你儲存這些資料變化,否則持久層的東西是不會變 的。(跟修改檔案後要儲存是一個道理)。
附在Core Data架構中模型對象(Model objects)常被稱為“被管理對象”(Managed objects)。所有的被管理對象都要通過上下文進行注冊。使用上下文,你可以在對象圖中添加、删除對象,并記錄對象的更改(包括單個對象,或是對象間 的關系)。記錄更改後就能支援撤銷和重做的功能。同時,上下文還能保證關系更改後對象圖的完整性。
如果你想要儲存所做的修改, 上下文會保證對象的有效性。在驗證有效性後,更改會被寫入到persistent store(持久化存儲層)中。你在程式中的添加和删除動作都會被作用在存儲的資料中。
在你的一個程式中,可能存在多個上下文。 對于資料存儲(store)中的每個對象,對應的都有唯一的一個被管理對象(managed object)和上下文相關聯(詳情請檢視"Faulting and Uniquing"文檔)。換個角度來想,在persistent store中存儲的對象有可能被用在不同的上下文中,每個上下文都有與之對應的被管理對象,被管理對象可以被獨立的修改,這樣就可能在存儲 時導緻資料的不一緻。Core Data提供了許多解決這個問題的途徑(請檢視"Using Managed Object"一章)。
擷取資料的請求(Fetch Requests)
要使用上下文來擷取資料,你需要建立相應的請求(Fetch request)。 Fetch request對象包含你想擷取的對象的描述。例如:“所有 Employee”,或“所有的Employee,department是marketing,按薪資降序排列”。Fetch Request包含三個部分。使用最簡單的寫法,必須指定實體(Entity)的名稱,這就暗示了,每次智能獲得一種類型的實體。 Fetch Request 還可以包含謂詞(predicate)——注:有些地方也把這個叫斷言,個人感覺謂詞更準确些。謂詞将描述對象需要滿足的條件(這就和我們在SQL裡加的 限定條件差不多,正如前面的"All Employees, in the Marketing department")。另外,Fetch Request還可包含一個用于描述排序方式的對象(熟悉的Order by操作)。如圖Figure3所示:
在程式中,你将Fetch Request這個請求發送給上下文,上下文就會從相關的資料源中查找複合條件的對象(也可能找不到),并傳回。 所有的被管理對象(managed object)都必須在上下文中注冊,是以通過fetch request獲得的對象自動被注冊。但如前所述,每個在持久存儲層(persistence store)中的對象都對應一個和上下文相關的被管理對象(managed object)是以,如果在上下文中已經存在了fetch request要取的對象,那麼這個被管理對象将被傳回。
Core Data追求高執行效率。 它是“需求驅動”的,是以隻會建立你确實需要的對象。對象圖不需要保留所有在資料存儲層中的對象。單純指定資料持久層的動作不會将其中所有的資料放到上下 文中去。 當你想從資料存儲層中擷取某些對象的時候,你隻會得到那些你請求的(有點羅嗦,總的意思就是需要時擷取,擷取的就是需要的)。如果你不在需要這個對象的時 候,預設情況下它會被釋放。(當然,隻是釋放這個對象,而不是從對象圖中移除該對象)。——注:個人感覺有點像重新拷了一個檔案的某些部分,不用了就在副 本中删除,不會影響原件。
持久化存儲助理(Persistent Store Coordinator)
之前提到過,程式中的對 象和外部存儲的資料通過Core Data架構中的一系列對象進行協調,這一系列的對象總的被稱為持久存儲棧(Persistence stack)。在棧頂是被管理對象上下文(Managed object context),而棧底是持久化對象存儲層(Persistence object store)。在它們之間就是持久化存儲助理。
事實上,持久化存儲助理定義了一個棧。從設計方面考慮,它就是可以作為上下 文的”外觀“, 這樣多個資料存儲(Persistence store)看起來就像是一個。 然後上下文就可以根據這些資料存儲來建立對象圖了。持久化存儲助理智能關聯一個被管理對象的模型。如果你像要把不同的實體放到不同的存儲中去,就需要為你 的模型實體做“分區”,方式是通過定義被管理對象模型的configurations。(請參考"Configurations"一章)。
Figure 4示範了這樣的一個結構:employees和departments存儲在一個檔案中,customers和companies存儲在另外一個檔案中。當你要擷取對象的時候,它們從相關的檔案中自動擷取;當儲存時,又被歸檔到相應的檔案中。
Figure 4存儲棧—改
持久化存儲(Persistent Stores)
持久化存儲是和單獨的一個檔案或外部的資料關聯的,它負責将資料和上下文中的對象進行對應。通常,需要你直接和持久化對象存儲打交道的地方,就是指定新 的、 和程式進行關聯的外部資料的位置(例如,當使用者打開或儲存一個文檔)。大多數需要通路持久化存儲的動作都由上下文來完成。
程式的代碼—— 特别是和被管理對象相關的部分——不應該對持久化存儲做任何假設(也就是不需要自己考慮存儲的方式或過程)。 Core Data對幾種檔案格式有原生的支援。你可以選擇一種自己程式需要的。假設在某個階段你決定換一種檔案的格式,而又不想修改程式的架構,而且,你的程式做 了适當的抽象(注:這個就屬于設計方面的東東了),這時,你就能嘗到使用Core Data的甜頭了。例如,在最初的設計中,程式隻從本地檔案中擷取資料,而你的程式沒有去硬指定對應資料的擷取位置,而是可以在後期指定從遠端位置添加新 的資料類型,這樣你就可以使用新的類型,而不需要修改代碼。(這段還是感覺翻的不太合适)。
重要提示:
雖然Core Dta支援SQLite作為一種存儲類型,但它不能使用任意的SQLite資料庫。Core Data在使用的過程種自己建立這個資料庫。(詳情,請參考"Persistence Store Features")。
持久化文檔(Persistent Documents)
你可以通過代碼的方式建立和配置持久存儲棧,但在多數情況下,你隻是想建立一個基于文檔 的應用程式(Document-based application,這個是mac上的)來讀寫檔案。這時,用NSDocument的子類NSPersistentDocument可以讓你感受到使 用Core Data的便利。預設狀況下,NSPersistentDocument就已經建立了它自己的持久存儲棧,其中包含了上下文,和單個的持久對象存儲,來處 理這樣文檔和外部資料“一對一”的映射關系。
NSPersistentDocument類提供了通路文檔的上下文的方法,也實作了标準的NSDocument方法來通過Core Data讀寫檔案。 一般說來,你不需要編寫額外的代碼來處理對象的持久化。
持久化文檔的撤銷(undo)操作也被內建在被管理對象的上下文中。
被管理對象和被管理對象模型(Managed Objects and the Managed Object Model)
為 了管理對象圖,也為了提供對象持久化的功能,Core Data需要對對象有很強的描述能力。被管理對象模型就是程式中對象、實體描述的概要圖,如圖Figure 5所示。建立模型的常用做法是通過Xcode的圖形化模組化工具Date Model Design tool。但是如果你願意的話,也可以在運作時通過代碼來模組化。
Figure 5 有兩個實體的對象模型
模型由多個實體描述對象構成,每個描述提供實體的某項中繼資料,它們包含實體名、實體在程式中的類名(當然,類名和實體名不需要一緻)、屬性還有關系。屬性和關系依次被屬性和關系描述對象所代表,如圖Figure 6所示。
Figure 6 帶有兩個屬性和一個關系的的實體描述
被管理對象必須是NSManagedObject或其子類的執行個體。 NSManagedObject可用來表示任何實體。它使用内部私有的存儲機制來維護自身的屬性,并執行一個被管理對象所必須的基本操作。一個被管理對象 擁有一份實體描述的引用。在使用時,它通過實體描述來找到自身的中繼資料,包括實體名和屬性、關系的資訊。你也可以繼承NSManagedObject來執 行額外的操作。
被管理對象模型(Managed Object Models)
多數Core Data的功能依賴于你建立的,用來描述程式的實體及其屬性、關系的模型圖。 模型圖由NSManagedObjectModel所表示。一般說來,模型的資訊越充實,Core Data能提供的功能就越好。 下文講解了對象模型的特性,以及如何在程式中建立、使用對象模型。
被管理對象模型的特性
被管理對象模型是 NSManagedObjectModel的執行個體。它描述了你在程式中使用的實體的概要資訊。(如果讀者不了解entity、property、 attribute和relationship的含義,請先檢視"Core Data Basics"和"Cocoa Design Patterns"文檔中的"Object Modeling"一節)
實體(Entities)
模型包含了NSEntityDescription對象,NSEntityDescription對象指代了模型的實體。關于實體由兩個重要特征:名稱 (name)和類名(name of class)。你應該弄清楚實體、實體的類和作為實體執行個體的被管理對象之間的差別。
NSEntityDescription 對象可包含NSAttributeDescription對象(指代實體的attribute)和NSRelationshipDescription對 象(指代實體間的relationship)。實體也可能包含fetched屬性,該屬性由NSFetchedPropertyDescription指 代,模型中有對應的fetch請求的模闆,fetch請求由NSFetchRequest所指代。
實體的繼承關系
實體的繼承和類 的繼承很類似,當然,也同樣有用。 如果你有若幹個相似的實體,就可以抽離出它們的共有特性作為一個“父實體”,就省去了在多個實體中都指定相同的屬性。 例如,你可以定義一個包含firstName和lastName的“Person”實體,然後在定義子實體"Employee"和"Customer"。
如果是使用Xcode的可視化模組化工具來建立模型,你就可以通過如下圖的方式為一個實體指定父級實體。
Figure1 Xcode中為一個實體指定父實體
如果你想在代碼中建立繼承關系。就需要自頂向下來執行。不能直接指定實體的父實體,而隻能給一個實體指定子實體(使 用setSubentities:)。這 就是說,如果你想給A實體指定父實體,就隻能把A作為數組中的一個元素,調用目标父實體setSubentities:的方式來設定。
抽象實體
你可以把一個實體指定為“抽象實體”,也就是說,你不打算使用這個實體來建立執行個體。通常,當你想把這個實體作為父實體,而有子實體來實作詳細内容的時候, 就 把它聲明“抽象實體”。(和抽象類很像)。例如,在一個繪圖程式中,你可能會設計一個Graphic實體,它包含了x和y坐标資訊、顔色、繪制區域,而你 不會去建立一個Graphic的執行個體,而是使用具體的子實體——Circle、TextArea、Line。(這些基本的東西就不給大牛們再羅嗦 了。。。)
Properties(屬性,這個和Attributes的意思一樣,實在差別不出來,隻好上英語了)
實體的 Properties是它的attributes和relationship,包含了fetched屬性(如果有的話)。每個property都有名稱和 類型。 Attribute也可能有預設值。property的名稱不能和NSObject和NSManagedObject類中的無參方法名相同。例如,不能把 property命名為"description"。
臨時屬性(Transient Property)也是作為模型的一部分,但是不作為實體執行個體的資料儲存在持久存儲層。 Core Data也會跟蹤臨時屬性的變化,以備撤銷操作時使用。
注意:如果你用模型外的資訊對臨時屬性執行撤銷操作,Core Data将不會使用舊值,調用你的set方法——它隻會更新快照資訊(snapshot information)。(這段怪怪的,用到的話在修改一下翻譯吧)
Attributes
Core Data内部支援各種attribute的類型,例如string,date,integer(NSString, NSDate, NSNumber)。如果你使用那些不支援的資料,你需要用到在“Non-Standard Persistent Attributes”介紹到的技術。
你可以将一個attribute聲明為“可選”(optional),可選的attribute不 必須有值,但是,不鼓勵你将屬性置空——尤其是數字值(更好的解決方案是使用強制的值,在這裡,我們用預設值,例如0)。 這樣做的原因是為了配合SQL中對于空值NULL做比較的操作:NULL不同于Objective-C中的nil。 資料庫中的NULL不同于0,搜尋0值的操作不會比對到值為NULL的列。
false == (NULL == 0)
false == (NULL != 0)
而且,在資料庫中,NULL也不等于空字元串或是空的資料對象:
false == (NULL == @"")
false == (NULL != @"")
它們之間一點關系都沒有。
關系(Relationships)
Core Data支援對一、對多的關系,也支援fetched屬性。 Fetched property表示了一種“弱”的、單項的關系。 在employees和departments的例子中, department 的一個fetched property可能是“最近雇傭人”(recent hires),而反過來,employee不會擁有這樣的關系。
擷取資料請求的模闆(Fetch Request Templates)
我們使用NSFetchRequest類來描述資料請求,利用資料請求從持久存儲(persistent store)中擷取對象。 經常需要多次執行同樣的請求,或是執行某種模式的請求,但是其中包含可變的元素(如查找條件)——這些元素經常有使用者提供。 例如,在運作的時候,你要根據使用者需要擷取某個作者在某個指定日期後的出版的所有出版物。
你可以預定義請求,把它們作為模闆存儲在被管理對象模型中。 預定義的模闆在你需要的時候就可以取出使用。通常情況下,我們通過Xcode的data modeling tool工具建立請求模闆。模闆可以包含變量,如圖Figure 2所示。
Figure 2 Xcode predicate builder
關于Fetch request templates的詳細資訊,請檢視"Accessing and Using a Managed Object Model at Runtime"的描述。
使用者資訊字典(User Info Dictionaries)
模型中的許多元素,諸如entities, attributes, relationships,都有相關的使用者資訊字典。用熟悉的鍵-值對,你可以向其中放置任何你需要的資料。這裡常用的資訊有實體的版本詳情,還有針對 fetched property,給謂詞(predicate)用的值。
配置(Configurations)
配置包含了一個名稱和若幹個相關的實體。實體的集合是可以重疊的——這就是說,一個實體可以出現在多個配置中。在代碼中,我們使用 setEntities: forConfiguration:的方法來指定配置。也可以用Xcode的模組化工具來指定(選中某個實體,就在屬性視窗的第三個,就是一個小扳手的符 号)。要擷取某項配置的實體,需要用entitiesForConfiguration:的方法。
一般說來,如果你想把不同的實體存放在不同的存儲中去,就可能用到配置。一個持久化存儲助理(persistent store coordinator)隻能有一個被管理對象模型。是以,預設情況下,和助理關聯的某個存儲必須包含同樣的實體。要想繞過這個限制,你可以建立一個包含 實體子集的模型,然後為每一個子集建立配置,這樣一來,使用這個模型建立助理,當你需要添加存儲時,可使用不同的配置指定對應的存儲屬性。當你建立配置的 時候,需要記住,不能建立跨存儲的關系。
使用被管理對象模型
通常可以使用Xcode的模組化工具來建立模型(請參考"Create a managed object with Xcode")。你也可以全部使用代碼來建立(請參考"Core Data Utility Tutorial")。
編譯資料模型
資料模型是一種部署資源。 在模型中,除了有實體和屬性的詳細資訊外,用Xcode建立的模型還包含了一些額外的視圖資訊,包括布局、顔色等等。這些資訊在運作時不是必須的。模型文 件在編譯的過程中會删除這些額外資訊以保證盡可能高效的加載。xcdatamodel“源”檔案會被momc編譯器編譯為mom的目标檔案。
"mom" 位于 /Library/Application Support/Apple/Developer Tools/Plug-ins/XDCoreDataModel.xdplugin/Contents/Resources/,如果你想把它用在自己的 build腳本中,格式是:mom source destination, source 就是Core Data Model檔案,destination就是輸出的mom檔案。
加載資料模型
在一些情況下,你不需要寫任何加載模型的代碼。如果你使用基于文檔的程式架構(Document-based application),NSPersistentDocument會管理諸如查找模型、加載模型的任務。 如果你建立了非Document-based application,而且裡面又用到了Core Data,一般将擷取模型的代碼放在application delegate裡。模型的存儲名稱——也就是檔案名,
和運作時的名稱是不相關的,一旦模型被加載,檔案名就沒有什麼意義了。也就是說,對模型檔案,你可以随意命名。
如果你想手動加載模型,有兩種方式可用,它們各有各的好處:
你可以從指定的bundle集合裡建立整合模型,使用如下的類方法:
mergeModelFromBundles:
也可以用指定的URL加載單個的模型,使用如下的執行個體方法:
initWithContentsOfURL: (這個方法相信大家都用過)
若不需要考慮分開加載模型,第一個類方法很适用。例如:在你的程式中和程式連結的framework裡都有你想要加載的模型。這個類方法可以讓你很輕松的加載所有的模型,而不需要考慮模型檔案的名稱,也不用特定的初始化方法來保證所有的模型都被找到。
但是當你有多個模型要加載,特别是這些模型都代表了一個schema的不同版本,這時,知道要加載哪個模型就很重要了(合并包含相同實體的模型可能導緻命 名沖突和錯誤,我們之前“一鍋端”的方法不太合适了)。在這種情況下,我們可以用第二個執行個體方法。 另外,有時我們也需要将模型存儲在bundle之外,也需要用這個方法從指定的URL位置加載模型。
還有一點需要說明:我們還有一個類方法 modelByMergingModels:可以用。像mergedModelFromBundles:方法一樣,它也能合并給定的若幹個模型。這樣,我 們就可以通過URL來逐一加載模型,然後在建立助理對象之前将它們整合為一個。
改變模型
由于模型描述了存儲層資料的結構,任何改變模型的動作都将使其不在适配于之前建立的存儲層。 如果你改變了模型的結構,就需要将目前存儲層的資料遷移到新版本。(請參考"Core Data Model Versioning and Data Migration Programming Guide"文檔)。例如:如果你添加了新的實體,或新的屬性,你将無法打開舊的存儲;如果你添加了驗證的限制,或者為屬性添加了新的預設值,你就可以打 開舊的存儲。
在運作時通路和适用被管理對象模型
在運作時,被管理對象模型就是一個簡單的“對象圖”(這個概念之前提到過),認識到這點很重要,尤其是當你需要用代碼來通路模型的詳細資訊時。例如:修改 模型(你隻能在runtime之前這樣做,請參考 NSManagedObjectModel),取回資訊(如本地化實體名,屬性資料類型,或資料請求模闆)。
在運作時通路模型有很多方法,通過持久棧最終從持久化存儲助理得到模型,代碼如下:
[[aManagedObjectContext persistentStoreCoordinator]managedObjectModel];
你也可以通過實體描述得到模型,是以給定一個被管理對象,你就可以得到它的實體描述,進而獲得模型。代碼如下:
[[aManagedObject entity] managedObjectModel];
某些情況下,你要維護模型的“直接”引用,也就是說,一個直接傳回模型的方法。NSPersistentDocument提供了 managedObjectModel方法,可以傳回一個模型,該模型和在文檔的上下文中使用的持久化存儲助理相關聯。如果你使用Core Data Appplication的模闆,application delegate将負責模型的引用。
通過代碼建立擷取資料請求模闆(Fetch Request Templates)
你可以通過代碼建立資料請求模闆并将其和模型關聯,方法是:setFetchRequestTemplate: forName:如Listing-1所示。 提醒一下:你隻能在模型被助理(coordinator)使用之前修改它。
Listing 1 通過代碼建立擷取資料請求模闆
NSManagedObjectModel *model = …;
NSFetchRequest * requestTemplate = [[NSFetchRequest alloc]init];
NSEntityDescription *publicationEntity =
[[model entitiesByName] objectForKey: @"Publication"];
[requestTemplate setEntity: publicationEntity];
NSPredicate *predicateTemplate = [NSPredicate predicateWithFormat:
@"(mainAuthor.firstName like[cd] $FIRST_NAME) AND \
(mainAuthor.lastName like[cd] $LAST_NAME) AND \
(publicationDate > $DATE)"];
[requestTemplate setPredicate: predicateTemplate];
[model setFetchRequestTemplate: requestTemplate
forName: @"PublicationForAuthorSinceDate"];
[requestTemplate release];
通路請求模闆
你可以用"Accessing and Using a Managed Object Model at Runtime"裡介紹的代碼片段來擷取并使用請求模闆。替換字典必須包含和模闆中定義的變量對應的鍵。如果你想測試null值,必須使用NSNull對 象——參考"Using Predicates"。(注:這裡的替換字典很好了解,之前的模闆中用到了諸如$FIRST_NAME, $LAST_NAME, $DATE這些東西,就相當于我們在模闆中建立好的“變量”,我們需要把一個模闆“具體化”,就用替換字典,将裡面的變量對應一個值,這裡看代碼就明白 了。)
NSDictionary *substitutionDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
@"Fiona", @"FIRST_NAME", @"Verde", @"LAST_NAME",
[NSDate dateWithTimeIntervalSinceNow: -31356000], @"DATE", nil]; //這裡的FIRST_NAME, LAST_NAME, DATE和我們之前模闆裡的$FIRST_NAME, $LAST_NAME和$DATE對應
NSFetchRequest *fetchRequest =
[model fetchRequestFromTemplateWithName: @"PublicationForAuthorSinceDate"
substitutionVariables: substitutionDictionary]; //從之前的model中拿出請求模闆,然後設定替換字典
NSArray *results =
[aManagedObjectContext executeFetchRequest: fetchRequest error: &error];
要是模闆裡不包含可替換的變量,你要麼
1. 使用fetchRequestFromTemplateWithName: substitutionVariables: 方法,傳遞nil給第二個參數
或者:
2. 使用fetchRequestTemplateForName: 并将結果copy。這個方法不需要傳遞“替換變量”這個參數,但是如果你要用傳回值本身,将會有異常抛出(無法在不可變的模型中修改命名的資料請 求"Can't modify named fetch request in an immutable model")。
本地化被管理對象模型
你可以對模型的大部分内容做本地化處理,包括實體和屬性名,還有錯誤資訊。要明白,“轉成你自己的語言”也是本地化的一部分。 即使你不打算提供外語版本, 顯示“自然語言”的出錯提示資訊也會有更好的使用者體驗。例如:“First Name is a required property”就比"firstName is a required property"更好。(後面的這個更像是開發者用的log,顯示的是變量名,這裡不太明顯)。
要想對模型進行本地化處理,需要提供一個本地化字典,模式如下:
Table 1 針對被管理對象模型的本地化字典鍵值對應關系:
- Key Value Note
- "Entity/NonLocalizedEntityName" "LocalizedEntityName"
- "Property/NonLocalizedPropertyName/Entity/EntityName" "LocalizedPropertyName" 1
- "Property/NonLocalizedPropertyName" "LocalizedPropertyName"
- "ErrorString/NonLocalizedErrorString" "LocalizedErrorString"
備注:(1)在不同實體中的屬性,擁有相同的原始名稱,但需要不同的本地化名稱,适用于該格式。
我們可以通過localizationDictionary方法來通路本地化字典。注意:在Mac OS X 10.4上,這個方法可能傳回nil,除了Core Data為了某些特定目的(如報告本地化的錯誤描述)延遲加載本地化字典。
字元串檔案
處理模型的本地化最簡單的方法就是建立對應的字元串檔案——字元串檔案名和模型檔案名一直,但是字尾名用.strings。(例如,模型檔案名為 MyDocument.xcdatamodel,對應的字元串檔案名就為MyDocumentModel.strings;如果模型檔案已經包含了 Model字尾,你必須再附加一個Model,是以,如果模型檔案名為JimsModel.xcdatamodel對應的字元串檔案名為 JimsModelModel.strings)。字元串檔案格式和标準字元串檔案類似(請參考"Localizing String Resources"),但是對應的鍵值要遵循Table-1中的規則。
一個模型的字元串檔案執行個體:
- "Entity/Emp" = "Employee";
- "Property/firstName" = "First Name";
- "Property/lastName" = "Last Name";
- "Property/salary" = "Salary";
更詳細的示例請參考"NSPersistentDocument Core Data Tutorial"。
代碼實作設定本地化字典
你可以在運作時設定本地化字典,适用NSManagedObjectModel的setLocalizationDictionary:方法即可。你必須 建立一個符合Table-1格式的字典,并把它和模型關聯。必須保證在模型被使用(擷取或建立被管理對象)之前做這些工作,因為再使用後模型就不可編輯 了。 Listing 3示範了建立包含本地化字典的被管理對象模型。實體名稱叫“Run”,它有兩個屬性: "date"和"processID",分别是date和integer類型。process ID的值不能為負。
Listing 3 通過代碼建立被管理對象模型
- NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] init];
- NSEntityDescription *runEntity = [[NSEntityDescription alloc] init];
- [runEntity setName:@"Run"];
- [runEntity setManagedObjectClassName:@"Run"];
- [mom setEntities:[NSArray arrayWithObject:runEntity]];
- [runEntity release];
- NSMutableArray *runProperties = [NSMutableArray array];
- NSAttributeDescription *dateAttribute = [[NSAttributeDescription alloc] init];
- [runProperties addObject:dateAttribute];
- [dateAttribute release];
- [dateAttribute setName:@"date"];
- [dateAttribute setAttributeType:NSDateAttributeType];
- [dateAttribute setOptional:NO];
- NSAttributeDescription *idAttribute= [[NSAttributeDescription alloc] init];
- [runProperties addObject:idAttribute];
- [idAttribute release];
- [idAttribute setName:@"processID"];
- [idAttribute setAttributeType:NSInteger32AttributeType];
- [idAttribute setOptional:NO];
- [idAttribute setDefaultValue:[NSNumber numberWithInt:0]];
- NSPredicate *validationPredicate = [NSPredicate predicateWithFormat:@"SELF >= 0"];
- NSString *validationWarning = @"Process ID < 0";
- [idAttribute setValidationPredicates:[NSArray arrayWithObject:validationPredicate]
- withValidationWarnings:[NSArray arrayWithObject:validationWarning]];
- [runEntity setProperties:runProperties];
- NSMutableDictionary *localizationDictionary = [NSMutableDictionary dictionary];
- [localizationDictionary setObject:@"Process ID"
- forKey:@"Property/processID/Entity/Run"];
- [localizationDictionary setObject:@"Date"
- forKey:@"Property/date/Entity/Run"];
- [localizationDictionary setObject:@"Process ID must not be less than 0"
- forKey:@"ErrorString/Process ID < 0"];
- [mom setLocalizationDictionary:localizationDictionary];
這段代碼寫的比較多,這裡不再解釋了。本地化字典的代碼在最後。建立一個符合格式的localizationDictionary,然後用model調用即可。
資料持久化總結
1 //1.沙盒:/Users/nono/Library/Application Support/iPhone Simulator/5.1/Applications/2D135859-1E80-4754-B36D-34A53C521DE3
2 /**
3 // 1、擷取程式的Home目錄
4 NSString *home = NSHomeDirectory();
5 NSLog(@"應用程式目錄:%@", home);
6
7 // 2、擷取Documents目錄
8 // NSUserDomainMask 代表從使用者檔案夾下找
9 // YES 代表展開路徑中的波浪字元“~”
10 NSArray *documents = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
11 // 隻有一個比對目錄,是以這個集合裡面隻有一個元素
12 NSString *doc = documents[0];
13 NSLog(@"文檔目錄:%@", doc);
14
15 // 使用字元串拼接的方式擷取目錄名
16 // 不建議采用,因為新版本的作業系統可能會修改目錄名
17 NSString *doc2 = [home stringByAppendingPathComponent:@"Documents"];
18 NSLog(@"拼接文檔目錄:%@", doc2);
19
20 // 3、擷取Cache目錄
21 NSArray *caches = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
22 NSString *cache = caches[0];
23 NSLog(@"緩存目錄:%@", cache);
24
25 // 4、擷取Tmp目錄
26 NSString *tmpDir = NSTemporaryDirectory();
27 NSLog(@"臨時目錄:%@", tmpDir);
28
29 //5,NSUserDefaults
30 在A類中:
31 NSUserDefaults * userDefault = [NSUserDefaultsstandardUserDefaults];
32 [userDefault setBool:YES forKey:@"isonline"];
33 [userDefault setInteger:111 forKey:@"online_user_number"];
34 ...等等。參見NSUserDefault用法。
35
36 在B中:擷取A傳遞過來的參數
37 NSUserDefault * userDefault = [NSUserDefault standardUserDefault];
38 BOOL isonline = [userDefault boolForKey:@"isonline"];
39 NSInteger onlineUserNumber = [userDefault integerForKey:@"online_user_number"];
40
41 */
42
43 //2,屬性清單
44 /**
45 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
46 NSString *docPath = [paths objectAtIndex:0];
47 NSString *myFile = [docPath stringByAppendingPathComponent:@"my.list"];
48 //讀取檔案
49 NSArray *array = [[NSArray alloc] initWithContentsOfFile:myFile];
50 //操作完若修改了資料則,寫入檔案
51 [array writeToFile:myFile atomically:YES];
52 */
53
54
55 //3.對象歸檔
56 /**
57 #pragma NSCoding協定實作實作
58 - (void)encodeWithCoder:(NSCoder *)aCoder
59 { //encoder
60 [aCoder encodeObject:stringAforKey:@"1"];
61 [aCoder encodeObject:stringBforKey:@"2"];
62 }
63 - (id)initWithCoder:(NSCoder *)aDecoder
64 {
65 //decoder
66 if (self = [superinit]) {
67 stringA = [[aDecoder decodeObjectForKey:@"1"] retain];
68 stringB = [[aDecoder decodeObjectForKey:@"2"] retain];
69 }
70 returnself;
71 }
72
73 #pragma NSCopying協定實作
74 - (id)copyWithZone:(NSZone *)zone
75 {
76 TestObj *copy = [[[selfclass] allocWithZone:zone] init];
77 copy.stringA = [[self.stringAcopyWithZone:zone] autorelease];
78 copy.stringB = [[self.stringBcopyWithZone:zone] autorelease];
79 return copy;
80 }
81
82 //讀取歸檔檔案
83 NSData *data = [[NSMutableDataalloc] initWithContentsOfFile:myFile];
84 NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiveralloc] initForReadingWithData:data];
85 TestObj * test = [unarchiver decodeObjectForKey:@"data"];
86 [unarchiver finishDecoding];
87 [data release];
88 [unarchiver release];
89
90 //寫入歸檔檔案
91 NSMutableData *data1 = [[NSMutableDataalloc] init];
92 NSKeyedArchiver *archiver = [[NSKeyedArchiveralloc] initForWritingWithMutableData:data1];
93 [archiver encodeObject:test forKey:@"data"];
94 [archiver finishEncoding];
95 [data writeToFile:myFile atomically:YES];
96 [data1 release];
97 [archiver release];
98 [test release];
99 */
100
101
102 //4.資料庫存儲(SQLite3)
103 /**
104 //資料庫操作
105 sqlite3 *database;
106 // const NSString * dbname = @"mydb"
107 int result;
108 //打開一個指定路徑的現有的資料庫,如果沒有則會建立一個db庫
109 result = sqlite3_open([myFile UTF8String], &database);
110 if (result != SQLITE_OK) {
111 sqlite3_close(database);
112 }
113
114 //建立一個db表
115 char *errorMsg;
116 NSString *sql_create_table = @"CREATE TABLE IF NOT EXISTS NONOTABLE 省略~~~~~~~~~~~~~";
117 int result1 ;
118 //sqlite_exec用了針對sqlite3運作任何不要傳回資料的指令,它用于執行更新,插入和删除。簡單來說,這個方法執行的都是一些無需傳回資料(雖然我們可能擷取一個狀态值。)。
119 result1 = sqlite3_exec(database, [sql_create_table UTF8String], NULL, NULL, &errorMsg);
120
121 //檢索查詢操作
122 int result2 ;
123 sqlite3_stmt *statment;
124 NSString *sql_selected = @"查詢語句";
125 result2 = sqlite3_prepare_v2(database, [sql_selected UTF8String], -1, &statment, nil);
126 if(result2 == SQLITE_OK){
127 //單步操作
128 while (sqlite3_step(statment) == SQLITE_ROW) {
129 int row = sqlite3_column_int(statment, 0);
130 char * rpwData = sqlite3_column_text(statment, 1);
131 }
132 sqlite3_finalize(statment);
133 }
134
135
136 //綁定變量,既就是插入操作的一種變種,比如我麼那上面提到sqlite_exec可以執行插入操作,插入内容直接是寫在sql字竄裡,但是考慮到字竄涉及到無效的符号以及會一些嚴重的注入漏洞(比如以前聽過的引号符号)。
137 NSString *sql_bind = @"insert into foo value(?,?)";
138 result2 = sqlite3_prepare_v2(database, [sql_selected UTF8String], -1, &statment, nil);
139 if(result2 == SQLITE_OK){
140 sqlite3_bind_int(statment, 1, 235);
141 sqlite3_bind_text(statment, 2, "test", -1, nil);
142 sqlite3_finalize(statment);
143 }
144 if (sqlite3_step(statment) != SQLITE_DONE)
145 NSLog(@"error");
146 sqlite3_finalize(statment);
147
148
149 sqlite3_close(database);
150
151 */
152
153
154 //5.蘋果公司提供的持久性工具Core Data
155 /*
156 一般需要定義以下Core Data的三個必備
157 NSPersistentStoreCoordinator *persistentStoreCoordinator;
158 NSManagedObjectModel *managedObjectModel;
159 NSManagedObjectContext *managedObjectContext;
160 以及使用時需要用到de
161 NSFetchedResultsController *fetchedResultsController;
162 */
163
164
165
166
167
168
169 //其實對于ios資料存儲,最常用和主要要掌握的就是屬性清單和資料庫,因為兩個是出鏡率比較高的。其他可能在資料存明顯展現出儲優勢時,我們會去考慮用另外兩種機制。基礎的來說,必須掌握屬性清單和sqlite的操作存儲。
1
2 //******************** 5.1 NSUserDefault和對象歸檔
3 func useNSUserDefault()
4 {
5 //通過單利來建立一個NSUserDefaults對象,全局變量NSUserDefault,可在整個項目傳遞變量
6 var userDefault:NSUserDefaults = NSUserDefaults.standardUserDefaults()
7
8 //通過init方法建立
9 var userDefault1:NSUserDefaults = NSUserDefaults(suiteName: "SwiftClass")!
10
11 //擷取userDefault單利下所有的值
12 println(userDefault.dictionaryRepresentation())
13
14 //判斷NSUserDefaults的“appMessage”key 在dictionaryRepresentation中是否存在,如果不存在就設定“appMessage”值為This is app message。
15 if(userDefault.objectForKey("message") == nil){
16 userDefault.setObject("This_is_my_default_message", forKey: "message")
17 }
18
19 //如果想單獨看某個key的設定,例如:
20 var dic = userDefault.dictionaryRepresentation()
21 var object_one:AnyObject? = (dic as NSDictionary).objectForKey("AppleKeyboards")
22 // //或者
23 // var object_one:AnyObject? = dic["AppleKeyboards"]
24
25 if let oValue: AnyObject! = object_one {
26 println(oValue)
27 }
28
29
30 //Int類型
31 //設定
32 userDefault.setInteger(123456, forKey: "Int")
33 //讀取
34 var intValue = userDefault.integerForKey("Int")
35 println(intValue)
36
37 //Float類型
38 //設定
39 userDefault.setFloat(3.2, forKey: "Float")
40 //讀取
41 var floatValue = userDefault.floatForKey("Float")
42 println(floatValue)
43
44 //Double類
45 //設定
46 userDefault.setDouble(5.6890, forKey: "Double")
47 //讀取
48 var doubleValue = userDefault.doubleForKey("Double")
49 println(doubleValue)
50
51 //Bool類型
52 //設定
53 userDefault.setBool(true, forKey: "Bool")
54 //讀取
55 var boolValue = userDefault.boolForKey("Bool")
56 println(boolValue)
57
58 //NSURL類型
59 //設定
60 userDefault.setURL(NSURL(string: "http://www.iphonetrain.com")!, forKey: "NSURL")
61 //讀取
62 var urlValue = userDefault.URLForKey("NSURL")
63 println(urlValue)
64
65
66
67 //儲存NSDate資料
68 //将對象轉換成NSData流
69 var imageData:NSData = NSKeyedArchiver.archivedDataWithRootObject(UIImage(named: "SwiftClassWeiXin.png")!)
70
71 //存儲NSData對象
72 userDefault.setObject(imageData, forKey: "imageData")
73
74 //讀取資料
75 //擷取NSData
76 var objData:AnyObject? = userDefault.objectForKey("imageData")
77
78 //還原對象
79 // var myImage:AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(objData as NSData)
80
81
82 //2015年5月2号修改
83 var myImage:AnyObject? = NSKeyedUnarchiver.unarchiveObjectWithData(objData as! NSData)
84
85
86
87 //初始一個UIImage對象
88 println(myImage)
89
90
91
92
93 //自定義的類實作存取需要通過NSData做載體
94
95 //建立AppsModel的執行個體
96 var model = AppsModel(imageName: "appIcon2.png", app_Name: "租房點評", app_Description: "租房被騙?現在開始,你來改變這一切!《租房點評》為你而備,租房無憂!")
97
98 //執行個體對象轉換成NSData
99 var modelData:NSData = NSKeyedArchiver.archivedDataWithRootObject(model)
100
101 //存儲NSData對象
102 userDefault.setObject(modelData, forKey: "myAppModel")
103
104
105
106
107
108
109
110
111 //儲存NSString,
112 userDefault.setValue("1_NSString", forKey: "NSString")
113 userDefault.setObject("1_NSString1", forKey: "NSString1")
114
115 //儲存NSNumber,
116 var number:NSNumber = NSNumber(int: 32)
117 userDefault.setValue(number, forKey: "number")
118 userDefault.setObject(number, forKey: "number1")
119
120 //儲存NSArray
121 var array1:NSArray = NSArray(array:["22222","33333"])
122 userDefault.setValue(array1, forKey: "array")
123 userDefault.setObject(array1, forKey: "array1")
124
125 //儲存NSDictionary
126 var dictionary:NSDictionary = NSDictionary(dictionary: ["1":"1111"])
127 userDefault.setValue(dictionary, forKey: "dictionary")
128 userDefault.setObject(dictionary, forKey: "dictionary1")
129
130
131
132
133 var value:AnyObject? = userDefault.valueForKey("dictionary")
134 println(value)
135
136 value = userDefault.objectForKey("dictionary1")
137 println(value)
138
139
140
141 //-------- 删除所有的值
142 var ar:NSDictionary = userDefault.dictionaryRepresentation()
143
144 for key in ar.allKeys {
145
146 // userDefault.removeObjectForKey(key as String)
147
148 //2015年5月2号修改
149 userDefault.removeObjectForKey(key as! String)
150
151 userDefault.synchronize()
152 }
153 }
Write the code ,change the world!