iOS開發系列—Objective-C之Foundation框架

KenshinCui發表於2014-08-01

概述

我們前面的章節中就一直新建Cocoa Class,那麼Cocoa到底是什麼,它和我們前面以及後面要講的內容到底有什麼關係呢?Objective-C開發中經常用到NSObject,那麼這個物件到底是誰?它為什麼又出現在Objective-C中間呢?今天我們將揭開這層面紗,重點分析在IOS開發中一個重要的框架Foundation,今天的主要內容有:

  1. Foundation概述
  2. 常用結構體
  3. 日期
  4. 字串
  5. 陣列
  6. 字典
  7. 裝箱和拆箱
  8. 反射
  9. 拷貝
  10. 檔案操作
  11. 歸檔

Foundation概述

為什麼前面說的內容中新建一個類的時候我們都是選擇Cocoa Class呢?Cocoa是什麼呢?

Cocoa不是一種程式語言(它可以執行多種程式語言),它也不是一個開發工具(通過命令列我們仍然可以開發Cocoa程式),它是建立Mac OS X和IOS程式的原生物件導向API,為這兩者應用提供了程式設計環境。

我們通常稱為“Cocoa框架”,事實上Cocoa本身是一個框架的集合,它包含了眾多子框架,其中最重要的要數“Foundation”和“UIKit”。前者是框架的基礎,和介面無關,其中包含了大量常用的API;後者是基礎的UI類庫,以後我們在IOS開發中會經常用到。這兩個框架在系統中的位置如下圖:

Cocoa

其實所有的Mac OS X和IOS程式都是由大量的物件構成,而這些物件的根物件都是NSObject,NSObject就處在Foundation框架之中,具體的類結構如下:

Foundation1

Foundation2

Foundation3

通常我們會將他們分為幾類:

  1. 值物件
  2. 集合
  3. 作業系統服務:檔案系統、URL、程式通訊
  4. 通知
  5. 歸檔和序列化
  6. 表示式和條件判斷
  7. Objective-C語言服務

UIKit主要用於介面構架,這裡我們不妨也看一下它的類結構:

UIKit

常用結構體

在Foundation中定義了很多常用結構體型別來簡化我們的日常開發,這些結構體完全採用Objective-C定義,和我們自己定義的結構體沒有任何區別,之所以由框架為我們提供完全是為了簡化我們的開發。常用的結構體有NSRange、NSPoint、NSSize、NSRect等

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

/*NSRange表示一個範圍*/
void test1(){
    NSRange rg={3,5};//第一引數是起始位置第二個引數是長度
    //NSRange rg;
    //rg.location=3;
    //rg.length=5;
    //NSRange rg={.location=3,.length=5};
    //常用下面的方式定義 NSRange rg2=NSMakeRange(3,5);//使用NSMakeRange定義一個NSRange 
    //列印NSRange可以使用Foundation中方法 NSLog(@"rg2 is %@", NSStringFromRange(rg2));//注意不能直接NSLog(@"rg2 is %@", rg2),因為rg2不是物件(準確的說%@是指標)而是結構體
}
/*NSPoint表示一個點*/
void test2(){
    NSPoint p=NSMakePoint(10, 15);//NSPoint其實就是CGPoint
    //這種方式比較常見 NSPoint p2=CGPointMake(10, 15);
    NSLog(NSStringFromPoint(p2));
}
/*NSSize表示大小*/
void test3(){
    NSSize s=NSMakeSize(10, 15);//NSSize其實就是CGSize
    //這種方式比較常見 CGSize s2=CGSizeMake(10, 15);
    NSLog(NSStringFromSize(s2));
}
/*NSRect表示一個矩形*/
void test4(){
    NSRect r=NSMakeRect(10, 5, 100, 200);//NSRect其實就是CGRect
    //這種方式比較常見 NSRect r2=CGRectMake(10, 5, 100, 200);
    NSLog(NSStringFromRect(r2));
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test1();
        test2();
        test3();
        test4();
    } return 0;
}
可以看到對於常用結構體在Foundation框架中都有一個對應的make方法進行建立,這也是我們日後比較常用的操作;而且與之對應的還都有一個NSStringFromXX方法來進行字串轉換,方便我們除錯。上面也提到NSSize其實就是CGSize,NSRect其實就是CGRect,我們可以通過檢視程式碼進行確認,例如NSSize定義:

NSSize

繼續檢視CGSize的程式碼:

CGSize

日期

接下來熟悉一下Foundation框架中日期的操作

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


int main(int argc, const char * argv[]) {
    
    NSDate *date1=[NSDate date];//獲得當前日期
    NSLog(@"%@",date1); //結果:2014-07-16 07:25:28 +0000
    
    NSDate *date2=[NSDate dateWithTimeIntervalSinceNow:100];//在當前日期的基礎上加上100秒,注意在ObjC中多數時間單位都是秒
    NSLog(@"%@",date2); //結果:2014-07-16 07:27:08 +0000
    
    NSDate *date3=[NSDate distantFuture];//隨機獲取一個將來的日期
    NSLog(@"%@",date3); //結果:4001-01-01 00:00:00 +0000
    
    NSTimeInterval time=[date2 timeIntervalSinceDate:date1];//日期之差,返回單位為秒
    NSLog(@"%f",time); //結果:100.008833
    
    NSDate *date5=[date1 earlierDate:date3];//返回比較早的日期
    NSLog(@"%@",date5); //結果:2014-07-16 07:25:28 +0000
    
    //日期格式化
    NSDateFormatter *formater1=[[NSDateFormatter alloc]init];
    formater1.dateFormat=@"yy-MM-dd HH:mm:ss";
    NSString *datestr1=[formater1 stringFromDate:date1];
    NSLog(@"%@",datestr1); //結果:14-07-16 15:25:28
    //字串轉化為日期
    NSDate *date6=[formater1 dateFromString:@"14-02-14 11:07:16"];
    NSLog(@"%@",date6); //結果:2014-02-14 03:07:16 +0000

    return 0;
}

字串

不可變字串

在ObjC中字串操作要比在C語言中簡單的多,在下面的例子中你將看到字串的初始化、大小寫轉化、字尾字首判斷、字串比較、字串擷取、字串轉換等,通過下面的例子我們基本可以掌握常用的字串操作(注意這些內容雖然基本,但卻是十分常用的操作,需要牢記):

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


/**字串操作*/
void test1(){
    char *str1="C string";//這是C語言建立的字串
    NSString *str2=@"OC string";//ObjC字串需要加@,並且這種方式建立的物件不需要自己釋放記憶體

    //下面的建立方法都應該釋放記憶體
    NSString *str3=[[NSString alloc] init];
    str3=@"OC string";
    NSString *str4=[[NSString alloc] initWithString:@"Objective-C string"];
    NSString *str5=[[NSString alloc] initWithFormat:@"age is %i,name is %.2f",19,1.72f];
    NSString *str6=[[NSString alloc] initWithUTF8String:"C string"];//C語言的字串轉換為ObjC字串

    //以上方法都有對應靜態方法(一般以string開頭),不需要管理記憶體(系統靜態方法一般都是自動釋放)
    NSString *str7=[NSString stringWithString:@"Objective-C string"];
}
void test2(){
    NSLog(@"\"Hello world!\" to upper is %@",[@"Hello world!" uppercaseString]);
    //結果:"Hello world!" to upper is HELLO WORLD!
    NSLog(@"\"Hello world!\" to lowwer is %@",[@"Hello world!" lowercaseString]);
    //結果:"Hello world!" to lowwer is hello world!
     
    //首字母大寫,其他字母小寫
    NSLog(@"\"Hello world!\" to capitalize is %@",[@"Hello world!" capitalizedString]);
    //結果:"Hello world!" to capitalize is Hello World!
     
    BOOL result= [@"abc" isEqualToString:@"aBc"];
    NSLog(@"%i",result);
    //結果:0
    NSComparisonResult result2= [@"abc" compare:@"aBc"];//如果是[@"abc" caseInsensitiveCompare:@"aBc"]則忽略大小寫比較
    if(result2==NSOrderedAscending){
        NSLog(@"left<right.");
    }else if(result2==NSOrderedDescending){
        NSLog(@"left>right.");
    }else if(result2==NSOrderedSame){
        NSLog(@"left=right.");
    }
    //結果:left>right.
}
void test3(){
    NSLog(@"has prefix ab? %i",[@"abcdef" hasPrefix:@"ab"]);
    //結果:has prefix ab? 1
    NSLog(@"has suffix ab? %i",[@"abcdef" hasSuffix:@"ef"]);
    //結果:has suffix ab? 1
    NSRange range=[@"abcdefabcdef" rangeOfString:@"cde"];//注意如果遇到cde則不再往後面搜尋,如果從後面搜尋或其他搜尋方式可以設定第二個options引數
    if(range.location==NSNotFound){
        NSLog(@"not found.");
    }else{
        NSLog(@"range is %@",NSStringFromRange(range));
    }
    //結果:range is {2, 3}
}
//字串分割
void test4(){
    NSLog(@"%@",[@"abcdef" substringFromIndex:3]);//從第三個索引開始(包括第三個索引對應的字元)擷取到最後一位
    //結果:def
    NSLog(@"%@",[@"abcdef" substringToIndex:3]);////從0開始擷取到第三個索引(不包括第三個索引對應的字元)
    //結果:abc
    NSLog(@"%@",[@"abcdef" substringWithRange:NSMakeRange(2, 3)]);
    //結果:cde
    NSString *str1=@"12.abcd.3a";
    NSArray *array1=[str1 componentsSeparatedByString:@"."];//字串分割
    NSLog(@"%@",array1);
     /*結果:
      (
         12,
         abcd,
         3a
      )
      */
 
}
//其他操作
void test5(){
    NSLog(@"%i",[@"12" intValue]);//型別轉換
    //結果:12
    NSLog(@"%zi",[@"hello world,世界你好!" length]);//字串長度注意不是位元組數
    //結果:17
    NSLog(@"%c",[@"abc" characterAtIndex:0]);//取出制定位置的字元
    //結果:a
    const char *s=[@"abc" UTF8String];//轉換為C語言字串
    NSLog(@"%s",s);
    //結果:abc
}

int main(int argc, const char * argv[]) {
    test1();
    test2();
    test3();
    test4();
    test5();
    return 0;
}

注意:上面程式碼註釋中提到的需要釋放記憶體指的是在MRC下的情況,當然本質上在ARC下也需要釋放,只是這部分程式碼編譯器會自動建立。

擴充套件--檔案操作

在ObjC中路徑、檔案讀寫等操作是利用字串來完成的,這裡通過幾個簡單的例子來演示(首先在桌面上新建一個test.txt檔案,裡面儲存的內容是”hello world,世界你好!”)

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


void test1(){
    //讀取檔案內容
    NSString *path=@"/Users/kenshincui/Desktop/test.txt";
    NSString *str1=[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    //注意上面也可以使用gb2312 gbk等,例如kCFStringEncodingGB_18030_2000,但是需要用CFStringConvertEncodingToNSStringEncoding轉換
    NSLog(@"str1 is %@",str1);
    //結果:str1 is hello world,世界你好!

    
    
    
    //上面我們看到了讀取檔案,但並沒有處理錯誤,當然在ObjC中可以@try @catch @finnally但通常我們並不那麼做
    //由於我們的test.txt中有中文,所以使用下面的編碼讀取會報錯,下面的程式碼演示了錯誤獲取的過程
    NSError *error;
    NSString *str2=[NSString stringWithContentsOfFile:path encoding:kCFStringEncodingGB_18030_2000 error:&error];//注意這句話中的error變數是**error,就是指標的指標那就是指標的地址,由於error就是一個指標此處也就是error的地址&error,具體原因見下面補充
    if(error){
        NSLog(@"read error ,the error is %@",error);
    }else{
        NSLog(@"read success,the file content is %@",str2);
    }
    //結果:read error ,the error is Error Domain=NSCocoaErrorDomain Code=261 "The file couldn’t be opened using the specified text encoding." UserInfo=0x100109620 {NSFilePath=/Users/kenshincui/Desktop/test.txt, NSStringEncoding=1586}

    
    
    
    //讀取檔案內容還有一種方式就是利用URl,它除了可以讀取本地檔案還可以讀取網路檔案
    //NSURL *url=[NSURL URLWithString:@"file:///Users/kenshincui/Desktop/test.txt"];
    NSURL *url=[NSURL URLWithString:@"http://www.apple.com"];
    NSString *str3=[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"str3 is %@",str3);
}
void test2(){
    //下面是檔案寫入
    NSString *path1=@"/Users/kenshincui/Desktop/test2.txt";
    NSError *error1;
    NSString *str11=@"hello world,世界你好!";
    [str11 writeToFile:path1 atomically:YES encoding:NSUTF8StringEncoding error:&error1];//automically代表一次性寫入,如果寫到中間出錯了最後就全部不寫入
    if(error1){
        NSLog(@"write fail,the error is %@",[error1 localizedDescription]);//呼叫localizedDescription是隻列印關鍵錯誤資訊
    }else{
        NSLog(@"write success!");
    }
    //結果:write success!
}
//路徑操作
void test3(){
    NSMutableArray *marray=[NSMutableArray array];//可變陣列
    [marray addObject:@"Users"];
    [marray addObject:@"KenshinCui"];
    [marray addObject:@"Desktop"];

    NSString *path=[NSString pathWithComponents:marray];
    NSLog(@"%@",path);//字串拼接成路徑
    //結果:Users/KenshinCui/Desktop

    NSLog(@"%@",[path pathComponents]);//路徑分割成陣列
    /*結果: 
     (
        Users,
        KenshinCui,
        Desktop
    )
    */

    NSLog(@"%i",[path isAbsolutePath]);//是否絕對路徑(其實就是看字串是否以“/”開頭)
    //結果:0
    NSLog(@"%@",[path lastPathComponent]);//取得最後一個目錄
    //結果:Desktop
    NSLog(@"%@",[path stringByDeletingLastPathComponent]);//刪除最後一個目錄,注意path本身是常量不會被修改,只是返回一個新字串
    //結果:Users/KenshinCui
    NSLog(@"%@",[path stringByAppendingPathComponent:@"Documents"]);//路徑拼接
    //結果:Users/KenshinCui/Desktop/Documents
}
 //副檔名操作
void test4(){
    NSString *path=@"Users/KenshinCui/Desktop/test.txt";
    NSLog(@"%@",[path pathExtension]);//取得副檔名,注意ObjC中副檔名不包括"."
    //結果:txt
    NSLog(@"%@",[path stringByDeletingPathExtension]);//刪除副檔名,注意包含"."
    //結果:Users/KenshinCui/Desktop/test
    NSLog(@"%@",[@"Users/KenshinCui/Desktop/test" stringByAppendingPathExtension:@"mp3"]);//新增副檔名
    //結果:Users/KenshinCui/Desktop/test.mp3
}

int main(int argc, const char * argv[]) {
    test1();
    test2();
    test3();
    test4();
    return 0;
}

注意:在上面的例子中我們用到了可變陣列,下面會專門介紹。

可變字串

我們知道在字串操作過程中我們經常希望改變原來的字串,當然這在C語言中實現比較複雜,但是ObjC為我們提供了新的可變字串類NSMutableString,它是NSString的子類。

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


int main(int argc, const char * argv[]) {
    
    /*可變字串,注意NSMutableString是NSString子類*/
    //注意雖然initWithCapacity分配字串大小,但是不是絕對的不可以超過此範圍,宣告此變數對效能有好處
    NSMutableString *str1= [[NSMutableString alloc] initWithCapacity:10];
    [str1 setString:@"hello"];//設定字串
    NSLog(@"%@",str1);
    //結果:hello

    [str1 appendString:@",world!"];//追加字串
    NSLog(@"%@",str1);
    //結果:hello,world!

    [str1 appendFormat:@"我的年齡是%i。dear,I love you.",18];
    NSLog(@"%@",str1);
    //結果:hello,world!我的年齡是18。dear,I love you.
    
    //替換字串
    NSRange range=[str1 rangeOfString:@"dear"];
    [str1 replaceCharactersInRange:range withString:@"Honey"];
    NSLog(@"%@",str1);
    //結果:hello,world!我的年齡是18。Honey,I love you.
    
    //插入字串
    [str1 insertString:@"My name is Kenshin." atIndex:12];
    NSLog(@"%@",str1);
    //結果:hello,world!My name is Kenshin.我的年齡是18。Honey,I love you.
    
    //刪除指定字串
    [str1 deleteCharactersInRange:[str1 rangeOfString:@"My name is Kenshin."]];//刪除指定範圍的字串
    NSLog(@"%@",str1);
    //結果:hello,world!我的年齡是18。Honey,I love you.
    
    return 0;
}

陣列

不可變陣列

下面將演示常用的陣列操作:初始化、陣列物件的方法執行、陣列元素的遍歷、在原有陣列基礎上產生新陣列、陣列排序等

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Person.h"


void test1(){
    //NSArray長度不可變所以初始化的時候就賦值,並且最後以nil結尾
    //此外需要注意NSArray不能存放C語言的基礎型別
    NSObject *obj=[[NSObject alloc]init];
    //NSArray *array1=[[NSArray alloc] initWithObjects:@"abc",obj,@"cde",@"opq", nil];
    NSArray *array1=[NSArray arrayWithObjects:@"abc",obj,@"cde",@"opq",@25, nil];
    NSLog(@"%zi",array1.count);//陣列長度,結果:5
    NSLog(@"%i",[array1 containsObject:@"cde"]);//是否包含某個物件,結果:1
    NSLog(@"%@",[array1 lastObject]);//最後一個物件,結果:25
    NSLog(@"%zi",[array1 indexOfObject:@"abc"]);//物件所在的位置:0
    
    Person *person1=[Person personWithName:@"Kenshin"];
    Person *person2=[Person personWithName:@"Kaoru"];
    Person *person3=[Person personWithName:@"Rosa"];
    NSArray *array2=[[NSArray alloc]initWithObjects:person1,person2,person3, nil];
    [array2 makeObjectsPerformSelector:@selector(showMessage:) withObject:@"Hello,world!"];//執行所有元素的showMessage方法,後面的引數最多隻能有一個
    /*結果:
     My name is Kenshin,the infomation is "Hello,world!".
     My name is Kaoru,the infomation is "Hello,world!".
     My name is Rosa,the infomation is "Hello,world!".
     */
}
//陣列的遍歷
void test2(){
    NSObject *obj=[[NSObject alloc]init];
    NSArray *array=[[NSArray alloc] initWithObjects:@"abc",obj,@"cde",@"opq",@25, nil];
    //方法1
    for(int i=0,len=array.count;i<len;++i){
        NSLog(@"method1:index %i is %@",i,[array objectAtIndex:i]);
    }
    /*結果:
     method1:index 0 is abc
     method1:index 1 is <NSObject: 0x100106de0>
     method1:index 2 is cde
     method1:index 3 is opq
     method1:index 4 is 25
     */
    
    
    //方法2
    for(id obj in array){
        NSLog(@"method2:index %zi is %@",[array indexOfObject:obj],obj);
    }
    /*結果:
     method2:index 0 is abc
     method2:index 1 is <NSObject: 0x100602f00>
     method2:index 2 is cde
     method2:index 3 is opq
     method2:index 4 is 25
     */
    
    
    //方法3,利用程式碼塊方法
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"method3:index %zi is %@",idx,obj);
        if(idx==2){//當idx=2時設定*stop為YES停止遍歷
            *stop=YES;
        }
    }];
    /*結果:
     method3:index 0 is abc
     method3:index 1 is <NSObject: 0x100106de0>
     method3:index 2 is cde
     */
    
    
    //方法4,利用迭代器
    //NSEnumerator *enumerator= [array objectEnumerator];//獲得一個迭代器
    NSEnumerator *enumerator=[array reverseObjectEnumerator];//獲取一個反向迭代器
    //NSLog(@"all:%@",[enumerator allObjects]);//獲取所有迭代物件,注意呼叫完此方法迭代器就遍歷完了,下面的nextObject就沒有值了
    id obj2=nil;
    while (obj2=[enumerator nextObject]) {
        NSLog(@"method4:%@",obj2);
    }
    /*結果:
     method4:25
     method4:opq
     method4:cde
     method4:<NSObject: 0x100106de0>
     method4:abc
     */
}
//陣列派生出新的陣列
void test3(){
    NSArray *array=[NSArray arrayWithObjects:@"1",@"2",@"3", nil];
    NSArray *array2=[array arrayByAddingObject:@"4"];//注意此時array並沒有變
    NSLog(@"%@",array2);
    /*結果:
     (
         1,
         2,
         3,
         4
     )
     */
    
    
    NSLog(@"%@",[array2 arrayByAddingObjectsFromArray:[NSArray arrayWithObjects:@"5",@"6", nil]]);//追加形成新的陣列
    /*結果:
     (
         1,
         2,
         3,
         4,
         5,
         6
     )
     */
    
    
    NSLog(@"%@",[array2 subarrayWithRange:NSMakeRange(1, 3)]);//根據一定範圍取得生成一個新的陣列
    /*結果:
     (
         2,
         3,
         4
     )
     */
    
    
    NSLog(@"%@",[array componentsJoinedByString:@","]);//陣列連線,形成一個字串
    //結果:1,2,3
    
    //讀寫檔案
    NSString *path=@"/Users/KenshinCui/Desktop/array.xml";
    [array writeToFile:path atomically:YES];
    NSArray *array3=[NSArray arrayWithContentsOfFile:path];
    NSLog(@"%@",array3);
    /*結果:
     (
         1,
         2,
         3
     )
     */
}
//陣列排序
void test4(){
    //方法1,使用自帶的比較器
    NSArray *array=[NSArray arrayWithObjects:@"3",@"1",@"2", nil];
    NSArray *array2= [array sortedArrayUsingSelector:@selector(compare:)];
    NSLog(@"%@",array2);
    /*結果:
     (
         1,
         2,
         3
     )
     */
    
    
    //方法2,自己定義比較器
    Person *person1=[Person personWithName:@"Kenshin"];
    Person *person2=[Person personWithName:@"Kaoru"];
    Person *person3=[Person personWithName:@"Rosa"];
    NSArray *array3=[NSArray arrayWithObjects:person1,person2,person3, nil];
    NSArray *array4=[array3 sortedArrayUsingSelector:@selector(comparePerson:)];
    NSLog(@"%@",array4);
    /*結果:
     (
         "name=Kaoru",
         "name=Kenshin",
         "name=Rosa"
     )
     */
    
    
    //方法3使用程式碼塊
    NSArray *array5=[array3 sortedArrayUsingComparator:^NSComparisonResult(Person *obj1, Person *obj2) {
        return [obj2.name compare:obj1.name];//降序
    }];
    NSLog(@"%@",array5);
    /*結果:
     (
         "name=Rosa",
         "name=Kenshin",
         "name=Kaoru"
     )
     */
    
    
    //方法4 通過描述器定義排序規則
    Person *person4=[Person personWithName:@"Jack"];
    Person *person5=[Person personWithName:@"Jerry"];
    Person *person6=[Person personWithName:@"Tom"];
    Person *person7=[Person personWithName:@"Terry"];
    NSArray *array6=[NSArray arrayWithObjects:person4,person5,person6,person7, nil];
    //定義一個排序描述
    NSSortDescriptor *personName=[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
    NSSortDescriptor *accountBalance=[NSSortDescriptor sortDescriptorWithKey:@"account.balance" ascending:YES];
    NSArray *des=[NSArray arrayWithObjects:personName,accountBalance, nil];//先按照person的name排序再按照account的balance排序
    NSArray *array7=[array6 sortedArrayUsingDescriptors:des];
    NSLog(@"%@",array7);
    /*結果:
     (
         "name=Jack",
         "name=Jerry",
         "name=Terry",
         "name=Tom"
     )
     */
}

int main(int argc, const char * argv[]) {
    test1();
    test2();
    test3();
    test4();
    return 0;
}

需要注意幾點:

  • NSArray中只能存放物件,不能存放基本資料型別,通常我們可以通過在基本資料型別前加@進行轉換;
  • 陣列中的元素後面必須加nil以表示資料結束;
  • makeObjectsPerformSelector執行陣列中物件的方法,其引數最多隻能有一個;
  • 上面陣列操作中無論是陣列的追加、刪除、擷取都沒有改變原來的陣列,只是產生了新的陣列而已;
  • 物件的比較除了使用系統自帶的方法,我們可以通過自定義比較器的方法來實現;

可變陣列

下面看一下可變陣列的內容:

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Person.h"


void test1(){
    Person *person1=[Person personWithName:@"Kenshin"];
    Person *person2=[Person personWithName:@"Kaoru"];
    Person *person3=[Person personWithName:@"Rosa"];
    NSMutableArray *array1=[NSMutableArray arrayWithObjects:person1,person2,person3, nil];
    NSLog(@"%@",array1);
    /*結果:
     (
         "name=Kenshin",
         "name=Kaoru",
         "name=Rosa"
     )
     */
    
    Person *person4=[Person personWithName:@"Jack"];//此時person4的retainCount為1
    [array1 addObject:person4];//新增一個元素,此時person4的retainCount為2
    NSLog(@"%@",array1);
    /*結果:
     (
         "name=Kenshin",
         "name=Kaoru",
         "name=Rosa",
         "name=Jack"
     )
     */
    
    [array1 removeObject:person3];//刪除一個元素
    NSLog(@"%@",array1);
    /*結果:
     (
         "name=Kenshin",
         "name=Kaoru",
         "name=Jack"
     )
     */
    
    [array1 removeLastObject];//刪除最後一個元素,//此時person4的retainCount為1
    NSLog(@"%@",array1);
    /*結果:
     (
         "name=Kenshin",
         "name=Kaoru"
     )
     */
    
    [array1 removeAllObjects];//刪除所以元素
    
    //注意當往陣列中新增一個元素時會retain因此計數器+1,當從陣列中移除一個元素時會release因此計數器-1
    //當NSMutalbeArray物件release的時候會依次呼叫每一個物件的release
}
void test2(){
    NSMutableArray *array1=[NSMutableArray arrayWithObjects:@"1",@"3",@"2", nil];
    NSLog(@"%@",array1);
    /*結果:
     (
         1,
         3,
         2
     )
     */
    
    NSArray *array2= [array1 sortedArrayUsingSelector:@selector(compare:)];//注意這個方法沒有修改array1
    NSLog(@"%@",array1);
    /*結果:
     (
         1,
         3,
         2
     )
     */
    
    NSLog(@"%@",array2);
    /*結果:
     (
         1,
         2,
         3
     )
     */
    [array1 sortUsingSelector:@selector(compare:)];//這個方法會修改array1
    NSLog(@"%@",array1);
    /*結果:
     (
         1,
         2,
         3
     )
     */
    
}

int main(int argc, const char * argv[]) {
    
    test1();
    
    test2();
    
    return 0;
}
  • 可變陣列中的元素後面必須加nil以表示資料結束;
  • 往一個可變陣列中新增一個物件,此時這個物件的引用計數器會加1,當這個物件從可變陣列中移除其引用計數器減1。同時當整個陣列銷燬之後會依次呼叫每個物件的releaes方法。
  • 在不可變陣列中無論對陣列怎麼排序,原來的陣列順序都不會改變,但是在可變陣列中如果使用sortUsingSelector:排序原來的陣列順序就發生了變化。

 

字典

字典在我們日常開發中也是比較常用的,通過下面的程式碼我們看一下在ObjC中的字典的常用操作:初始化、遍歷、排序

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


void test1(){
    NSDictionary *dic1=[NSDictionary dictionaryWithObject:@"1" forKey:@"a"];
    NSLog(@"%@",dic1);
    /*結果:
     {
        a = 1;
     }
     */
    
    //常用的方式
    NSDictionary *dic2=[NSDictionary dictionaryWithObjectsAndKeys:
                        @"1",@"a",
                        @"2",@"b",
                        @"3",@"c",
                        nil];
    NSLog(@"%@",dic2);
    /*結果:
     {
         a = 1;
         b = 2;
         c = 3;
     }
     */
    
    
    NSDictionary *dic3=[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@"1",@"2", nil] forKeys:[NSArray arrayWithObjects:@"a",@"b", nil]];
    NSLog(@"%@",dic3);
    /*結果:
     {
         a = 1;
         b = 2;
     }
     */
    
    
    //更簡單的方式
    NSDictionary *dic4=@{@"1":@"a",@"2":@"b",@"3":@"c"};
    NSLog(@"%@",dic4);
    /*結果:
     {
         1 = a;
         2 = b;
         3 = c;
     }
     */
}
void test2(){
    NSDictionary *dic1=[NSDictionary dictionaryWithObjectsAndKeys:
                        @"1",@"a",
                        @"2",@"b",
                        @"3",@"c",
                        @"2",@"d",
                        nil];
    NSLog(@"%zi",[dic1 count]); //結果:4
    NSLog(@"%@",[dic1 valueForKey:@"b"]);//根據鍵取得值,結果:2
    NSLog(@"%@",dic1[@"b"]);//還可以這樣讀取,結果:2
    NSLog(@"%@,%@",[dic1 allKeys],[dic1 allValues]);
    /*結果:
     (
         d,
         b,
         c,
         a
     ),(
         2,
         2,
         3,
         1
     )

     */
    
    NSLog(@"%@",[dic1 objectsForKeys:[NSArray arrayWithObjects:@"a",@"e" , nil]notFoundMarker:@"not fount"]);//後面一個引數notFoundMarker是如果找不到對應的key用什麼值代替
    /*結果:
     (
         1,
         "not fount"
     )
     */
}
void test3(){
    NSDictionary *dic1=[NSDictionary dictionaryWithObjectsAndKeys:
                        @"1",@"a",
                        @"2",@"b",
                        @"3",@"c",
                        @"2",@"d",
                        nil];
    //遍歷1
    for (id key in dic1) {//注意對於字典for遍歷迴圈的是key
        NSLog(@"%@=%@",key,[dic1 objectForKey:key]);
    }
    /*結果:
     d=2
     b=2
     c=3
     a=1
     */
    
    //遍歷2
    NSEnumerator *enumerator=[dic1 keyEnumerator];//還有值的迭代器[dic1 objectEnumerator]
    id key=nil;
    while (key=[enumerator nextObject]) {
        NSLog(@"%@=%@",key,[dic1 objectForKey:key]);
        
    }
    /*結果:
     d=2
     b=2
     c=3
     a=1
     */
    
    //遍歷3
    [dic1 enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        NSLog(@"%@=%@",key,obj);
    }];
    /*結果:
     d=2
     b=2
     c=3
     a=1
     */
}

void test4(){
    NSMutableDictionary *dic=[NSMutableDictionary dictionaryWithObjectsAndKeys:@"1",@"a",
                              @"2",@"b",
                              @"3",@"c",
                              @"2",@"d",
                            nil];
    [dic removeObjectForKey:@"b"];
    NSLog(@"%@",dic);
    /*結果:
     {
         a = 1;
         c = 3;
         d = 2;
     }
     */
    
    [dic addEntriesFromDictionary:@{@"e":@"7",@"f":@"6"}];
    NSLog(@"%@",dic);
    /*結果:
     {
         a = 1;
         c = 3;
         d = 2;
         e = 7;
         f = 6;
     }
     */
    
    [dic setValue:@"5" forKey:@"a"];
    NSLog(@"%@",dic);
    /*結果:
     {
         a = 5;
         c = 3;
         d = 2;
         e = 7;
         f = 6;
     }
     */
     
    
    //注意,一個字典的key或value新增到字典中時計數器+1;字典釋放時呼叫key或value的release一次,計數器-1
}


int main(int argc, const char * argv[]) {
    test1();
    test2();
    test3();
    test4();
    return 0;
}

注意:同陣列一樣,不管是可變字典還是不可變字典初始化元素後面必須加上nil以表示結束。

裝箱和拆箱

其實從上面的例子中我們也可以看到,陣列和字典中只能儲存物件型別,其他基本型別和結構體是沒有辦法放到陣列和字典中的,當然你也是無法給它們傳送訊息的(也就是說有些NSObject的方法是無法呼叫的),這個時候通常會用到裝箱(boxing)和拆箱(unboxing)。其實各種高階語言基本上都有裝箱和拆箱的過程,例如C#中我們將基本資料型別轉化為Object就是一個裝箱的過程,將這個Object物件轉換為基本資料型別的過程就是拆箱,而且在C#中裝箱的過程可以自動完成,基本資料型別可以直接賦值給Object物件。但是在ObjC中裝箱的過程必須手動實現,ObjC不支援自動裝箱。

在ObjC中我們一般將基本資料型別裝箱成NSNumber型別(當然它也是NSObject的子類,但是NSNumber不能對結構體裝箱),呼叫其對應的方法進行轉換:

+(NSNumber *)numberWithChar:(char)value;

+(NSNumber *)numberWithInt:(int)value;

+(NSNumber *)numberWithFloat:(float)value;

+(NSNumber *)numberWithDouble:(double)value;

+(NSNumber *)numberWithBool:(BOOL)value;

+(NSNumber *)numberWithInteger:(NSInteger)value;

拆箱的過程就更加簡單了,可以呼叫如下方法:

-(char)charValue;

-(int)intValue;

-(float)floatValue;

-(double)doubleValue;

-(BOOL)boolValue;

簡單看一個例子

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


/*可以存放基本型別到陣列、字典*/
void test1(){
    //包裝類NSNumber,可以包裝基本型別但是無法包裝結構體型別
    NSNumber *number1=[NSNumber numberWithChar:'a'];//'a'是一個C語言的char型別我們無法放倒NSArray中,但是我們可以通過NSNumber包裝
    NSArray *array1=[NSArray arrayWithObject:number1];
    NSLog(@"%@",array1);
    /*結果:
     (
        97
     )
     */
    
    NSNumber *number2= [array1 lastObject];
    NSLog(@"%@",number2);//返回的不是基本型別,結果:97
    
    
    char char1=[number2 charValue];//number轉化為char
    NSLog(@"%c",char1); //結果:a
}

int main(int argc, const char * argv[]) {
    test1();
    return  0;
}

上面我們看到了基本資料型別的裝箱和拆箱過程,那麼結構體呢?這個時候我們需要引入另外一個型別NSValue,其實上面的NSNumber就是NSValue的子類,它包裝了一些基本資料型別的常用裝箱、拆箱方法,當要對結構體進行裝箱、拆箱操作我們需要使用NSValue,NSValue可以對任何資料型別進行裝箱、拆箱操作。

事實上對於常用的結構體Foundation已經為我們提供好了具體的裝箱方法:

+(NSValue *)valueWithPoint:(NSPoint)point;

+(NSValue *)valueWithSize:(NSSize)size;

+(NSValue *)valueWithRect:(NSRect)rect;

對應的拆箱方法:

-(NSPoint)pointValue;

-(NSSize)sizeValue;

-(NSRect)rectValue;

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

//NSNumber是NSValue的子類,而NSValue可以包裝任何型別,包括結構體
void test1(){
    CGPoint point1=CGPointMake(10, 20);
    NSValue *value1=[NSValue valueWithPoint:point1];//對於系統自帶型別一般都有直接的方法進行包裝
    NSArray *array1=[NSArray arrayWithObject:value1];//放倒陣列中
    NSLog(@"%@",array1);
    /*結果:
     (
        "NSPoint: {10, 20}"
     )
     */
    
    NSValue *value2=[array1 lastObject];
    CGPoint point2=[value2 pointValue];//同樣對於系統自帶的結構體有對應的取值方法(例如本例pointValue)
    NSLog(@"x=%f,y=%f",point2.x,point2.y);//結果:x=10.000000,y=20.000000
}


int main(int argc, const char * argv[]) {
    test1();
    return  0;
}

 

那麼如果是我們自定義的結構體型別呢,這個時候我們需要使用NSValue如下方法進行裝箱:

+(NSValue *)valueWithBytes:(const void *)value objCType:(const char *)type;

呼叫下面的方法進行拆箱:

-(void)getValue:(void *)value;

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef struct {
    int year;
    int month;
    int day;
} Date;


//NSNumber是NSValue的子類,而NSValue可以包裝任何型別,包括結構體
void test1(){
    //如果我們自己定義的結構體包裝
    Date date={2014,2,28};
    char *type=@encode(Date);
    NSValue *value3=[NSValue value:&date withObjCType:type];//第一引數傳遞結構體地址,第二個引數傳遞型別字串
    NSArray *array2=[NSArray arrayWithObject:value3];
    NSLog(@"%@",array2);
    /*結果:
     (
        "<de070000 02000000 1c000000>"
     )
     */
    
    Date date2;
    [value3 getValue:&date2];//取出對應的結構體,注意沒有返回值
    //[value3 objCType]//取出包裝內容的型別
    NSLog(@"%i,%i,%i",date2.year,date2.month,date2.day); //結果:2014,2,28
    
}


int main(int argc, const char * argv[]) {
    test1();
    return  0;
}

擴充套件1-NSNull

通過前面的介紹大家都知道無論在陣列還是在字典中都必須以nil結尾,否則陣列或字典無法判斷是否這個陣列或字典已經結束(與C語言中的字串比較類似,C語言中定義字串後面必須加一個”\0”)。但是我們有時候確實想在資料或字典中儲存nil值而不是作為結束標記怎麼辦呢?這個時候需要使用NSNull,這個類是一個單例,只有一個null方法。簡單看一下:

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>



int main(int argc, const char * argv[]) {
    
    NSNull *nl=[NSNull null];//注意這是一個物件,是一個單例,只有一個方法null建立一個物件
    NSNull *nl2=[NSNull null];
    NSLog(@"%i",nl==nl2);//由於是單例所以地址相等,結果:1
    
    NSArray *array1=[NSArray arrayWithObjects:@"abc",nl,@123, nil];
    NSLog(@"%@",array1);
    /*結果:
     (
         abc,
         "<null>",
         123
     )
     */

    return  0;
}

 

擴充套件2-@符號

我們知道在ObjC中很多關鍵字前都必須加上@符號,例如@protocol、@property等,當然ObjC中的字串必須使用@符號,還有就是%@可以表示輸出一個物件。其實@符號在新版的ObjC中還有一個作用:裝箱。

相信聰明的童鞋在前面的例子中已經看到了,這裡簡單的介紹一下(在下面的演示中你也將看到很多ObjC新特性)。

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef enum {
    spring,
    summer,
    autumn,
    winter
} Season;

int main(int argc, const char * argv[]) {
    /*裝箱*/
    NSNumber *number1=@100;
    NSArray *array1=[NSArray arrayWithObjects:number1,@"abc",@16,@'A',@16.7,@YES, nil];
    NSLog(@"%@",array1);
    /*結果:
     (
         100,
         abc,
         16,
         65,
         "16.7"
         1
     )
     */
    NSNumber *number2=@(1+2*3);
    NSLog(@"%@",number2); //結果:7
    NSNumber *number3=@(autumn);
    NSLog(@"%@",number3); //結果:2
    

    NSArray *array2=@[@"abc",@16,@'A',@16.7,@YES];//使用這種方式最後不用新增nil值了
    NSLog(@"%@",array2[2]); //結果:65
    NSMutableArray *array3=[NSMutableArray arrayWithArray:array2];
    array3[0]=@"def";
    NSLog(@"%@",array3[0]); //結果:def
    
    NSDictionary *dic1=@{@"a":@123,@"b":@'c',@"c":@YES};
    NSLog(@"%@",dic1);
    /*結果:
     {
         a = 123;
         b = 99;
         c = 1;
     }
     */
    NSMutableDictionary *dic2=[NSMutableDictionary dictionaryWithDictionary:dic1];
    dic2[@"a"]=@456;
    NSLog(@"%@",dic2[@"a"]);//結果:456

    return 0;
}

反射

由於ObjC動態性,在ObjC中實現反射可以說是相當簡單,下面程式碼中演示了常用的反射操作,具體作用也都在程式碼中進行了註釋說明:

Account.h

//
//  Account.h
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Account : NSObject

@property (nonatomic,assign) double balance;

@end

Account.m

//
//  Account.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "Account.h"

@implementation Account

@end

Person.h

//
//  Person.h
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
@class Account;

@interface Person : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,retain) Account *account;

-(Person *)initWithName:(NSString *)name;

+(Person *)personWithName:(NSString *)name;

-(void)showMessage:(NSString *)infomation;

//自己實現物件比較方法
-(NSComparisonResult)comparePerson:(Person *)person;
@end

Person.m

//
//  Person.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "Person.h"

@implementation Person

-(Person *)initWithName:(NSString *)name{
    if(self=[super init]){
        self.name=name;
    }
    return self;
}

+(Person *)personWithName:(NSString *)name{
    Person *person=[[Person alloc]initWithName:name];
    return person;
}

-(void)showMessage:(NSString *)infomation{
    NSLog(@"My name is %@,the infomation is \"%@\".",_name,infomation);
}

//自己實現物件比較方法
-(NSComparisonResult)comparePerson:(Person *)person{
    return [_name compare:person.name];
}

-(NSString *)description{
    return [NSString stringWithFormat:@"name=%@",_name];
}

@end

main.m

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Person.h"


int main(int argc, const char * argv[]) {
    /*常用方法*/
    Person *person1=[Person personWithName:@"Kenshin"];
    NSLog(@"%i",[person1 isKindOfClass:[NSObject class]]); //判斷一個物件是否為某種型別(如果是父類也返回YES),結果:1
    NSLog(@"%i",[person1 isMemberOfClass:[NSObject class]]); //判斷一個物件是否是某個類的例項化物件,結果:0
    NSLog(@"%i",[person1 isMemberOfClass:[Person class]]); //結果:1
    NSLog(@"%i",[person1 conformsToProtocol:@protocol(NSCopying)]);//是否實現了某個協議,結果:0
    NSLog(@"%i",[person1 respondsToSelector:@selector(showMessage:)]);//是否存在某個方法,結果:1
    
    [person1 showMessage:@"Hello,world!"];//直接呼叫一個方法
    [person1 performSelector:@selector(showMessage:) withObject:@"Hello,world!"];
    //動態呼叫一個方法,注意如果有引數那麼引數型別只能為ObjC物件,並且最多隻能有兩個引數

    
    /*反射*/
    //動態生成一個類
    NSString *className=@"Person";
    Class myClass=NSClassFromString(className);//根據類名生成類
    Person *person2=[[myClass alloc]init]; //例項化
    person2.name=@"Kaoru";
    NSLog(@"%@",person2);//結果:name=Kaoru

    //類轉化為字串
    NSLog(@"%@,%@",NSStringFromClass(myClass),NSStringFromClass([Person class])); //結果:Person,Person

    //呼叫方法
    NSString *methodName=@"showMessage:";
    SEL mySelector=NSSelectorFromString(methodName);
    Person *person3=[[myClass alloc]init];
    person3.name=@"Rosa";
    [person3 performSelector:mySelector withObject:@"Hello,world!"]; //結果:My name is Rosa,the infomation is "Hello,world!".

    //方法轉化為字串
    NSLog(@"%@",NSStringFromSelector(mySelector)); //結果:showMessage:
    
    return 0;
}

拷貝

物件拷貝操作也比較常見,在ObjC中有兩種方式的拷貝:copy和mutablecopy,這兩種方式都將產生一個新的物件,只是後者產生的是一個可變物件。在ObjC中如果要想實現copy或者mutablecopy操作需要實現NSCopy或者NSMutableCopy協議,拷貝操作產生的新的物件預設引用計數器是1,在非ARC模式下我們應該對這個物件進行記憶體管理。在熟悉這兩種操作之前我們首先需要弄清兩個概念:深複製(或深拷貝)和淺複製(或淺拷貝)。

  • 淺複製:在執行復制操作時,對於物件中每一層(物件中包含的物件,例如說屬性是某個物件型別)複製都是指標複製(如果從引用計數器角度出發,那麼每層物件的引用計數器都會加1)。
  • 深複製:在執行復制操作時,至少有一個物件的複製是物件內容複製(如果從引用計數器角度出發,那麼除了物件內容複製的那個物件的引用計數器不變,其他指標複製的物件其引用計數器都會加1)。

注:

指標拷貝:拷貝的是指標本身(也就是具體物件的地址)而不是指向的物件內容本身。

物件複製:物件複製指的是複製內容是物件本身而不是物件的地址。

完全複製:上面說了深複製和淺複製,既然深複製是至少一個物件複製是物件內容複製,那麼如果所有複製都是物件內容複製那麼這個複製就叫完全複製。

對比copy和mutablecopy其實前面我們一直還用到一個操作是retain,它們之間的關係如下:

retain:始終採取淺複製,引用計數器會加1,返回的物件和被複制物件是同一個物件1(也就是說這個物件的引用多了一個,或者說是指向這個物件的指標多了一個);

copy:對於不可變物件copy採用的是淺複製,引用計數器加1(其實這是編譯器進行了優化,既然原來的物件不可變,複製之後的物件也不可變那麼就沒有必要再重新建立一個物件了);對於可變物件copy採用的是深複製,引用計數器不變(原來的物件是可變,現在要產生一個不可變的當然得重新產生一個物件);

mutablecopy:無論是可變物件還是不可變物件採取的都是深複製,引用計數器不變(如果從一個不可變物件產生一個可變物件自然不用說兩個物件絕對不一樣肯定是深複製;如果從一個可變物件產生出另一個可變物件,那麼當其中一個物件改變自然不希望另一個物件改變,當然也是深複製)。

注:

可變物件:當值發生了改變,那麼地址也隨之發生改變;

不可變物件:當值發生了改變,內容首地址不發生變化;

引用計數器:用於計算一個物件有幾個指標在引用(有幾個指標變數指向同一個記憶體地址);

 

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


void test1(){
    NSString *name=@"Kenshin";
    NSString *str1=[NSString stringWithFormat:@"I'm %@.",name];//注意此時str1的計數器是1
    NSLog(@"%lu",[str1 retainCount]); //結果:1
    
    
    NSMutableString *str2=[str1 mutableCopy];//注意此時str2的計數器為1,str1的計數器還是1
    //NSMutableString *str5 =CFRetain((__bridge CFTypeRef)str2);
    NSLog(@"retainCount(str1)=%lu,retainCount(str2)=%lu",[str1 retainCount],[str2 retainCount]);
    //結果:retainCount(str1)=1,retainCount(str2)=1
    
    
    [str2 appendString:@"def"];//改變str2,str1不變
    NSLog(@"%zi",str1==str2);//二者不是向同一個物件,結果:0
    NSLog(@"str1=%@",str1); //結果:str1=I'm Kenshin.
    NSLog(@"str2=%@",str2); //結果:str2=I'm Kenshin.def
    
    
    NSLog(@"str1's %lu",[str1 retainCount]);
    NSString *str3=[str1 copy];//str3不是產生的新物件而是複製了物件指標,但是str1的計數器+1(當然既然str3同樣指向同一個物件,那麼如果計算str3指向的物件引用計數器肯定等於str1的物件引用計數器)
    NSLog(@"%zi",str1==str3);//二者相等指向同一個物件,結果:1
    NSLog(@"retainCount(str1)=%lu,retainCount(str3)=%lu",str1.retainCount,str3.retainCount);
    //結果:retainCount(str1)=2,retainCount(str3)=2
    
    //需要注意的是使用copy和mutableCopy是深複製還是淺複製不是絕對,關鍵看由什麼物件產生什麼樣的物件
    NSString *str4=[str2 copy];//由NSMutableString產生了NSString,二者型別都不同肯定是深拷貝,此時str2的計數器還是1,str4的計數器也是1
    [str2 appendString:@"g"];//改變原物件不影響str4
    NSLog(@"%zi",str2==str4); //結果:0
    NSLog(@"str2=%@",str2); //結果:str2=I'm Kenshin.defg
    NSLog(@"str4=%@",str4); //結果:str4=I'm Kenshin.def

    
    [str1 release];
    str1=nil;
    [str3 release];//其實這裡也可以呼叫str1再次release,因為他們兩個指向的是同一個物件(但是一般不建議那麼做,不容易理解)
    str3=nil;
    
    [str2 release];
    str2=nil;
    [str4 release];
    str4=nil;
    
    //上面只有一種情況是淺拷貝:不可變物件呼叫copy方法
    
}

int main(int argc,char *argv[]){
    test1();
    return 0;
}

為了方便大家理解上面的程式碼,這裡以圖形畫出str1、str2、str3、str4在記憶體中的儲存情況:

MemoryStore

從上面可以清楚的看到str1和str3同時指向同一個物件,因此這個物件的引用計數器是2(可以看到兩箭頭指向那個物件),str2和str4都是兩個新的物件;另外ObjC引入物件拷貝是為了改變一個物件不影響另一個物件,但是我們知道NSString本身就不能改變那麼即使我重新複製一個物件也沒有任何意義,因此為了效能著想如果通過copy方法產生一個NSString時ObjC不會再複製一個物件而是將新變數指向同一個物件。

注意網上很多人支招在ARC模式下可以利用_objc_rootRetainCount()或者CFGetRetainCount()取得retainCount都是不準確的,特別是在物件拷貝操作之後你會發現二者取值也是不同的,因此如果大家要檢視retainCount最好還是暫時關閉ARC。

要想支援copy或者mutablecopy操作那麼物件必須實現NSCoping協議並實現-(id)copyWithZone:(NSZone*)zone方法,在Foundation中常用的可複製物件有:NSNumber、NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary。下面看一下如何讓自定義的類支援copy操作:

Person.h

//
//  Person.h
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
@class Account;

@interface Person : NSObject

@property  NSMutableString *name;
@property (nonatomic,assign) int age;


@end

Person.m

//
//  Person.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "Person.h"

@implementation Person


-(NSString *)description{
    return [NSString stringWithFormat:@"name=%@,age=%i",_name,_age];
}

//實現copy方法
-(id)copyWithZone:(NSZone *)zone{
    //注意zone是系統已經分配好的用於儲存當前物件的記憶體
    //注意下面建立物件最好不要用[[Person allocWithZone:zone]init],因為子類如果沒有實現該方法copy時會呼叫父類的copy方法,此時需要使用子類物件初始化如果此時用self就可以表示子類物件,還有就是如果子類呼叫了父類的這個方法進行重寫copy也需要呼叫子類物件而不是父類Person
    Person *person1=[[[self class] allocWithZone:zone]init];
    person1.name=_name;
    person1.age=_age;
    return person1;
}

@end

main.m

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Account.h"
#import "Person.h"

void test1(){
    Person *person1=[[Person alloc]init];
    NSMutableString *str1=[NSMutableString stringWithString:@"Kenshin"];
    person1.name=str1;
    //由於name定義的時候使用屬性引數採用的是copy策略,而根據前面的知識我們知道NSMutableString的copy策略採用的是物件內容複製,因此如果修改str1不會改變person1.name
    [str1 appendString:@" Cui"];
    NSLog(@"%@",str1);//結果:Kenshin Cui
    NSLog(@"%@",person1.name); //結果:Kenshin
    
}

void test2(){
    Person *person1=[[Person alloc]init];
    person1.name=[NSMutableString stringWithString:@"Kenshin"];
    person1.age=28;
    Person *person2=[person1 copy];
    NSLog(@"%@",person1); //結果:name=Kenshin,age=0
    NSLog(@"%@",person2); //結果:name=Kenshin,age=0
    
    [person2.name appendString:@" Cui"];
    
    NSLog(@"%@",person1);//結果:name=Kenshin Cui,age=28
    NSLog(@"%@",person2);//結果:name=Kenshin Cui,age=28
}

int main(int argc,char *argv[]){
    test1();
    test2();
    return 0;
}

在上面的程式碼中重點說一下test2這個方法,在test2方法中我們發現當修改了person2.name屬性之後person1.name也改變了,這是為什麼呢?我們可以看到在Person.m中自定義實現了copy方法,同時實現了一個淺拷貝。之所以說是淺拷貝主要是因為我們的name屬性引數是直接賦值完成的,同時由於name屬性定義時採用的是assign引數(預設為assign),所以當通過copy建立了person2之後其實person2物件的name屬性和person1指向同一個NSMutableString物件。通過圖形表示如下:

MemoryStore2

上面test2的寫法純屬為了讓大家瞭解複製的原理和本質,實際開發中我們很少會遇到這種情況,首先我們一般定義name的話可能用的是NSString型別,根本也不能修改;其次我們定義字串型別的話一般使用(copy)引數,同樣可以避免這個問題(因為NSMutableString的copy是深複製)。那麼如果我們非要使用NSMutabeString同時不使用屬性的copy引數如何解決這個問題呢?答案就是使用深複製,將-(id)copyWithZone:(NSZone *)zone方法中person1.name=_name改為,person1.name=[_name copy];或person1.name=[_name mutablecopy]即可,這樣做也正好滿足我們上面對於深複製的定義。

補充-NSString的引用計數器

在好多語言中字串都是一個特殊的物件,在ObjC中也不例外。NSString作為一個物件型別儲存在堆中,多數情況下它跟一般的物件型別沒有區別,但是這裡我們需求強調一點那就是字串的引用計數器。

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>


int main(int argc,char *argv[]){
    
    NSString *str1=@"Kenshin";
    NSLog(@"retainCount(str1)=%i",(unsigned long)str1.retainCount); //結果:-1
    [str1 retain];
    NSLog(@"retainCount(str1)=%i",(unsigned long)str1.retainCount); //結果:-1
    
    NSString *str2=[NSString stringWithString:@"Kaoru"];
    NSLog(@"retainCount(str2)=%i",str2.retainCount); //結果:-1
    [str1 retain];
    NSLog(@"retainCount(str2)=%i",str2.retainCount); //結果:-1
    NSString *str2_1=[NSString stringWithString:[NSString stringWithFormat:@"Kaoru %@",@"sun"]];
    NSLog(@"retainCount(str2_1)=%i",str2_1.retainCount);//結果:2 
    [str2_1 release];
    [str2_1 release];
    
    
    
    NSString *str3=[NSString stringWithFormat:@"Rosa %@",@"Sun"];
    NSLog(@"retainCount(str3)=%i",str3.retainCount); //結果:1
    [str3 retain];
    NSLog(@"retainCount(str3)=%i",str3.retainCount); //結果:2
    [str3 release];
    [str3 release];
    
    NSString *str4=[NSString stringWithUTF8String:"Jack"];
    NSLog(@"retainCount(str4)=%i",str4.retainCount); //結果:1
    [str4 retain];
    NSLog(@"retainCount(str4)=%i",str4.retainCount); //結果:2
    [str4 release];
    [str4 release];
    
    NSString *str5=[NSString stringWithCString:"Tom" encoding:NSUTF8StringEncoding];
    NSLog(@"retainCount(str5)=%i",str5.retainCount); //結果:1
    [str5 retain];
    NSLog(@"retainCount(str5)=%i",str5.retainCount); //結果:2
    [str5 release];
    [str5 release];
    
    
    
    NSMutableString *str6=@"Jerry";
    NSLog(@"retainCount(str6)=%i",str6.retainCount); //結果:-1
    [str6 retain];
    NSLog(@"retainCount(str6)=%i",str6.retainCount); //結果:-1
    [str6 release];
    [str6 release];
    
    NSMutableArray *str7=[NSMutableString stringWithString:@"Lily"];
    NSLog(@"retainCount(str7)=%i",str7.retainCount); //結果:1
    [str7 retain];
    NSLog(@"retainCount(str7)=%i",str7.retainCount); //結果:2
    [str7 release];
    [str7 release];

    
    return 0;
}

看完上面的例子如果不瞭解NSString的處理你也許會有點奇怪(注意上面的程式碼請在Xcode5下執行)?請看下面的解釋

  • str1是一個字串常量,它儲存在常量區,系統不會對它進行引用計數,因此無論是初始化還是做retain操作其引用計數器均為-1;
  • str3、str4、str5建立的物件同一般物件類似,儲存在堆中,系統會對其進行引用計數;
  • 採用stringWithString定義的變數有些特殊,當後面的字串是字串常量,則它本身就作為字串常用量儲存(str2),類似於str1;如果後面的引數是通過類似於str3、str4、str5的定義,那麼它本身就是一個普通物件,只是後面的物件引用計數器預設為1,當給它賦值時會做一次拷貝操作(淺拷貝),引用計數器加1,所有str2_1引用計數器為2;
  • str6其實和str1類似,雖然定義的是可變陣列,但是它的本質還是字串常量,事實上對於可變字串只有為字串常量時引用計數器才為-1,其他情況它的引用計數器跟一般物件完全一致;

後記:注意上面這段程式碼的執行結果是在Xcode5中執行的結果,事實上針對最新的Xcode6由於LLVM的優化,只有str2_1和str7的引用計數器為1(str7 retain一次後第二次為2),其他均為-1。

檔案操作

在今天的最後一節內容中讓我們看一下Foundation中檔案操作,下面將以一個例子進行說明:

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

/*目錄操作*/
void test1(){
    //檔案管理器是專門用於檔案管理的類
    NSFileManager *manager=[NSFileManager defaultManager];
    
    //獲得當前程式所在目錄(當然可以改變)
    NSString *currentPath=[manager currentDirectoryPath];
    NSLog(@"current path is :%@",currentPath);
    //結果:/Users/kenshincui/Library/Developer/Xcode/DerivedData/FoundationFramework-awxjohcpgsqcpsanqofqogwbqgbx/Build/Products/Debug
    
    //建立目錄
    NSString *myPath=@"/Users/kenshincui/Desktop/myDocument";
    BOOL result= [manager createDirectoryAtPath:myPath withIntermediateDirectories:YES attributes:nil error:nil];
    if(result==NO){
        NSLog(@"Couldn't create directory!");
    }
    
    //目錄重新命名,如果需要刪除目錄只要呼叫removeItemAtPath:<#(NSString *)#> error:<#(NSError **)#>
    NSError *error;
    NSString *newPath=@"/Users/kenshincui/Desktop/myNewDocument";
    if([manager moveItemAtPath:myPath toPath:newPath error:&error]==NO){
        NSLog(@"Rename directory failed!Error infomation is:%@",error);
    }
    
    //改變當前目錄
    if([manager changeCurrentDirectoryPath:newPath]==NO){
        NSLog(@"Change current directory failed!");
    }
    NSLog(@"current path is :%@",[manager currentDirectoryPath]);
    //結果:current path is :/Users/kenshincui/Desktop/myNewDocument
    
    //遍歷整個目錄
    NSString *path;
    NSDirectoryEnumerator *directoryEnumerator= [manager enumeratorAtPath:newPath];
    while (path=[directoryEnumerator nextObject]) {
        NSLog(@"%@",path);
    }
    /*結果:
     documents
     est.txt
    */
    
    //或者這樣遍歷
    NSArray *paths= [manager contentsOfDirectoryAtPath:newPath error:nil];
    NSObject *p;
    for (p in paths) {
        NSLog(@"%@",p);
    }
    /*結果:
     documents
     est.txt
     */
}

/*檔案操作*/
void test2(){
    NSFileManager *manager=[NSFileManager defaultManager];
    NSString *filePath=@"/Users/kenshincui/Desktop/myNewDocument/test.txt";
    NSString *filePath2=@"/Users/kenshincui/Desktop/test.txt";
    NSString *newPath=@"/Users/kenshincui/Desktop/myNewDocument/test2.txt";
    
    //判斷檔案是否存在,這個方法也可以判斷目錄是否存在,這要後面的引數設定位YES
    if ([manager fileExistsAtPath:filePath isDirectory:NO]) {
        NSLog(@"File exists!");
    }
    
    //檔案是否可讀
    if([manager isReadableFileAtPath:filePath]){
        NSLog(@"File is readable!");
    }
    
    //判斷兩個檔案內容是否相等
    if ([manager contentsEqualAtPath:filePath andPath:filePath2]) {
        NSLog(@"file1 equals file2");
    }
    
    //檔案重新命名,方法類似於目錄重新命名
    if (![manager moveItemAtPath:filePath toPath:newPath error:nil]) {
        NSLog(@"Rename file1 failed!");
    }
    
    //檔案拷貝
    NSString *filePath3=@"/Users/kenshincui/Desktop/test3.txt";
    if(![manager copyItemAtPath:newPath toPath:filePath3 error:nil]){
        NSLog(@"Copy failed!");
    }
    
    //讀取檔案屬性
    NSDictionary *attributes;
    if ((attributes=[manager attributesOfItemAtPath:newPath error:nil])==nil) {
        NSLog(@"Read attributes failed!");
    }
    for (NSObject *key in attributes) {
        NSLog(@"%@=%@",key,attributes[key]);
    }
    /*結果:
         NSFileOwnerAccountID=501
         NSFileHFSTypeCode=0
         NSFileSystemFileNumber=1781953
         NSFileExtensionHidden=0
         NSFileSystemNumber=16777218
         NSFileSize=27
         NSFileGroupOwnerAccountID=20
         NSFileOwnerAccountName=kenshincui
         NSFileCreationDate=2014-07-28 11:47:58 +0000
         NSFilePosixPermissions=420
         NSFileHFSCreatorCode=0
         NSFileType=NSFileTypeRegular
         NSFileExtendedAttributes={
         "com.apple.TextEncoding" = <7574662d 383b3133 34323137 393834>;
         }
         NSFileGroupOwnerAccountName=staff
         NSFileReferenceCount=1
         NSFileModificationDate=2014-07-28 11:47:58 +0000
     */
    
    //刪除檔案
    [manager removeItemAtPath:newPath error:nil];
    
}
//檔案操作--檔案內容操作(NSData,非結構化位元組流物件,有緩衝區管理機制,可用於網路傳輸)
void test3(){
    NSFileManager *manager=[NSFileManager defaultManager];
    NSString *filePath=@"/Users/kenshincui/Desktop/myNewDocument/test2.txt";
    NSData *data=[manager contentsAtPath:filePath];
    NSLog(@"%@",data);//儲存的是二進位制位元組流
    //結果:<68656c6c 6f20776f 726c642c e4b896e7 958ce4bd a0e5a5bd efbc81>
    
    //NSData轉化成字串
    NSString *str1=[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
    NSLog(@"%@",str1);
    //結果:hello world,世界你好!
    
    //字串轉化成NSData
    NSString *str2=@"Kenshin";
    NSData *data2=[str2 dataUsingEncoding:NSUTF8StringEncoding];
    NSLog(@"%@",data2);
    
    //當然一般如果僅僅是簡單讀取檔案內容,直接使用者NSString方法即可
    NSString *content=[NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"%@",content);
    //結果:hello world,世界你好!
    
}
//檔案操作--細粒度控制檔案,檔案操作柄
void test4(){
    NSFileManager *manager=[NSFileManager defaultManager];
    NSString *filePath=@"/Users/kenshincui/Desktop/myNewDocument/test2.txt";
    
    //以只讀方式開啟檔案
    NSFileHandle *fileHandle=[NSFileHandle fileHandleForReadingAtPath:filePath];//注意這個方法返回型別為instancetype,也就是說對於上面的NSFileHandle它的返回型別也是NSFileHandle
    NSData *data= [fileHandle readDataToEndOfFile];//完整讀取檔案
    NSString *newPath=@"/Users/kenshincui/Desktop/test4.txt";
    [manager createFileAtPath:newPath contents:nil attributes:nil];
    NSFileHandle *fileHandle2=[NSFileHandle fileHandleForWritingAtPath:newPath];//以可寫方式開啟檔案
    [fileHandle2 writeData:data];//寫入檔案內容
    
    [fileHandle2 closeFile];//關閉檔案

    
    //定位到指定位置,預設在檔案開頭
    [fileHandle seekToFileOffset:12];
    NSData *data2= [fileHandle readDataToEndOfFile];
    NSLog(@"data2=%@",[[NSString alloc]initWithData:data2 encoding:NSUTF8StringEncoding]);
    //結果:data2=世界你好!
    
    [fileHandle seekToFileOffset:6];
    NSData *data3=[fileHandle readDataOfLength:5];
    NSLog(@"data3=%@",[[NSString alloc]initWithData:data3 encoding:NSUTF8StringEncoding]);
    //結果:data3=world
    
    [fileHandle closeFile];
    
}

//檔案路徑
void test5(){
    NSString *filePath=@"/Users/kenshincui/Desktop/myDocument";
    NSString *filePath2=@"/Users/kenshincui/Desktop/test.txt";

    //臨時檔案所在目錄
    NSString *path=NSTemporaryDirectory();
    NSLog(@"temporary directory is :%@",path);
    //結果:/var/folders/h6/lss6gncs509c2pgzgty3wd_40000gn/T/

    NSString *lastComponent= [filePath lastPathComponent];
    NSLog(@"%@",lastComponent); //結果:myDocument
    
    NSLog(@"%@",[filePath stringByDeletingLastPathComponent]);
    //結果:/Users/kenshincui/Desktop
    NSLog(@"%@",[filePath stringByAppendingPathComponent:@"Pictrues"]);
    //結果:/Users/kenshincui/Desktop/myDocument/Pictrues
    NSLog(@"%@",[filePath2 pathExtension]);
    //結果:txt
    
    [[filePath pathComponents] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"%i=%@",idx,obj);
    }];
    /*結果:
     0=/
     1=Users
     2=kenshincui
     3=Desktop
     4=myDocument
     */
    
    
}

//檔案操作--NSURL
void test6(){
    NSURL *url=[NSURL URLWithString:@"http://developer.apple.com"];
    NSString *str1=[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"%@",str1);
}

//檔案操作--NSBundle,程式包,一般用於獲取Resource中的資源(當然由於當前並非IOS應用沒有程式包,只是表示當前程式執行路徑)
//在ios中經常用於讀取應用程式中的資原始檔,如圖片、聲音、視訊等
void test7(){
    //在程式包所在目錄建立一個檔案
    NSFileManager *manager=[NSFileManager defaultManager];
    NSString *currentPath=[manager currentDirectoryPath];
    NSLog(@"current path is :%@",currentPath);
    //結果:current path is :/Users/kenshincui/Library/Developer/Xcode/DerivedData/FoundationFramework-awxjohcpgsqcpsanqofqogwbqgbx/Build/Products/Debug
    NSString *filePath=[currentPath stringByAppendingPathComponent:@"test.txt"];
    [manager createFileAtPath:filePath contents:[@"Hello,world!" dataUsingEncoding:NSUTF8StringEncoding] attributes:nil];
    
    
    //利用NSBundle在程式包所在目錄查詢對應的檔案
    NSBundle *bundle=[NSBundle mainBundle];//主要操作程式包所在目錄
    //如果有test.txt則返回路徑,否則返回nil
    NSString *path=[bundle pathForResource:@"test" ofType:@"txt"];//也可以寫成:[bundle pathForResource:@"instructions.txt" ofType:nil];
    NSLog(@"%@",path);
    //結果:/Users/kenshincui/Library/Developer/Xcode/DerivedData/FoundationFramework-awxjohcpgsqcpsanqofqogwbqgbx/Build/Products/Debug/test.txt
    NSLog(@"%@",[NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil]);
    //結果:Hello,world!
    
    //假設我們在程式執行建立一個Resources目錄,並且其中新建pic.jpg,那麼用下面的方法獲得這個檔案完整路徑
    NSString *path1= [bundle pathForResource:@"pic" ofType:@"jpg" inDirectory:@"Resources"];
    NSLog(@"%@",path1);
    //結果:/Users/kenshincui/Library/Developer/Xcode/DerivedData/FoundationFramework-awxjohcpgsqcpsanqofqogwbqgbx/Build/Products/Debug/Resources/pic.jpg
}

int main(int argc,char *argv[]){

    test1();
    test2();
    test3();
    test4();
    test5();
    test6();
    test7();
    
    return 0;
}

歸檔

歸檔,在其他語言中又叫“序列化”,就是將物件儲存到硬碟;解檔,在其他語言又叫“反序列化”就是將硬碟檔案還原成物件。其實歸檔就是資料儲存的過程,在IOS中資料的儲存有五種方式:

  1. xml屬性列表(plist歸檔)

  2. NSUserDefaults(偏好設定)

  3. NSKeyedArchiver歸檔(加密形式)

  4. SQLite3(嵌入式資料庫)

  5. Core Data(物件導向方式的嵌入式資料庫)

當然關於2、4、5點不是我們今天介紹的重點,這個在IOS開發過程中我們會重點說到。

xml屬性列表

首先我們先來看一下xml屬性列表,xml屬性列表進行歸檔的方式是將物件儲存在一個plist檔案中,這個操作起來比較簡單,其實相當於xml序列化。但是同時它也有缺點:一是這種方式是明文儲存的;二是這種方式操作的物件有限,只有NSArray、NSMutableArray、NSDictionary、NSMutableDictionary支援(歸檔時只要呼叫對應的writeToFile方法即可,解檔呼叫arrayWithContentsOfFile或dictionaryWithContentsOfFile,注意像NSString、NSNumber、NSData即使有這個方法它儲存的也不是xml格式)。

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

//xml屬性
void test1(){
    //陣列
    NSString *path=@"/Users/kenshincui/Desktop/arrayXml.plist";
    NSArray *array1=@[@"Kenshin",@"Kaoru",@"Rosa"];
    [array1 writeToFile:path atomically:YES];
    
    NSArray *array2=[NSArray arrayWithContentsOfFile:path];
    [array2 enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"array2[%lu]=%@",idx,obj);
    }];
    /*結果:
     array1[0]=Kenshin
     array1[1]=Kaoru
     array1[2]=Rosa
     */
    
    
    //字典
    NSString *path2=@"/Users/kenshincui/Desktop/dicXml.plist";
    NSDictionary *dic1=@{@"name":@"Kenshin",@"age":@28,@"height":@172.5};
    [dic1 writeToFile:path2 atomically:YES];
    
    NSDictionary *dic2=[NSDictionary dictionaryWithContentsOfFile:path2];
    [dic2 enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
        NSLog(@"dic2[%@]=%@",key,obj);
    }];
    /*結果:
     dic2[height]=172.5
     dic2[age]=28
     dic2[name]=Kenshin
     */
}

int main(int argc,char *argv[]){
    
    test1();
    
    return 0;
}

生成的檔案如下

arrayXml

dicXml

NSKeyedArchiver歸檔

如果要針對更多物件歸檔或者需要歸檔時能夠加密的話就需要使用NSKeyedArchiver進行歸檔和解檔,使用這種方式歸檔的範圍更廣而且歸檔內容是密文儲存。從歸檔範圍來講NSKeyedArchiver適合所有ObjC物件,但是對於自定義物件我們需要實現NSCoding協議;從歸檔方式來講NSKeyedArchiver分為簡單歸檔和複雜物件歸檔,簡單歸檔就是針對單個物件可以直接將物件作為根物件(不用設定key),複雜物件就是針對多個物件,儲存時不同物件需要設定不同的Key。

首先看一下系統物件兩種歸檔方式(注意由於本章主要介紹Foundation內容,下面的程式是OS X命令列程式並沒有建立成iOS應用,如果移植到到iOS應用下執行將NSArchiver和NSUnarchiver換成NSKeyedArchiver和NSKeyedUnarchiver。雖然在Foundation部分iOS和OS X在設計上儘可能通用但是還存在著細微差別。)

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

//系統物件簡單歸檔
void test1(){
    //NSString歸檔
    NSString *str1=@"Hello,world!";
    NSString *path1=@"/Users/kenshincui/Desktop/archiver1.arc";
    if(![NSArchiver archiveRootObject:str1 toFile:path1]){
        NSLog(@"archiver failed!");
    }
    //NSString解檔
    NSString *str2= [NSUnarchiver unarchiveObjectWithFile:path1];
    NSLog(@"str2=%@",str2);//結果:str2=Hello,world!
    
    
    //NSArray歸檔
    NSString *path2=@"/Users/kenshincui/Desktop/archiver2.arc";
    NSArray *array1=@[@"Kenshin",@"Kaoru",@"Rosa"];
    if(![NSArchiver archiveRootObject:array1 toFile:path2]){
        NSLog(@"archiver failed!");
    }
    //NSArray解檔
    NSArray *array2=[NSUnarchiver unarchiveObjectWithFile:path2];
    [array2 enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        NSLog(@"array2[%lu]=%@",idx,obj);
    }];
    /*結果:
     array2[0]=Kenshin
     array2[1]=Kaoru
     array2[2]=Rosa
     */
}

//系統複雜物件歸檔(多物件歸檔)
void test2(){
    /*歸檔*/
    NSString *path1=@"/Users/kenshincui/Desktop/archiver3.arc";
    
    int int1=89;
    CGSize size1={12.5,16.8};
    NSNumber *number1=@60.5;
    NSString *str1=@"Hello,world!";
    NSArray *array1=@[@"Kenshin",@"Kaoru",@"Rosa"];
    NSDictionary *dic1=@{@"name":@"Kenshin",@"age":@28,@"height":@172.5};
    
    //同時對多個物件進行歸檔
    NSMutableData *data1=[[NSMutableData alloc]init];//定義一個NSMutableData用於臨時存放資料
    NSKeyedArchiver *archiver=[[NSKeyedArchiver alloc]initForWritingWithMutableData:data1];//定義歸檔物件
    [archiver encodeInt:int1 forKey:@"int"];//對int1歸檔並指定一個key以便以後讀取
    [archiver encodeSize:size1 forKey:@"size"];
    [archiver encodeObject:number1 forKey:@"number"];
    [archiver encodeObject:str1 forKey:@"string"];
    [archiver encodeObject:array1 forKey:@"array"];
    [archiver encodeObject:dic1 forKey:@"dic"];

    [archiver finishEncoding];//結束歸檔
    
    [data1 writeToFile:path1 atomically:YES];//寫入檔案
    
    
    
    /*解檔*/
    int int2;
    CGSize size2;
    NSNumber *number2;
    NSString *str2;
    NSArray *array2;
    NSDictionary *dic2;
    
    NSData *data2=[[NSData alloc]initWithContentsOfFile:path1];//讀出資料到NSData
    NSKeyedUnarchiver *unarchiver=[[NSKeyedUnarchiver alloc]initForReadingWithData:data2];
    
    int2= [unarchiver decodeInt64ForKey:@"int"];
    size2=[unarchiver decodeSizeForKey:@"size"];
    number2=[unarchiver decodeObjectForKey:@"number"];
    str2=[unarchiver decodeObjectForKey:@"string"];
    array2=[unarchiver decodeObjectForKey:@"array"];
    dic2=[unarchiver decodeObjectForKey:@"dic"];
    
    [unarchiver finishDecoding];
    
    NSLog(@"int2=%i,size=%@,number2=%@,str2=%@,array2=%@,dic2=%@",int2,NSStringFromSize(size2),number2,str2,array2,dic2);
    /*結果:
     int2=89,
     size={12.5, 16.800000000000001},
     number2=60.5,
     str2=Hello,world!,
     array2=(
         Kenshin,
         Kaoru,
         Rosa
     ),
     dic2={
         age = 28;
         height = "172.5";
         name = Kenshin;
     }
     */
}


int main(int argc,char *argv[]){

    test1();
    test2();
    
    return 0;
}

接下來看一下自定義的物件如何歸檔,上面說了如果要對自定義物件進行歸檔那麼這個物件必須實現NSCoding協議,在這個協議中有兩個方法都必須實現:

-(void)encodeWithCoder:(NSCoder *)aCoder;通過給定的Archiver對訊息接收者進行編碼;

-(id)initWithCoder:(NSCoder *)aDecoder;從一個給定的Unarchiver的資料返回一個初始化物件;

這兩個方法分別在歸檔和解檔時呼叫。下面通過一個例子進行演示(注意對於自定義類的多物件歸檔與系統類多物件歸檔完全一樣,程式碼中不再演示):

Person.h

//
//  Person.h
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Person : NSObject<NSCoding>

@property (nonatomic,copy) NSString *name;
@property (nonatomic,assign) int age;
@property (nonatomic,assign) float height;
@property (nonatomic,assign) NSDate *birthday;

@end

Person.m

//
//  Person.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "Person.h"

@implementation Person

#pragma mark 解碼
-(id)initWithCoder:(NSCoder *)aDecoder{
    NSLog(@"decode...");
    if (self=[super init]) {
        self.name=[aDecoder decodeObjectForKey:@"name"];
        self.age=[aDecoder decodeInt64ForKey:@"age"];
        self.height=[aDecoder decodeFloatForKey:@"heiht"];
        self.birthday=[aDecoder decodeObjectForKey:@"birthday"];
    }
    return self;
}

#pragma mark 編碼
-(void)encodeWithCoder:(NSCoder *)aCoder{
    NSLog(@"encode...");
    [aCoder encodeObject:_name forKey:@"name"];
    [aCoder encodeInt64:_age forKey:@"age" ];
    [aCoder encodeFloat:_height forKey:@"height"];
    [aCoder encodeObject:_birthday forKey:@"birthday"];

}

#pragma mark 重寫描述
-(NSString *)description{
    NSDateFormatter *formater1=[[NSDateFormatter alloc]init];
    formater1.dateFormat=@"yyyy-MM-dd";
    return [NSString stringWithFormat:@"name=%@,age=%i,height=%.2f,birthday=%@",_name,_age,_height,[formater1 stringFromDate:_birthday]];
}

@end

main.m

//
//  main.m
//  FoundationFramework
//
//  Created by Kenshin Cui on 14-2-16.
//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Person.h"


int main(int argc,char *argv[]){

    //歸檔
    Person *person1=[[Person alloc]init];
    person1.name=@"Kenshin";
    person1.age=28;
    person1.height=1.72;
    NSDateFormatter *formater1=[[NSDateFormatter alloc]init];
    formater1.dateFormat=@"yyyy-MM-dd";
    person1.birthday=[formater1 dateFromString:@"1986-08-08"];
    
    NSString *path1=@"/Users/kenshincui/Desktop/person1.arc";
    
    [NSKeyedArchiver archiveRootObject:person1 toFile:path1];

    //解檔
    Person *person2= [NSKeyedUnarchiver unarchiveObjectWithFile:path1];
    NSLog(@"%@",person2);
    /*結果:
     name=Kenshin,age=28,height=0.00,birthday=1986-08-08
     */
    
    return 0;
}
今天的文章就到這裡了,內容確實不少,但是要用一篇文章把Foundation的所有內容說完也是完全不現實的。這篇文章有一部分內容並沒有詳細的解釋程式碼,這部分內容主要是個人認為比較簡單,通過註釋大家就可以理解。不過並不是說這部分內容不重要,而是這些內容多數是記憶性的東西,不需要過多解釋。

相關文章