#objective-c基础教程
框架是一种聚集在一个单元的部件集合,包含头文件、库、图像、声音文件等。苹果公司将Cocoa、Carbon、QuickTime、OpenGL等技术作为框架集提供。Cocoa的组成部分有Foundation和Application Kit框架。还有一个支持框架的套件,包含Core Animation和Core Image。
Foundation框架头文件目录:/System/Library/Frameworks/Foundation.framework/Headers
NSSpeech-Synthesizer使你听到语音。
Objective-C中的BOOL实际上是一种对带符号的字符类型(signed char)的定义(typedef),它使用8位存储空间,YES定义为1,而NO定义为0。(使用#define)。在C语言中非零值表示为true(YES),但在Objective-C中并非如此。
XCode项目目录/Users/FlyFire/Library/Developer/Xcode/DerivedData
变量名称必须以字母或者下划线开头,之后可以是任何字母、下划线或者0-9之间的数字组合。
分类(category)机制允许以模块方式向现有类定义添加新方法,也就是,不必经常给同一接口和实现文件增加新定义。当想要对你没有源代码访问权限的类添加新定义时,这特别方便。
@class ClassName指令提高了效率,因为编译器不需要处理整个ClassName.h文件,而只需知道ClassName是一个类名字。如果需要引用ClassName类中方法,@class指令是不够的,因为编译器需要知道方法中有多少参数、参数类型、方法返回类型等信息。可以直接#import "ClassName.h"。
Foundation框架包含几个所谓的抽象类。Foundation的NSNumber类是为了将数字作为对象处理而创建的抽象类。整数与浮点数字通常有不同的内存需求。每种数字类型都有单独的NSNumber子类。因为这些子类与他们的抽象超类不同,这些子类是具体存在的,他们名为具体子类。每个具体子类属于NSNumber类,总起来名为簇(cluster)。向NSNumber类发送消息来创建新的整数对象时,使用合适的子类位整数对象分配必须的空间,并正确的设置其值。这些子类是私有的,无法直接访问这些子类,只能通过抽象的超类间接访问。抽象超类提供了处理所有数字对象类型的公共接口,无需了解存储在数字对象中的数字类型以及如何设置和检索该值。
多态使得能够开发以下程序:来自不同类的对象可以定义共享相同名称的方法。动态类型能使程序知道运行时才确定对象所属的类;动态绑定则能使程序直到执行时才确定要对对象调用的实际方法。
处理动态类型的方法:-(BOOL) isKindOf:Class-Object -(BOOL)isMemberOfClass:Class-Object -(BOOL) respondsToSelector:selector +(BOOL) instancesRespondToSelector:selector +(BOOL) isSubclassOfClass:class-object -(id) performSelector:selector -(id)performSelector:selector withObject:object -(id)performSelector:selector withObject:object1 withObject:object2
class-object是一个类对象(通常是由class方法产生)selector是一个SEL类型的值(通常是有@selector指令产生)。
@try{
statement
statement
…
}
@catch(NSException *e){
statement
statement
…
}
在@try中加入这些statement后,程序正常执行。但是,如果块中的某一条语句抛出异常,执行不会终止,而是立即跳到@catch块,在那里继续执行。在@catch块内可以处理异常。
在接口部分声明实例变量时,可以把以下3个指令放在实例变量之前,以便更精确地控制其作用域,@protected指令后面的实例变量可以被该类以及任何子类中定义的方法直接访问。这是默认的情况,@private指令后面的实例变量可被定义在该类中的方法直接访问,但是不能被子类中定义的方法直接访问,@public指令后面的实例变量可被该类中定义的方法直接访问,也可被其他类或模块中定义的方法直接访问。
编译器允许你对程序中值不变的变量设置const特性。这样,就告诉编译器,指定的变量在程序运行期间都有恒定的值。在初始化变量之后,如果尝试给const变量指派一个值,或试图将其增1或减1,编译器就会给出警告信息。必须在定义的时候初始化const变量。volatile类型和const类型正好相反,它明确的告诉编译器,指定类型变量的值会改变。在语言中加入这个关键字是为了防止编译器优化掉看似多余的变量赋值,同时避免重复地检查值没有变化的变量。
分类提供了一种简单的方式,用它可以将类的定义模块化到相关方法的组或分类中,他还提供了扩展现有类定义的简便方式,并且不必访问类的源代码,也无需创建子类,分类是一个功能强大且简单的概念。
#import "Fraction.h"
@interface Fraction (MathOps)
-(Fraction *) add:(Fraction *)f;
-(Fraction *) mul:(Fraction *)f;
-(Fraction *) sub:(Fraction *)f;
-(Fraction *) div:(Fraction *)f;
@end
@implementation Fraction (MathOps)
// code for category methods
…
@end
尽管分类可以访问原始类的实例变量,但是它不能添加自身的任何变量。如果需要添加变量,可以考虑创建子类。另外,分类可以重载该类中的另一个方法,但是通常认为这种做法是拙劣的设计习惯。其一,重载了一个方法之后,再也不能访问原来的方法。因此,必须小心地将被重载方法中的所有功能复制到替换方法中,如果确实需要重载方法,正确的选择可能是创建子类。如果在子类中重载方法,仍然可以通过super发送消息来引用父类的方法。因此,不必了解要重载方法的复杂内容,就能够调用父类的方法,并向子类的方法添加自己的功能。
和一般接口部分不同的是,不必实现分类中的所有方法。这对于程序扩展很有用,因为可以在该分类中声明所有方法,然后在一段时间之后才实现它。通过使用分类添加新方法来扩展类不仅会影响这个类,同时也会影响它的所有子类。例如,如果为NSObject添加新方法,就存在潜在的危险性,因为每个类都将继承这些方法,无论是否需要。
协议是多个类共享的一个方法列表,协议中列出的方法没有相应的实现。
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
@interface AddressBook:NSObject <NSCopying>
…
@end
@protocol Drawing
-(void) paint;
-(void) erase;
@optional
-(void) outline;
可以使用conformsToProtocol:方法检查一个对象是否遵循某项协议。
id currentObject;
if ([currentObject conformsToProtocol:@protocol(Drawing)]==YES){
//code goes here
…
}
通过在类型名称之后的尖括号中添加协议名称,可以借助编译器的帮助来检查变量的一致性,如下所示:id<Drawing> currentObject告知编译器currentObject将包含遵守Drawing协议的对象,如果向currentObject指派静态类型的对象,这个对象不遵守Drawing协议,编译器将发出警告信息。
协议可以扩展现有协议定义。分类方法也可以采用协议。和类名一样,协议名必须是唯一的。
自动释放池可以自动释放添加到该池中的对象所使用的内存。向对象发送一条autorelease消息时,就将该对象放到这个池中。释放这个池时,添加到该池中的所有对象也会一起释放。因此,所有这样的对象都会被销毁,除非已经指明这些对象所在的作用域超出自动释放池(如,使用引用计数指明)。
NSInteger不是一个对象,而是基本数据类型的typedef,它被typedef成64位的long或者32位的int。
使用isEqualToNumber:方法根据数值比较两个NSNumber对象,返回Boolean值,以查看这两个值是否相等。使用compare:方法来测试一个数值型的值是否在数值上小于、等于或大于另一个值。[intNumber compare:otherIntNumber],在intNumber中的值小于otherIntNumber中的值时,返回值NSOrderedAscending;如果两个数相等返回值NSOrderedSame;如果大于返回值NSOrderedDescending;返回值可以保存在NSComparisonResult中。
C-String由char字符组成,NSString对象由unichar字符组成。NSString一些方法需要制定范围来确定子字符串。范围包括开始索引数和字符计数,索引数以0开始,因此使用数字对{0,3}制定字符串中前3个字符。NSString类(和其他的Foundation类)中的一些方法使用了特殊的数据类型NSRange来创建范围指定。它定义在<Foundation/NSRange.h>中,实际上,它是结构的typedef定义,该结构包含location和length两个成员。
-(void) setName:(NSString *)theName
{
[name release];
name = [[NSString alloc] initWithString:theName];
}
-(NSString *)name
{
return name;
}
-(void) dealloc
{
[name release];
[super dealloc];
}
与@property(copy,nonatomic) NSString *name生成的方法一致。copy属性将在setter方法内生成实例变量的副本,默认行为不会生成副本,而是仅仅执行分配(为默认的assign属性),nonatomic属性指明在返回值之前,getter方法不会保留或自动释放实例变量。
@interface NSArray
+(id)arrayWithObjects:obj1,obj2,…nil
-(BOOL)containsObject:obj
-(NSUInteger)count
-(NSUInteger)indexOfObject:obj
-(id)objectAtIndex:i
-(void)makeObjectsPerformSelector:(SEL)selector
-(NSArray*)sortedArrayUsingSelector:(SEL)selector
-(BOOL)writeToFile:path automatically:(BOOL)flag
@end
@interface NSMutableArray:NSArray
+(id)array
+(id)initWithCapacity:size
-(id)initWithCapacity:size
-(void)addObject:obj
-(void)insertObject:obj atIndex:i
-(void)replaceObjectAtIndex:i withObject:obj
-(void)removeObject:obj
-(void)removeObjectAtIndex:i
-(void)sortUsingSelector:(SEL)selector
@end
@interface NSDictionary:NSObject
+(id)dictionaryWithObjectsAndKeys:obj1,key1,obj2,key2,…nil
-(id)initWithObjectsAndKeys:obj1,key1,obj2,key2,…,nil
-(NSUInteger)count
-(NSEnumerator*)keyEnumerator
-(NSArray*)keysSortedByValueUsingSelector:(SEL)selector
-(NSEnumerator*)objectEnumerator
-(id)objectForKey:key
@end
@interface NSMutableDictionary:NSDictionary
+(id)dictionaryWithCapacity:size
-(id)initWithCapacity:size
-(void)removeAllObjects
-(void)removeObjectForKey:key
-(void)setObject:obj forKey:key
@end
@interface NSSet
+(id)setWithObjects:obj1,obj2,…,nil
-(id)initWithObjects:obj1,obj2,…,nil
-(NSUInteger)count
-(BOOL)containsObject:obj
-(BOOL)member:obj //使用isEqual:方法确定集合是否包含obj
-(NSEnumerator*)objectEnumerator
-(BOOL)isSubsetOfSet:nsset //确定receiveer的每个成员释放都出现在nsset中
-(BOOL)intersectsSet:nsset //确定是否receiver中至少一个成员出现在对象nsset中
-(BOOL)isEqualToSet:nsset //确定两个集合是否相等
@end
@interface NSMutableSet:NSSet
-(id)setWithCapacity:size
-(id)initWithCapacity:size
-(void)addObject:obj
-(void)removeObject:obj
-(void)removeAllObjects
-(void)unionSet:nsset
-(void)minusSet:nsset
-(void)intersectSet:nsset
@end
Foundation框架同样提供了一个名为NSCountedSet的类,这些集合中同一对象可出现多次,然而,并非在集合中多次存放这个对象,而是维护一个次数计数。countForObject:方法用于在集合中检索某个对象的count值。
Foundation框架允许利用文件系统对文件或目录执行基本操作。这些基本操作由NSFileManager类提供。用NSFileHandle类提供的方法,可以打开文件并对其执行多次读写操作。NSFileHandle类提供的方法也可以用于各种设备或套接字。
@interface NSFileManager
-(NSString*)currentDirectoryPath
-(BOOL)changeCurrentDirectoryPath:path
-(BOOL)contentsAtPath:path
-(BOOL)createFileAtPath:path contents:(BOOL)data attributes:attr //attr是一个NSDictionary对象
-(BOOL)createDirectoryAtPath:path attributes:attr
-(NSArray*)directoryContentsAtPath:path
-(BOOL)removeFileAtPath:path handler:handler //handler是一个回调函数,允许使用自己的方式来处理错误,如果handler指定为nil,就会采取默认的行为
-(BOOL)movePath:from toPath:to handler:handler//重命名或者移动一个文件,to可能是已存在的,可以将整个目录结构(包括目录的内容)从文件系统的一个位置移动到另一个位置
-(BOOL)copyPath:from toPath:to handler:handler//复制文件,to不能是已存在的
-(BOOL)contentsEqualAtPath:path1 andPath:path2
-(BOOL)fileExistsAtPath:path
-(BOOL)isReadableFileAtPath:path
-(BOOL)isWritableFileAtpath:path
-(NSDictionary*)fileAttributesAtPath:path traverseLink:(BOOL)flag//获取文件的属性
-(NSDirectoryEnumerator*)enumeratorAtPath:path
-(BOOL)changeFileAttributes:attr atPath:path //更改文件的属性
@end
NSFileManager *fm=[NSFileManager defaultManager];
属性字典允许指定要创建的文件的权限,以便获取或者更改现有文件的信息。对于文件的创建,如果将该参数指定为nil,就会为该文件设置默认权限。fileAttributesAtPath:traverseLink:方法返回一个包含指定文件属性的字典。对于符号链接(symbolic link),traverseLink:参数的值为YES或者NO。如果该文件是一个符号链接,指定为YES,并且返回链接到的文件属性。如果指定NO,则返回链接本身的属性。对于现有的文件,属性字典包括各种信息,如文件的所有者、文件大小、文件的创建日期等等。字典的每个属性都可以根据其键来提取,所有键都在<Foundation/NSFileManager.h>中定义。
有时需要获得目录的内容列表,使用enumeratorAtPath:方法或者directoryContentsAtPath:方法都可以完成枚举过程。如果使用第一种方法,一次可以枚举指定目录中的每个文件,默认情况下,如果其中一个文件为目录,那么也会递归枚举它的内容。在这个过程中,通过向枚举对象发送一条skipDescendats消息,可以动态阻止递归过程,从而不再枚举目录中的内容。对于directoryContentsAtPath:方法,使用这个方法,可以枚举指定目录的内容,并在一个数组中返回目录列表,如果这个目录中的任何文件本身是个目录,这个方法并不递归枚举他的内容。
使用文件时,需要频繁地将数据读入一个临时存储区,它通常称为缓冲区。当收集数据以便随后将这些数据输出到文件中时,通常也是用存储区。Foundation的NSData类提供了一种简单的方式,它用来设置缓冲区、将文件的内容读入缓冲区,或将缓冲区的文件写入到一个文件。
NSFileManager *fm = [NSFileManager defaultManager];
NSDirectoryEnumerator *dirEnum=[fm enumeratorAtPath:path];
while((path = [dirEnum nextObject]) != nil){
NSLog("%@",path);
[fm fileExistsAtPath:path isDirectory:&flag];
if(flag == YES)
[dirEnum skipDescendents];
}
NSPathUtilities.h包含了NSString的函数和分类扩展,它允许你操作路径名。应该尽可能使用这些函数,以便程序更独立于文件系统结构以及特定文件和目录的位置。
NSString *tmpdir = NSTemporaryDirectory();
NSString *homedir = NSHomeDirectory();
NSArray *components = [homedir pathComponents];//divide a path into its components
常用的路径工具方法
+(NSString*)pathWithComponents:components
-(NSString*)pathComponents
-(NSString*)lastPathComponent
-(NSString*)pathExtension
-(NSString*)stringByAppendingPathComponents:path
-(NSString*)stringByAppendingPathExtension:ext
-(NSString*)stringByDeletingPathExtension
-(NSString*)stringByResolvingSymlinksInPath
-(NSString*)stringByStandardizingPath
常用的路径工具函数
NSString *NSUserName(void)
NSString *NSFullUserName(void)
NSString *NSHomeDirectory(void)
NSString *NSHomeDirectoryForUser(NSString* user)
NSString *NSTemporaryDirectory(void)
Foundation函数
FOUNDATION_EXPORT NSArray * NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);可用于查找系统的特殊目录,如Application目录。
NSProcessInfo类中包含一些方法,它们允许设置或检索正在允许的应用程序(进程)的各种类型的信息。
@interface NSProcessInfo
+(NSProcessInfo *)processInfo//返回当前进程的信息
-(NSArray*)arguments
-(NSDictionary*)environment
-(int)processIdentifier
-(NSString*)processName
-(NSString*)globallyUniqueString
-(NSString*)hostname
-(NSString*)operatingSystemName
-(unsigned int)operatingSystem
-(NSString*)operatingSystemVersionString
-(void)setProcessName:(NSString*)name
@interface NSFileHandle
+(NSFileHandle*) fileHandleForReadingAtPath:path
+(NSFileHandle*) fileHandleForWritingAtPath:path
+(NSFileHandle*) fileHandleForUpdatingAtPath:path
-(NSData*)availableData
-(NSData*)readDataToEndOfFile
-(NSData*)readDataOfLength:(unsigned int)bytes
-(void)writeData:data
-(unsigned long long)offsetInFile
-(void)seekToFileOffset:offset
-(unsigned long long)seekToEndOfFile
-(void)truncateFileAtOffset:offset
-(void)closeFile
@end
程序中可以有多个自动释放池,并且它们是可以嵌套的。如果在程序中产生了大量的临时对象,可能需要在程序中创建多重自动释放池。
NSAutoreleasePool *tmpPool;
…
for(i=0;i<n;i++){
tmpPool=[[NSAutoReleasePool alloc] init];
… //lots of work with temporary objects here
[tmpPool drain];
}
自动释放池并不包含实际的对象本身,仅仅是对释放池时要释放的对象的引用,通过对对象发送autorelease消息可以将对象放入自动释放池中。
创建对象时,将它的引用次数设置为1。每一次必须保持该对象时,发送一条retain消息,使其引用次数加1。Foundation框架提供的其他一些方法也可以增加对象的引用次数,例如,把对象添加到数组中时。不再需要对象时,可以发送release消息,使对象的引用次数减1。当对象的引用次数达到0时,系统就知道不再需要这个对象(因为在理论上它不再被引用),因此系统就会释放dealloc它的内存。系统会自动处理一些对象的引用次数,但并不是对所有的对象都是如此。通过向对象发送retainCount消息,可以获得这个对象的引用计数。
自动释放池用于在释放池本身时自动释放池中的对象,每次释放池时,系统通过向池中的每一个对象发送一条release消息来实现这个功能。同时,系统会向池中每个引用计数为0的对象发送一条dealloc消息来销毁这个对象。
将对象添加到任何类型的集合都会使该对象的引用次数增加。从任何集合中删除对象都能够使其引用计数减小。将对象赋值给另一个变量,使其指向同一片内存区域并不会使引用计数自动增加,因此必须自己增加对象的引用计数,这个规则同样适用于Foundation对象。
内存中常量字符串的空间分配与其他对象不同,它们没有引用计数机制,因为永远不能释放这些对象,向这些对象发送retainCount消息,将返回UINT_MAX。
Foundation类实现了名为copy和mutableCopy的方法,可以使用这些方法创建对象的副本。通过实现一个符合协议(用于制作副本)的方法来完成此任务。如果类必须区分要产生的对象是可变副本还是不可变副本,还需要根据协议实现一个方法。
使用mutableCopy方法复制数组时,在内存中为新的数组对象分配了空间,并且将单个数组元素复制到新数组中。然而将原始数组中每个元素复制到新位置意味着:仅将引用从一个数组元素复制到另一个数组元素。这样做的最终结果,就是这两个数组中的元素都指向内存中的同一片区域。要为数组中每个元素创建完全不同的副本,需要执行所谓的深复制。这就意味着要创建数组中每个对象内容的副本,而不仅是这些对象的引用的副本。然而使用Foundation类的copy或mutableCopy方法时,深复制并不是默认执行的。
-(id) copyWithZone:(NSZone*)zone
{
Fraction *newFract = [[[self class]] allocWithZone:zone] init];
[newFract setTo:numerator over:denominator] ;
return newFract;
}
参数zone与不同的存储区有关,可以在程序中分配并使用这些存储区。只有在编写要分配大量内存的应用程序并且想要通过将空间分配分组到这些存储器中来优化内存分配时,才需要处理这些zone。可以使用传递给copyWithZone:的值,并将它传给名为allocWithZone:的内存分配方法,这个方法在指定的存储区中分配内存。
归档指的是用某种格式来保存一个或多个对象,以便以后还原这些对象的过程。通常,这个过程包括将多个对象写入文件,以便以后读回该文件。属性列表和带键值的编码是两种常用的归档数据的方法。Mac OS X上的应用程序使用XML属性列表(或plists)来存储诸如默认参数选择,应用程序设置和配置信息这样的数据,因此,了解如何创建和读回这些数据很有用。然而,这些列表的归档用途是有限,因为当为某个数据结构创建属性列表时,没有保存特定的对象类,没有存储对同一对象的多个引用,也没有保持对象的可变性。若对象是NSString、NSDictionary、NSArray、NSData或NSNumber对象,可以使用这些类中实现的writeToFile:automically方法将数据写到文件中。在写出某个文件或数组的情况下,该方法可以使用XML属性列表的格式写出数据。
NSDictionary *glossary = …
if([glossary writeToFile:@"glossary" automically:YES]==NO)
NSLog(@"Save to file failed!");
writeToFile:automically:encoding:error:消息被发送给字典对象glossary,使字典以属性列表的形式写到文件glossary中。automically参数被设为YES,表示希望首先将字典写入临时备份文件中,并且一旦成功,将把最终数据转移到名为glossary的指定文件中。这是一种安全措施,它保护文件在一些情况下(如系统在执行操作过程中崩溃)免受破坏。在这种情况下,原始的glossary文件(若文件已经存在)不会受到损害。
根据字典创建属性列表时,字典中的键必须全都是NSString对象。数组中的元素或字典中的值可以是NSString、NSArray、NSDictionary、NSData、NSDate或NSNumber对象。要将文件中的XML属性列表读入你的程序,使用dictionaryWithContentsOfFile:或arrayWithContentsOfFile:方法。要读回数据,使用dataWithContentsOfFile:方法,要读回字符串对象,使用stringWithContentsOfFile:方法。
将各种类型的对象存储到文件中,而且不仅仅是字符串、数组和字典类型,有一种更灵活的方法,就是利用NSKeyedArchiver类创建keyd档案来完成。iPhoneSDK中没有提供NSArchiver,如果想要在iPhone上使用归档功能,则必须使用NSKeyedArchiver。NSKeyedArchiver类中的archiveRootObject:toFile:方法将NSDictionary对象存储到磁盘上。NSKeyedUnarchiver类的unarchiveObjectWithFile:方法将指定的文件打开并读取文件的内容。可以使用这种方式归档和恢复NSString/NSArray/NSDictionary/NSSet/NSDate/NSNumber/NSData之类的基本Objective-c类对象,这还包括嵌套的对象,如包含字符串,甚至其他数组对象的数组。要归档用户创建出来的对象,用户创建出来的对象需要按照协议,在类定义中添加encodeWithCoder:方法和initWithCoder:方法实现。每次归档程序想要根据指定类编码对象时,都将调用encodeWithCoder:方法,该方法告知归档程序如何进行归档。类似地,每次从指定的类解码对象时,就会调用initWithCoder:方法。一般而言,编码方法应该指定如何归档想要保存的对象中的每个实例变量。对于NSData/NSArray等Foundation类,可以使用encodeObject:forKey:方法,对于基本数据类型可以使用encodeBool:forKey:等方法。initWithCoder:的工作方式正好相反,它使用decodeObject:forKey:来解码Foundation类,使用decodeBool:forKey:等方法来解码基本数据类型。
NSMutableData *dataArea;
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:dataArea];
[archiver encodeObject:myBook forKey:@"myAddrBook"];
[archiver finishEncoding];
if([dataArea writeToFile:@"myArchive" automically:YES] == NO)
NSLog(@"Archiving failed");
NSData *dataArea = [NSData dataWithContentsOfFile:@"myArchive"];
if(!dataArea)
{
NSLog(@"cant read back archive file");
return 1;
}
NSKeyedUnarchiver *unarchiver = [[NSkeyedUnarchiver alloc] initForReadingWithData:dataArea];
AddressBook myBook = [unarchiver decodeObjectForKey:@"myaddrbook"];
[unarchiver finishDecoding];
Foundation框架提供的类用于处理集合、字符串、内存管理、文件系统、存档等。AppKit框架提供的类用于管理试图、窗口、文档和多信息用户界面。
iPhone运行时,系统将自动调用一次applicationDidFinishLaunching:方法,在这里可以初始化实例变量,在屏幕绘制内容并让窗口可见。让窗口可见的操作是通过把makeKeyAndVisible消息发送给窗口来实现的。