ObjC: Foundation Kit
Foundation Kit是什么?
你可以把它看作Java JDK中的java.lang包。不論是JavaME、JavaEE還是Android,各種變種Java環境,都擁有的基礎類庫,或者擁有它的一部分。
Foundation Kit在Mac OS X和iOS開發中都包含。是這兩個環境的最基礎ObjC類庫。比如,下面是Mac環境代碼一般會引用的頭文件:
#import <Cocoa/Cocoa.h>
Cocoa是Mac OS X環境下框架的統稱。
可以通過File>Open quickly打開這個頭文件,發現它又引用了三個頭文件:
#import <Foundation/Foundation.h>?
#import <AppKit/AppKit.h>?
#import <CoreData/CoreData.h>
其中AppKit是Mac OS X下面特定的類庫。CoreData是自動化ORMapping框架,用于數據庫和對象持久化的。
在iOS環境下,不使用Cocoa框架,而是Cocoa touch,一般會導入UIKit頭文件。那么可以推算出,在該頭文件中也導入了Foundation頭文件,是這樣的么?打開UIKit頭文件:
沒有發現,但是有UITouch頭文件,在該文件內部有:
#import <Foundation/Foundation.h>?
#import <CoreGraphics/CoreGraphics.h>?
#import <UIKit/UIKitDefines.h>
?
因此可知,Mac OS X和iOS都使用了Foundation基礎框架。該框架中有很多日后開發常用的API。以下就常用的類和結構做個介紹。
?
NSObjct
NSObject類,是ObjC類族中的根類。NSObject有一些高級特性支持,在靈活和高效開發中十分常用。
perform…方法和selector
selector是ObjC高級的語法特性,可看作C函數指針,或者Java反射API的Method類相關內容。看一下下面的代碼,讀者就應該能理解,首先我有個Book類:
#import <Foundation/Foundation.h>
@interface Book : NSObject {
}
-(void) printInformation;
@end
可以看到有個方法printInformation。一般調用該方法大致是這樣:
Book *book=[[Book alloc] init];?
[book printInformation];
如果使用selector和perform…方法可這樣調用:
Book *book=[[Book alloc] init];?
[book printInformation];
SEL method=@selector(printInformation);?
id object=book;?
[object performSelector:method];
這里,SEL是selector類型。方法可通過這種機制成為變量,傳遞到任意位置調用,可編寫極為靈活和復用性高的代碼。
這里調用的方法沒有參數傳遞,是最簡單的情況,下面演示一下帶參數的,修改Book類的方法:
#import <Foundation/Foundation.h>
@interface Book : NSObject {
}
-(void) printInformation:(NSString *)bookName;
@end
帶一個參數。那么上面的兩種調用方法的代碼將修改為:
Book *book=[[Book alloc] init];?
[book printInformation:@"Objective-C Tutorial"];
SEL method=@selector(printInformation:);?
id object=book;?
[object performSelector:method withObject:@"Objective-C Tutorial"];
如果多個參數呢?比如:
-(void) printInformation:(NSString *)bookName bookPrice:(NSString *)price;
?
那么:
Book *book=[[Book alloc] init];?
[book printInformation:@"Objective-C Tutorial" bookPrice:@"$17.00"];
SEL method=@selector(printInformation:bookPrice:);?
id object=book;?
[object performSelector:method withObject:@"Objective-C Tutorial" withObject:@"$17.00"];
再多的參數,直接這樣調用就不行了。需要借助NSInvocation類。具體實現就不展開說了。
perform…方法調用還有一個方便的機制,就是延時調用。可以看作一次性的timer。比如:
Book *book=[[Book alloc] init];?
[book printInformation:@"Objective-C Tutorial"];
SEL method=@selector(printInformation:);?
id object=book;?
[object performSelector:method withObject:@"Objective-C Tutorial" afterDelay:10];?
NSLog(@"view did load ok.");
可以看到多了個參數,afterDelay,參數值單位是秒。結果:
2011-05-19 15:27:56.930 iOSSimpleDemo[6793:207] Objective-C Tutorial?
2011-05-19?15:27:56.932?iOSSimpleDemo[6793:207] view did load ok.?
2011-05-19?15:28:06.931?iOSSimpleDemo[6793:207] Objective-C Tutorial
從日志可知,帶afterDelay參數的方法調用,將延時10秒鐘再執行。而主線程并不阻塞,而是繼續打印了后面的“view did load ok.”日志。這里的實現原理是,調用afterDelay參數的方法后,將該方法調用存入隊列,主線程在10秒后,再從隊列中取得它并調用。
判斷對象是否相等
這里涉及到兩個方法:
- isEqual
- hash
前者,比如:
BOOL isEqual=[book isEqual:book];?
NSLog(@"book is equals: %@",isEqual?@"yes":@"no");
和Java類似的,相等的對象,它們的hash值必須是相同的。這是因為,hash值將用于依賴hash值的集合元素定位,比如HashSet。hash值不同但相等的對象,將導致在集合中,比如HashSet放入多個。另外,如果自行設置hash值(默認是內存值),不能使用可能變化的值,比如你有個類,User,該類有個屬性,name,如果這個name是可改的,就不要用該屬性的hash值(字符串的hash值)作為User類的hash方法返回值。
?
獲取類對象
有時候,想知道需要得到Class的類對象,可以調用class方法:
NSLog(@"Book class: %@",[book class]);
有時候,想要知道超類,可以比如[book superclass]。
?
管理引用計數
方法比較多:
– retain? required method?
– release? required method?
– autorelease? required method?
– retainCount? required method
這里不細說了,到內存管理部分會詳細講的。
?
有關對象關系,行為等的測試
這些方法,在高級開發中常用。比如,判斷一個類型是不是屬于某個類型:
NSLog(@"book instance is kind of Class Book? %@",?
????????? [book isKindOfClass:[Book class]]?@"yes":@"no");
當然子類也是超類類型。比如:
NSLog(@"book instance is kind of Class Book? %@",?
????????? [book isKindOfClass:[NSObject class]]?@"yes":@"no");
以上兩個語句都會返回yes。
如果要求嚴格判斷一個實例是不是屬于一個類的,可:
NSLog(@"book instance is kind of Class Book? %@",?
????????? [book isMemberOfClass:[NSObject class]]?@"yes":@"no");
?
這里會返回no,因為book是Book類的實例,而不是嚴格意義上的NSObject類的實例。
另外,還有個高級的用法,就是判斷實例是不是代理類。即:
- (BOOL)isProxy
這里先要說說Foundation kit提供的代理類用法,即NSProxy。先寫個Book的代理類。頭文件:
#import <Foundation/Foundation.h>?
#import "Book.h"
@interface BookProxy : NSProxy {?
??? Book *book;?
}
-(id)init;
@property(nonatomic,retain) Book *book;
@end
實現:
#import "BookProxy.h"
@implementation BookProxy
@synthesize book;
-(id)init{?
??? return self;?
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{?
??? [anInvocation setTarget:book];?
????
??? NSLog(@"call proxy instance …");?
??? [anInvocation invoke];?
??? NSLog(@"call proxy instance ok.");?
????
??? return;?
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{?
??? return [book methodSignatureForSelector:aSelector];?
}
@end
調用:
BookProxy *bookProxy=[[BookProxy alloc]init];?
bookProxy.book=book;
book=(Book *)bookProxy;?
[book printInformation:@"Objective-C Tutorial"];
NSLog(@"Book class: %@",[book class]);?
NSLog(@"book instance is kind of Class Book? %@",?
????? [book isKindOfClass:[NSObject class]]?@"yes":@"no");
這里要注意的是,NSProxy類沒有init方法(是NSObject類才有),需要自己寫一個。從BookProxy到Book要做一次強制轉型。之后,使用上就是透明代理了。isKindOfClass會返回yes。isMemberOfClass會返回no。但是,怎么區分是子類還是代理類呢?就用到這個了:
- (BOOL)isProxy
行為,英文是behavior,是動作嘛,一般指的就是方法。有時候,需要判斷一個對象,是否能對一個selector做出回應。比如:
SEL method=@selector(printInformation:);?
id object=book;?
[object performSelector:method withObject:@"Objective-C Tutorial" afterDelay:10];?
NSLog(@"view did load ok.");
NSLog(@"book has this selector: %@",[book respondsToSelector:method]?@"yes":@"no");
這里打印的是yes。
有的時候,想判斷某個實例是否實現了某個協議,可以:
NSLog(@"book implement NSObject protocol: %@",?
?????????[book conformsToProtocol:@protocol(NSObject)]?@"yes":@"no");
還有一些方法,可以查看NSObject類reference學習。
?
常用的結構體
結構體是C提供的一種數據類型。在ObjC中也常使用。比如:
- NSRange
- NSPoint
- NSSize
- NSRect
以NSRange為例:
typedef struct _NSRange {?
??? NSUInteger location;?
??? NSUInteger length;?
} NSRange;
上面代碼是從NSRange頭文件中找到的。
可以用三種方式為結構體賦值。比如:
NSRange range;?
range.location=17;?
range.length=4;
還可以:
NSRange range={17,4};
或者:
NSRange range=NSMakeRange(17, 4);
第三種是比較常用的方式,因為是通過函數調用,可在各種情況下使用。
使用結構體作為數據類型而不是ObjC對象,是從性能上的考慮。后者在動態分配內存上代價較大。
?
NSString
NSString類似Java中的String類,提供了很多便利的對字符串操作的方法。字符串操作在開發中是十分常用的。
?
如何創建字符串
最普通的創建字符串的方式是:
@"hello"
我測試了一下這個:
NSLog(@"print string hash, instance1: %i, instance2: %i",?
????????? [@"hello" hash],[@"hello" hash]);
日志顯示:
print string hash, instance1: -1553534663, instance2: -1553534663
這說明什么呢?兩次創建的對象,是內存中同一個實例。這和Java中的String實例化原理是相同的。即系統要保持一個字符串池。當聲明的字符串在池中已存在了,就只提供一個指向存在字符串的指針。這也是為什么字符串是不可修改對象的原因之一。
字符串還提供了stringWithFormat方法創建可插入參數的字符串:
NSString *content=[NSString stringWithFormat:@"用戶姓名:%@",@"張三"];
這里使用的是類方法,而不是實例方法。可以將這個方法看作簡單工廠模式。
?
字符串長度
得到字符串長度,是常見的操作:
unsigned int length=[content length];
?
字符串的相等
有的時候,需要判斷兩個NSString字符串是否相等。這里有兩個概念:
- 對象不一樣,但是他們的字符值是一樣的
- 對象是同一個,即在內存中同一個區域
這個概念在各種面向對象語言中應該都有。我拿Java舉例子,比如:
String s="hello";
這個語義上和:
NSString *s=@"hello";
是一樣的。即查找字符串內存池,看是否有包含相同字符串的對象,如果有,就不重復創建,而是引用池中的字符串對象。因此,用上面語法,用不同變量名創建多個相同字符串的變量,它們字符串值一樣,而且,hash值也一樣,即在內存中也是同一個對象。
但是Java如果:
String s=new String("hello");
就是強制創建新的對象,那么字符串值一樣,但是內存上不是同一個對象,因為這樣創建不使用字符串池。
問題來了,ObjC怎么寫出這樣的語句?我沒有找到。應該是有的吧。
如果在ObjC中判斷兩個字符串是不是相同的對象,用==即可。如果是判斷字符串值是否相等:
NSString *s=@"hello";?
[s isEqualToString:@"hello"];
建議使用isEqualToString針對字符串值是否相等時使用,而不是==,因為不排除有情況是內存中不是相同對象,但字符串值是相同的情況。
?
字符串的排序
字符串排序,比如按照首字母排序,兩個字符串,比較誰應該排在前面。這時要用到:
- (NSComparisonResult)compare:(NSString *)aString
比如:
NSString *s1=@"Apple";?
NSString *s2=@"Google";?
NSComparisonResult result=[s1 compare:s2];
switch (result) {?
??? case NSOrderedSame:?
??????? NSLog(@"is same string");?
??????? break;?
??? case NSOrderedAscending:?
??????? NSLog(@"ascending");?
??????? break;?
??? case NSOrderedDescending:?
??????? NSLog(@"descending");?
??????? break;?
??? default:?
??????? break;?
}
?
結果是:
ascending
也就是說s1和s2之間的順序是升序,即s1應該排在前面。
另外,可以使用這個方法:
- (NSComparisonResult)compare:(NSString *)aString options:(NSStringCompareOptions)mask
進行特殊的大小比較,mask確定特殊比較的類型:
- 大小寫敏感
- 大小寫不敏感
- 數字排序,這個要特別說一下,數字和文字有不同,比如1和9,1排在9前面,無論是字符還是數字都沒問題。如果100和99,字符比較就會出現100在99之前的問題
?
是否包含子字符串
經常要用到這樣的字符串功能,判斷字符串:
- - (BOOL)hasPrefix:(NSString *)aString,是否某個字符串開始的
- - (BOOL)hasSuffix:(NSString *)aString,是否某個字符串結束的
- - (NSRange)rangeOfString:(NSString *)aString,字符串中是否有子字符串,如果有返回NSRange結構體,包含起始位置和長度,如沒有,range.location==NSNotFound
NSMutableString
可變字符串。NSString是不可變的,類似Java的String;NSMutableString類似Java的StringBuffer。
具體方法使用,見Reference。
?
使用集合
?
NSArray
NSArray是最常用的集合類型。它類似Java中的ArrayList,但是又有所區別,就是一旦創建就不能再改變。
NSArray有兩個限制:
- 不能存儲C語言的基本數據類型,比如int、float、enum、struct等
- 不能存儲nil對象
比較常用的創建NSArray實例的方法是:
+ (id)arrayWithObjects:(id)firstObj, …
其他的看Reference吧。
獲取NSArray的元素數:
- (NSUInteger)count
從指定的位置,比如第2個下標獲取元素,要用到這個:
- (id)objectAtIndex:(NSUInteger)index
結合上面的count方法,可以對NSArray做循環迭代了。
?
NSMutableArray
這個類,允許添加和刪除元素。
添加:
- (void)addObject:(id)anObject
刪除:
- (void)removeObject:(id)anObject
或者根據下標刪除:
- (void)removeObjectAtIndex:(NSUInteger)index
?
NSEnumerator
在Java中有迭代器Iterator,在ObjC中類似功能的類是NSEnumerator。比如:
NSArray *array=[NSArray arrayWithObjects:@"h1",@"h2",nil];?
NSEnumerator *enumerator=[array objectEnumerator];?
NSString *string;?
while (string=[enumerator nextObject]) {?
??? NSLog(@"element: %@",string);?
}
?
快速枚舉
很類似Java中的:
for (String s:array) {?
????
}
?
ObjC提供了這樣的語法支持:
NSArray *array=[NSArray arrayWithObjects:@"h1",@"h2",nil];
for (NSString *string in array) {?
??? NSLog(@"element: %@",string);?
}
?
NSSet和NSMutableSet
類似Java中的HashSet。NSSet是不可變的,而NSMutableSet是可變的。使用和NSArray類似。
?
NSDictionary和NSMutableDictionary
類似Java中的HashMap。
可通過:
- (id)initWithObjectsAndKeys:(id)firstObject , …
初始化帶鍵值對的NSDictionary。比如:
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:?
??? @"value1", @"key1", @"value2", @"key2", nil];
NSMutableDictionary增加了:
- (void)setObject:(id)anObject forKey:(id)aKey
可加入新的鍵值對。和刪除鍵值對:
- (void)removeObjectForKey:(id)aKey
?
各種數值
上面提到了,C語言的基本數據類型是不能存儲到集合里的。需要將這些數據類型封裝到對象中才能放入。遺憾的是,ObjC不像Java或者.net,提供了自動裝箱和拆箱的功能,即編程語言支持自動將基本型包裝成對象,或者相反的操作。ObjC提供了手動的方式。下面介紹一下。
NSNumber
NSNumber提供了很多類似這樣的類方法:
+ (NSNumber *)numberWithInt:(int)value
用來將數值型基本數據類型包裝成ObjC對象。比如,把int型包裝成ObjC的實例:
NSNumber *number=[NSNumber numberWithInt:24];
這個number已經是ObjC對象了,可以放置到集合中去了。
反過來:
int n=[number intValue];
?
NSValue
NSNumber只能包裝數值。NSValue可以包裝任意類型數值,比如將結構體包裝放入到集合中。
下面示例將NSRange結構體放入到NSArray中:
NSRange numberRange={10,29};?
NSValue *value=[NSValue valueWithRange:numberRange];?
NSArray *array=[NSArray arrayWithObjects:@"h1",value,nil];
這里使用的方法valueWithRange,是NSValue為常用結構體提供的便利方法。如果是自己定義的結構體呢?
比如在頭文件中定義了struct:
typedef struct{?
??? NSString *name;?
??? int rank;?
}User;
?
在代碼中創建集合,通過NSValue包裝結構體存入集合,并且從集合中取出結構體:
User user={@"張三",2};?
NSValue *userValue=[NSValue valueWithBytes:&user objCType:@encode(User)];?
NSArray *myArray=[NSArray arrayWithObjects:@"h1",userValue,nil];
userValue=[myArray objectAtIndex:1];?
[userValue getValue:&user];?
NSLog(@"用戶姓名:?%@",user.name);
?
NSNull
nil有特殊含義,因此不能在集合中保存。那么,如果需要存入空的內容,怎么辦呢?
見下面代碼:
NSRange numberRange={10,29};?
NSValue *value=[NSValue valueWithRange:numberRange];?
NSArray *array=[NSArray arrayWithObjects:[NSNull null],value,nil];
那么,檢查是否為null,可以直接用:
if ([array objectAtIndex:0]==[NSNull null]) {?
??? NSLog(@"element is null.");?
}
?
NSDate
在Java里有java.util.Date類,在ObjC中對應的是NSDate。寫個最簡單的示例:
NSDate *date=[NSDate date];?
NSLog(@"time:?%@",date);
將打印出:
time: 2011-05-26 11:44:37 +0800
date方法將返回表示當前時間的NSDate對象。
下面的方法:
- (id)initWithTimeIntervalSinceNow:(NSTimeInterval)seconds
可返回當前時間間隔秒數的日期對象。
下面的方法:
+ (id)dateWithTimeInterval:(NSTimeInterval)seconds sinceDate:(NSDate *)date
返回和指定日期對象間隔時間的日期對象。
下面方法:
- (BOOL)isEqualToDate:(NSDate *)anotherDate
判斷日期是否相等。
下面方法:
- (NSDate *)earlierDate:(NSDate *)anotherDate
返回更早的日期對象。
- (NSComparisonResult)compare:(NSDate *)anotherDate
比較日期大小。類似NSString的compare方法。
- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)anotherDate
返回日期之間間隔的秒數。
?
NSData
在C語言中,經常將數據緩沖區作為參數傳遞給函數。這時需要將緩沖區的指針傳遞給函數。如果是動態分配的緩沖區,那么還要考慮何時釋放內存。
在ObjC中,可以使用NSData簡化緩沖區的使用方式。比如:
char *s="hello everyone";?
NSData *data=[NSData dataWithBytes:s length:strlen(s)+1];?
NSLog(@"data: %s",[data bytes]);
這里char *s不是動態分配內存的,這里只是演示一下,通過NSData的類方法,可以將s的字節數組復制到NSData中。NSData是ObjC的類實例,有內存管理機制,這里按照ObjC內存管理原則應該是已經autorelease了的。
總結
以上是生活随笔為你收集整理的ObjC: Foundation Kit的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: KMP子串匹配算法(Knuth–Morr
- 下一篇: show me