Objective-C代码的文件扩展名
扩展名 | 内容类型 |
.h | 头文件。头文件包含类,类型,函数和常数的声明。 |
.m | 源代码文件。这是典型的源代码文件扩展名,可以包含 Objective-C 和 C 代码。 |
.mm | 源代码文件。带有这种扩展名的源代码文件,除了可以包含Objective-C和C代码以外还可以包含C++代码。仅在你的Objective-C代码中确实需要使用C++类或者特性的时候才用这种扩展名。 |
当你需要在源代码中包含头文件的时候,你可以使用标准的 #include 编译选项,但是 Objective-C 提供了更好的方法。#import 选项和 #include 选项完全相同,只是它可以确保相同的文件只会被包含一次。Objective-C 的例子和文档都倾向于使用 #import,你的代码也应该是这样的。
消息传递
[obj method: argument] 对象调用方法,对象之间互相传递消息。
字符串
NSString类提供了字符串的类包装,包含了所有你期望的优点,包括对保存任意长度字符串的内建内存管理机制,支持Unicode,printf风格的格式化工具,等等。因为这种字符串使用的非常频繁,Objective-C提供了一个助记符可以方便地从常量值创建NSString对象。要使用这个助记符,你需要做的全部事情,是在普通的双引号字符串前放置一个@符号。
NSString* myString = @"my string";
NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"];
类
类是 Objective-C 用来封装数据,以及操作数据的行为的基础结构。对象就是类的运行期间实例,它包含了类声明的实例变量自己的内存拷贝,以及类成员的指针。Objective-C 的类规格说明包含了两个部分:定义(interface)与实现(implementation)。定义(interface)部分包含了类声明和实例变量的定义,以及类相关的方法。实现(implementation)部分包含了类方法的实际代码。
类声明总是由 @interface 编译选项开始,由 @end 编译选项结束。类名之后的(用冒号分隔的)是父类的名字。类的实例(或者成员)变量声明在被大括号包含的代码块中。实例变量块后面就是类声明的方法的列表。每个实例变量和方法声明都以分号结尾。
类声明图
Interface
定义部分,清楚定义了类的名称、数据成员和方法。 以关键字@interface作为开始,@end作为结束。
@interface MyObject : NSObject { int memberVar1; // 实体变量 id memberVar2; } +(return_type) class_method; // 类方法 -(return_type) instance_method1; // 实例方法 -(return_type) instance_method2: (int) p1; -(return_type) instance_method3: (int) p1 andPar: (int) p2; @end
方法前面的 +/- 号代表函数的类型:加号(+)代表类方法(class method),不需要实例就可以调用,与C++ 的静态函数(static member function)相似。减号(-)即是一般的实例方法(instance method)。
Objective-C定义一个新的方法时,名称内的冒号(:)代表参数传递,不同于C语言以数学函数的括号来传递参数。
- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 宣告方法*/ [myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 呼叫方法*/
Interface
实现区块则包含了公开方法的实现,以及定义私有(private)变量及方法。 以关键字@implementation作为区块起头,@end结尾。
@implementation MyObject { int memberVar3; //私有實體變數 } +(return_type) class_method { .... //method implementation } -(return_type) instance_method1 { .... } -(return_type) instance_method2: (int) p1 { .... } -(return_type) instance_method3: (int) p1 andPar: (int) p2 { .... } @end
值得一提的是不只Interface区块可定义实体变量,Implementation区块也可以定义实体变量,两者的差别在于访问权限的不同,Interface区块内的实体变量默认权限为protected,宣告于implementation区块的实体变量则默认为private,故在Implementation区块定义私有成员更匹配面向对象之封装原则,因为如此类别之私有信息就不需曝露于公开interface(.h文件)中。
创建对象
Objective-C创建对象需通过alloc以及init两个消息。alloc的作用是分配内存,init则是初始化对象。 init与alloc都是定义在NSObject里的方法,父对象收到这两个信息并做出正确回应后,新对象才创建完毕。以下为范例:
MyObject * my = [[MyObject alloc] init];
在Objective-C 2.0里,若创建对象不需要参数,则可直接使用new
MyObject * my = [MyObject new];
仅仅是语法上的精简,效果完全相同。
若要自己定义初始化的过程,可以重写init方法,来添加额外的工作。(用途类似C++ 的构造函数constructor)
方法
方法声明包括方法类型标识符,返回值类型,一个或多个方法标识关键字,参数类型和名信息。下图展示 insertObject:atIndex: 实例方法的声明。声明由一个减号(-)开始,这表明这是一个实例方法。方法实际的名字(insertObject:atIndex:)是所有方法标识关键的级联,包含了冒号。冒号表明了参数的出现。如果方法没有参数,你可以省略第一个(也是唯一的)方法标识关键字后面的冒号。本例中,这个方法有两个参数。
方法声明语法
当你想调用一个方法,你传递消息到对应的对象。这里消息就是方法标识符,以及传递给方法的参数信息。发送给对象的所有消息都会动态分发,这样有利于实现Objective-C类的多态行为。也就是说,如果子类定义了跟父类的具有相同标识符的方法,那么子类首先收到消息,然后可以有选择的把消息转发(也可以不转发)给他的父类。
消息被中括号( [ 和 ] )包括。中括号中间,接收消息的对象在左边,消息(包括消息需要的任何参数)在右边。例如,给myArray变量传递消息insertObject:atIndex:消息,你需要使用如下的语法:
[myArray insertObject:anObj atIndex:0];
为了避免声明过多的本地变量保存临时结果,Objective-C允许你使用嵌套消息。每个嵌套消息的返回值可以作为其他消息的参数或者目标。例如,你可以用任何获取这种值的消息来代替前面例子里面的任何变量。所以,如果你有另外一个对象叫做myAppObject拥有方法,可以访问数组对象,以及插入对象到一个数组,你可以把前面的例子写成如下的样子:
[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];
与实例方法使用减号作为方法类型标识符不同,类方法使用加号( + )。
下面的例子演示了一个类方法如何作为类的工厂方法。在这里,arrayWithCapacity是NSMutableArray类的类方法,为类的新实例分配内容并初始化,然后返回给你。
NSMutableArray* myArray = nil; // nil 基本上等同于 NULL // 创建一个新的数组,并把它赋值给 myArray 变量 myArray = [NSMutableArray arrayWithCapacity:0];
属性
属性是用来代替声明存取方法的便捷方式。属性不会在你的类声明中创建一个新的实例变量。他们仅仅是定义方法访问已有的实例变量的速记方式而已。暴露实例变量的类,可以使用属性记号代替getter和setter语法。类还可以使用属性暴露一些“虚拟”的实例变量,他们是部分数据动态计算的结果,而不是确实保存在实例变量内的。
实际上可以说,属性节约了你必须要写的大量多余的代码。因为大多数存取方法都是用类似的方式实现的,属性避免了为类暴露的每个实例变量提供不同的getter和setter的需求。取而代之的是,你用属性声明指定你希望的行为,然后在编译期间合成基于声明的实际的getter和setter方法。
属性声明应该放在类接口的方法声明那里。基本的定义使用@property编译选项,紧跟着类型信息和属性的名字。你还可以用定制选项对属性进行配置,这决定了存取方法的行为。下面的例子展示了一些简单的属性声明:
@interface Person : NSObject { @public NSString *name; @private int age; } @property(copy) NSString *name; @property(readonly) int age; -(id)initWithAge:(int)age; @end
属性的访问方法由@synthesize关键字来实现,它由属性的声明自动的产生一对访问方法。另外,也可以选择使用@dynamic关键字表明访问方法会由程序员手工提供。
@implementation Person @synthesize name; @dynamic age; -(id)initWithAge:(int)initAge { age = initAge; // 注意:直接赋给成员变量,而非属性 return self; } -(int)age { return 29; // 注意:并非返回真正的年龄 } @end
属性可以利用传统的消息表达式、点表达式或"valueForKey:"/"setValue:forKey:"方法对来访问。
Person *aPerson = [[Person alloc] initWithAge: 53]; aPerson.name = @"Steve"; // 注意:点表达式,等于[aPerson setName: @"Steve"]; NSLog(@"Access by message (%@), dot notation(%@), property name(%@) and direct instance variable access (%@)", [aPerson name], aPerson.name, [aPerson valueForKey:@"name"], aPerson->name);
为了利用点表达式来访问实例的属性,需要使用"self"关键字:
-(void) introduceMyselfWithProperties:(BOOL)useGetter { NSLog(@"Hi, my name is %@.", (useGetter ? self.name : name)); // NOTE: getter vs. ivar access }
类或协议的属性可以被动态的读取。
int i; int propertyCount = 0; objc_property_t *propertyList = class_copyPropertyList([aPerson class], &propertyCount); for ( i=0; i < propertyCount; i++ ) { objc_property_t *thisProperty = propertyList + i; const char* propertyName = property_getName(*thisProperty); NSLog(@"Person has a property: '%s'", propertyName); }
快速枚举
比起利用NSEnumerator对象或在集合中依次枚举,Objective-C 2.0提供了快速枚举的语法。在Objective-C 2.0中,以下循环的功能是相等的,但性能特性不同。
// 使用NSEnumerator NSEnumerator *enumerator = [thePeople objectEnumerator]; Person *p; while ( (p = [enumerator nextObject]) != nil ) { NSLog(@"%@ is %i years old.", [p name], [p age]); }
// 使用依次枚举 for ( int i = 0; i < [thePeople count]; i++ ) { Person *p = [thePeople objectAtIndex:i]; NSLog(@"%@ is %i years old.", [p name], [p age]); }
// 使用快速枚举 for (Person *p in thePeople) { NSLog(@"%@ is %i years old.", [p name], [p age]); }
快速枚举可以比标准枚举产生更有效的代码,由于枚举所调用的方法被使用NSFastEnumeration协议提供的指针算术运算所代替了。
协议(Protocol)
协议以关键字@protocol作为区块起始,@end结束,中间为方法列表。
@protocol Locking - (void)lock; - (void)unlock; @end
这是一个协议的例子,多线程编程中经常要确保一份共享资源同时只有一个线程可以使用,会在使用前给该资源挂上锁 ,以上即为一个表明有"锁"的概念的协议,协议中有两个方法,只有名称但尚未实现。
下面的SomeClass宣称他采纳了Locking协议:
@interface SomeClass : SomeSuperClass <Locking> @end
一旦SomeClass表明他采纳了Locking协议,SomeClass就有义务实现Locking协议中的两个方法。
@implementation SomeClass - (void)lock { // 實現lock方法... } - (void)unlock { // 實現unlock方法... } @end
由于SomeClass已经确实遵从了Locking协议,故调用端可以安全的发送lock或unlock消息给SomeClass实体变量,不需担心他没有办法回应消息。
插件是另一个使用抽象定义的例子,可以在不关心插件的实现的情况下定义其希望的行为。
动态类型
类似于Smalltalk,Objective-C具备动态类型:即消息可以发送给任何对象实体,无论该对象实体的公开接口中有没有对应的方法。对比于C++这种静态类型的语言,编译器会挡下对(void*)指针调用方法的行为。但在Objective-C中,你可以对id发送任何消息(id很像void*,但是被严格限制只能使用在对象上),编译器仅会发出"该对象可能无法回应消息"的警告,程序可以通过编译,而实际发生的事则取决于运行期该对象的真正形态,若该对象的确可以回应消息,则依旧运行对应的方法。
一个对象收到消息之后,他有三种处理消息的可能手段,第一是回应该消息并运行方法,若无法回应,则可以转发消息给其他对象,若以上两者均无,就要处理无法回应而抛出的例外。只要进行三者之其一,该消息就算完成任务而被丢弃。若对"nil"(空对象指针)发送消息,该消息通常会被忽略,取决于编译器选项可能会抛出例外。
虽然Objective-C具备动态类型的能力,但编译期的静态类型检查依旧可以应用到变量上。以下三种声明在运行时效力是完全相同的,但是三种声明提供了一个比一个更明显的类型信息,附加的类型信息让编译器在编译时可以检查变量类型,并对类型不符的变量提出警告。
下面三个方法,差异仅在于参数的形态:
- setMyValue:(id) foo;
id形态表示参数"foo"可以是任何类的实例。
- setMyValue:(id <aProtocol>) foo;
id<aProtocol>表示"foo"可以是任何类的实例,但必须采纳"aProtocol"协议。
- setMyValue:(NSNumber*) foo;
该声明表示"foo"必须是"NSNumber"的实例。
动态类型是一种强大的特性。在缺少泛型的静态类型语言(如Java 5以前的版本)中实现容器类时,程序员需要写一种针对通用类型对象的容器类,然后在通用类型和实际类型中不停的强制类型转换。无论如何,类型转换会破坏静态类型,例如写入一个"整数"而将其读取为"字符串"会产生运行时错误。这样的问题被泛型解决,但容器类需要其内容对象的类型一致,而对于动态类型语言则完全没有这方面的问题。