標籤:
本節的內容主要是 OC 的語言基礎,對 OC 比較熟的可以不用看這一篇的內容了,基本也就是翻譯一下官方文檔。
這一篇的主要內容就是關於 OC 的語言基礎,主要是針對值和集合而言。實際上 OC 中的值可以先分為兩類,一類是 C 語言提供的類型,一類是 OC 為我們提供的類型。
C 語言提供的類型,就是 int, float, char 這些,而在 OC 中,我們則是用對象來表示一個值,所以主要類型是 NSInteger, NSUInteger 以及 CGFloat 這樣的形式。C 語言所提供的這些類型的好處就在於我們不需要使用一個對象來表示值。但是在涉及到集合的操作的時候,我們往往需要的是 OC 中提供的類型。
C語言基本類型
首先介紹的是 C 語言中的基本類型,它們的使用都很簡單:
int someInteger = 42; float someFloatingPointNumber = 3.1415; double someDoublePrecisionFloatingPointNumber = 6.02214199e23;
當然,在 OC 中我們也可以直接使用這些基本類型,比如將其聲明為屬性:
@interface XYZCalculator : NSObject@property double currentValue;@end
聲明為屬性的時候,我們也可以使用點操作符在類的內部對其進行訪問,因為這一層訪問主要是基於存取方法來擷取和設定值,所以我們用 C 的類型還是用 OC 的類型差別不大。
Objective-C 中的基本類型
OC 中使用 BOOL 類型來存放布爾值,它的值是 YES 或者 NO。而在 Cocoa 或者 Cocoa Touch 中對象所包含的方法中,參數一般使用的都是 NSInteger 或者 CGFloat 這種類型的值。比如 NSTableViewDataSource 以及 UITableViewDataSource 協議中的方法,所用的就是 NSInteger 類型的值。
不論 NSInteger 也好,還是 NSUInteger 也好,他們的範圍都和目標結構有關,比如在32位的系統下運行,他們就是32位元的值,如果在64位下運行,那麼他們就會佔據64位的空間。在使用系統 API 的時候,蘋果推薦還是使用 NSInteger 這樣的值作為參數傳遞。
結構體
在 Cocoa 以及 Cocoa Touch API 中,有時候也會使用 C 語言中的結構體來儲存他們的值,比如:
NSString *mainString = @"This is a long string"; NSRange substringRange = [mainString rangeOfString:@"long"];
這個地方的 NSRange 其實擷取到的就是一個結構體,它包含了一個 long 字串在原本字串中的起始值,以及 long 這個字串的長度。
對象也可以用於表示值
如果說你需要用一個對象來表示基本類型的值,那麼你就可以使用 Cocoa 以及 Cocoa Touch 提供的基本類型,一般我們在使用集合的時候就會需要用對象來表示值。
字串使用 NSString 對象來表示
在之前的很多代碼中,我們都會看到 NSString 這個類,它用於表示一個字串。在 OC 中,如果我們想建立一個 NSString 類的對象,可以採取很多種方法,比如使用標準的 alloc init 方法,或者使用Factory 方法,也可以直接用字面值來實現:
NSString *firstString = [[NSString alloc] initWithString:"Hello World!" encoding:NSUTF8StringEncoding];
上面這些代碼都可以通過已有的字串來建立一個字串對象。不過,這裡建立的字串對象都是不可變的,也就是說一旦我們建立了這個字串對象,那麼我們就無法修改它的內容。如果我們需要一個不同的字串,那麼我們只能建立一個新的字串對象,比如:
NSString *name = @"John";name = [name stringByAppendingString:@"ny"]; // 返回的是一個新字串對象
需要注意的一點是,對於一個普通的字串對象,我們採用 stringByAppendingString: 這樣的方法的時候,最後擷取的都是一個新的字串對象所以我們需要一個變數來接受它,直接使用 [name stringByAppendingString:] 這樣的形式我們是沒有辦法接受到新產生的字串對象的,而 name 依舊指向原本的字串。
所以如果我們的字串是可能產生變動的,那麼我們需要一個 NSMutableString 類的對象,它是 NSString 類的一個子類,或者說是 NSString 的可變版本。我們基於 NSMutableString 類的對象,可以直接對字串進行修改,比如:
NSMutableString *name = [NSMutableString stringWithString:@"John"]; [name appendString:@"ny"];
另外說一下,其實 NSMutableString 的實現並不難,就好比之前我們在使用的時候會採取 name = [name stringByAppendingString:] 這樣的形式一樣,並不是說這個過程就沒有產生新的字串對象,而是 Cocoa 將這個過程封裝起來的,我們在用的時候代碼更加簡潔了,同時 Cocoa 又提供了一些新的方法供我們使用。
格式化的字串
格式化的字串在各個程式設計語言中都有,基本上就是採用一個替換符來按照自訂的格式在字串中插入變數。格式化字串的作用還是挺大的,並且從形式上來講也是相當靈活的。具體的代碼如下:
int magicNumber = ...NSString *magicString = [NSString stringWithFormat:@"The magic number is %i", magicNumber];
數字可以用 NSNumber 類的執行個體來表示
實際上 NSNumber 類可以用來表示 C 語言中的基本類型,包括 char, double, float, int, long, short 以及相應的 unsigned 變數,另外 OC 中的 BOOL 值也是可以用 NSNumber 來表示的。同樣地,NSNumber 的執行個體也可以採用不同的執行個體化方法:
NSNubmer *magicNumber = [[NSNumber alloc] initWithInt:42];NSNumber *unsignedNumber = [[NSNumber alloc] initWithUnsignedInt:42u];NSNumber *longNumber = [[NSNumber alloc] initWithLong:42l];NSNumber *boolNumber = [[NSNumber alloc] initWithBOOL:YES];NSNumber *simpleFloat = [NSNumber numberWithFloat:3.14f];NSNumber *betterDouble = [NSNumber numberWithDouble:3.1415926535];NSNumber *someChar = [NSNumber numberWithChar:‘T‘];
對於 NSNumber 類,我們也可以直接使用字面值來初始化:
NSNumber *magicNumber = @42;NSNumber *unsignedNumber = @42u;NSNumber *longNumber = @42l;NSNumber *boolNumber = @YES;NSNumber *simpleFloat = @3.14f;NSNumber *betterDouble = @3.1415926535;NSNumber *someChar = @‘T‘;
這種寫法和直接用Factory 方法來執行個體化是一樣的。
使用 NSValue 來表示其他的值
之前提到的 NSNumber 實際上是 NSValue 的一個子類,它主要的作用是封裝單個值或者一個資料項目。相比 C 語言中的基本類型,NSValue 也可以用於表示指標或者結構體。NSValue 提供了很多Factory 方法讓我們可以直接根據一個結構體來建立一個值,比如 NSRange,具體的用法如下:
NSString *mainString = @"This is a long string";NSRange substringRange = [mainString rangeOfString:@"long"];NSValue *rangeValue = [NSValue valueWithRange:substringRange];
當然,一些自定的結構體也是可以存放在 NSValue 對象中的,比如:
struct MyIntegerFloatStruct aStruct;aStruct.i = 42;aStruct.f = 3.14;NSValue *structValue = [NSValue value:&aStruct withObjCType:@encode(MyIntegerFloatStruct)];
集合基本是對象
在 C 語言中,我們表示集合的方式主要是數組,用於儲存一系列同種類的變數。而在 OC 中,集合主要是 Cocoa 以及 Cocoa Touch 中集合類的執行個體,比如 NSArray, NSSet 以及 NSDictionary。這些類,主要作用就是管理一組對象,這就是說,C 語言中的基本類型是沒有辦法直接放到 OC 的集合中去的,如果要放進去的話,就必須將他們先放到 NSNubmer 或者 NSValue 的執行個體中去,然後再把這些執行個體添加到集合中去。
對於 OC 中的集合,需要注意的就是它們儲存的並不是對象的拷貝,而是對象本身,並且是採用強引用的形式儲存的。所以放在集合中的對象想要釋放,至少得先把集合給釋放掉。另外,由於直接存放對象本身,所以如果我們需要從集合中擷取一個對象並執行某些操作的時候,就非常方便了。同樣地,NSArray, NSSet, NSDictionary 本身都是不可變的,它們也都有一個可變的子類。
數組
數組和其他兩種集合的不同之處在於,它存放的對象是有序的,就像其他語言中,我們會通過下標訪問一樣,下標就代表著對象之間的順序。
數組的建立
數組的建立和之前提到的一些值一樣,可以通過多種執行個體化的方式來擷取對象:
+ (id)arrayWithObject:(id)anObject;+ (id)arrayWithObjects:(id)firstObject, ...;- (id)initWithObjects:(id)firstObject, ...;
arrayWithObjects 和 initWithObjects 這個兩個方法的獨特之處在於他們的最後一個參數是 nil,所以,如果說我們插入的對象中間有一個對象的值是 nil,那麼最後我們得到的數組只會到這個對象為止,因為編譯器判斷 nil 就是初始化的終點,這個地方是需要注意的。
數組的查詢
對於數組對象,我們可以擷取其中的一些資訊,比如:
NSUInteger numberOfItems = [someArray count];if ([someArray containsObject:someString]) { ...}
以上就是對於數組中對象個數以及是否含有某個對象的判斷,我們還可以通過下標來擷取一個對象,當然,這個時候如果訪問越界的話系統還是會報錯的,所以一般在訪問前還是會做一層判斷:
if ([someArray count] > 0) { NSLog(@"First item is: %@", [someArray objectAtIndex:0]);}
另外,下標訪問其實除了使用 objectAtIndex: 形式之外,我們也可以使用中括弧來進行訪問,這和其他語言中數組的下標訪問是一致的。
數組的排序
之前也有說到了,數組的一個特性就在於它所包含的元素是有序的,這也就意味著我們可以調整其中元素的順序。不過在對數組排序的時候,需要注意一點,數組本身是不可變的,所以排序之後產生的是一個新數組,我們需要用一個對象來儲存結果。打個比方:
NSArray *unsortedStrings = @[@"gammaString", @"alphaString", @"betaString"];NSArray *sortedStrings = [unsortedStrings sortedArrayUsingSelector:@selector(compare:)];
這裡使用的是 NSString 類的一個方法 compare: ,當然我們也可以自訂一個方法來實現排序的功能,不過這個自訂方法需要注意一件事,那就是要根據比較的原則,如果說結果為小那麼就應當返回 NSOrderedAscending,大的話就返回 NSOrderedDescending,相等則返回 NSOrderedSame。
可變數組
可變數組實際上就是數組的一個子類,添加了可變的屬性,並且多了一系列與可變相關的方法。
NSMutableArray *mutableArray = [NSMutableArray array];[mutableArray addObject:@"gamma"];[mutableArray addObject:@"alpha"];[mutableArray addObject:@"beta"];[mutableArray replaceObjectAtIndex:0 withObject:@"epsilon"];
對於可變數組,我們也可以使用 sortUsingSelector: 來進行排序。
集
實際上,在所有用於存放對象的類中,集的概念應該是比較容易讓人感到困惑的。首先,它是無序的,並且,它所包含的對象都是唯一的。一定要說的話,集其實只是單純地表明,它所包含的這些對象可以歸為一類,除此之外,就沒有什麼其他的含義了。至於它的初始化,基本上和數組是一致的。
因為集用的不多,而且它本身確實容易讓人感到困惑,這裡就不多講了,有興趣的可以直接看官方文檔的解釋:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Collections/Articles/Sets.html#//apple_ref/doc/uid/20000136。
字典
字典也是比較特殊的一種集合,因為它不存在有序或者無序的問題,它所包含的內容,是以索引值對的形式呈現的。無論我們是要放入對象,還是要從中擷取對象,都是基於鍵來訪問。關於鍵的設定,最好是用字串作為鍵。當然,使用其他的對象來當做鍵也可以,但是在字典擷取鍵的時候,它拿到的是副本,所以我們用作鍵的對象必須實現了 NSCopying 協議。
建立字典
字典的建立也可以用常規的初始化以及Factory 方法來實現,比如:
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys: someObject, @"anObject", @"Hello, World!", @"helloString", @42, @"magicNumber", someValue, @"aValue", nil];
對於 NSDictionary 這個類,需要注意的就是我們在初始化的時候,都是先傳對象再傳鍵,這和其他的程式設計語言中有所不同,使用的時候一定要注意。
字典的查詢
之前也說過,字典存放的是索引值對,索引值對的訪問模式就是通過鍵來擷取值,也就是下面這樣:
NSNumber *storedNumber = [dictionary objectForKey:@"magicNumber"];
如果說對象找不到,那麼就會返回 nil,以前也有提到過,對 nil 發送訊息是可行的,所以在使用字典的時候需要注意擷取的對象是否為空白的問題。另外,我們也可以通過下標訪問來擷取對象,只是下標中的內容應當是鍵的值。
可變字典
和其他幾個類一樣,蘋果的設計思路是基類為不可變版本,然後為你提供一個可變的子類,字典也有一個可變的版本 MutableNSDictionary,使用這個類的話,我們就可以動態地向字典中添加或者刪除對象。
[dictionary setObject:@"another string" forKey:@"secondString"];[dictionary removeObjectForKey:@"anObject"];
另外,對於集合類,它們在初始化的時候都是以 nil 作為終結,所以如果我們真的有需要在集合中插入一個 nil,那麼必須得傳進去一個對象。蘋果對 nil 的定位是,不存在對象,它是一個特殊值,所以並不是一個實際的對象,那麼如果我們要傳入一個空的對象,就必須使用 NSNull 這個類來擷取一個對象,就像下面這樣:
NSArray *array = @[ @"string", @42, [NSNull null] ];
使用集合來完成資料持久化
一般來說,資料處理完之後,總有一些資料是需要記錄的,在 iOS中,我們可以通過檔案系統、歸檔、SQLite 和 CoreData 來實現資料的持久化,這裡不會詳細說明資料持久化,只是說明一些這些集合類在持久化的過程中可以起到的作用。
使用 NSArray 和 NSDictionary 來進行資料持久化非常簡單,比如:
NSURL *fileURL = ...NSArray *array = @[@"first", @"second", @"third"];BOOL success = [array writeToURL:fileURL atomically:YES];if (!success) { // an error occured...}
同時,對於磁碟上的檔案,如果它們儲存的資料對象是屬性清單類型中的一種,那麼就可以直接通過一個檔案把它們轉換成運行時的對象:
NSURL *fileURL = ...NSArray *array = [NSArray arrayWithContentsOfURL:fileURL];if (!array) { // an error occurred...}
最後說一下關於集合類的遍曆,對於蘋果提供的這幾個集合類,我們都可以使用 for in 的形式來搞定,不過對於字典,for in 中迴圈的變數是 key,所以在定義 key 的時候最好是採用可以標識不同對象的某一個類,這樣在遍曆的時候會方便很多。
Programming with Objective-C(五)