iOS 之 Foundation 框架

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

Foundation概述
常用結構體
日期
字符串
數組
字典
裝箱和拆箱
反射
拷貝
文件操作
歸檔
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

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

值對象
集合
操作系統服務:文件系統、URL、進程通訊
通知
歸檔和序列化
表達式和條件判斷
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

import

import

import

import

import

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

import

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

import

import

import

import

import

import

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

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

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

import

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

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

import

import

import

import

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

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的所有內容說完也是完全不現實的。這篇文章有一部分內容並沒有詳細的解釋代碼,這部分內容主要是個人認爲比較簡單,通過註釋大家就可以理解。不過並不是說這部分內容不重要,而是這些內容多數是記憶性的東西,不需要過多解釋。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章