Objective-C:C的超集
Objective-Objective-C是C语言的严格超集--任何C语言程序不经修改就可以直接通过Objective-C编译器,在Objective-C中使用C语言代码也是完全合法的。Objective-C被描述为盖在C语言上的薄薄一层,因为Objective-C的原意就是在C语言主体上加入面向对象的特性。
第一个 Objective-C 程序,基于Xcode 4.3.1:
#import <Foundation/Foundation.h> int main(int argc, char *argv[]) { @autoreleasepool { NSLog(@"Hello World!"); } return 0; }
消息传递
C++里,送一个消息给对象(或者说调用一个方法)的语法如下:
obj.method(argument);
Objective-C则写成:
[obj method: argument];
此二者并不仅仅是语法上的差异,还有基本行为上的不同。
这里以一个汽车类(car class)的简单例子来解释Objective-C的消息传递特性:
[car fly];
典型的C++意义解读是"调用car类别的fly方法"。若car类别里头没有定义fly方法,那编译肯定不会通过。但是Objective-C里,我们应当解读为"发提交一个fly的消息给car对象",fly是消息,而car是消息的接收者。car收到消息后会决定如何回应这个消息,若car类别内定义有fly方法就运行方法内之代码,若car内不存在fly方法,则程序依旧可以通过编译,运行期则抛出异常。
此二种风格各有优劣。C++强制要求所有的方法都必须有对应的动作,且编译期绑定使得函数调用非常快速。缺点是仅能借由virtual关键字提供有限的动态绑定能力。Objective-C天生即具备鸭子类型之动态绑定能力,因为运行期才处理消息,允许发送未知消息给对象。可以送消息给整个对象集合而不需要一一检查每个对象的类型,也具备消息转送机制。同时空对象nil接受消息后默认为不做事,所以送消息给nil也不用担心程序崩溃。
字符串
Objective-C提供了一个助记符可以方便地从常量值创建NSString对象。要使用这个助记符,你需要做的全部事情,是在普通的双引号字符串前放置一个@符号,如下面的例子所示:
NSString* myString = @"My String "; NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"]; // 从一个C语言字符串创建Objective-C字符串 NSString* fromCString = [NSString stringWithCString:"A C string" encoding:NSASCIIStringEncoding];
类
Objective-C 的类规格说明包含了两个部分:定义(interface)与实现(implementation)。定义(interface)部分包含了类声明和实例变量的定义,以及类相关的方法。实现(implementation)部分包含了类方法的实际代码。
下图展现了声明一个叫做 MyClass 的类的语法,这个类继承自 NSObject 基础类。类声明总是由 @interface 编译选项开始,由 @end 编译选项结束。类名之后的(用冒号分隔的)是父类的名字。类的实例(或者成员)变量声明在被大括号包含的代码块中。实例变量块后面就是类声明的方法的列表。每个实例变量和方法声明都以分号结尾。
类的定义文件遵循C语言之惯例以.h为后缀,实现文件以.m为后缀。
类声明图
Interface
定义部分,清楚定义了类的名称、数据成员和方法。 以关键字@interface作为开始,@end作为结束。
@interface MyObject : NSObject { int memberVar1; // 实体变量 id memberVar2; //访问权限默认为protected } +(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)。
这里提供了一份意义相近的C++语法对照,如下:
class MyObject : public NSObject { protected: int memberVar1; // 实体变量 void * memberVar2; public: static return_type class_method(); // 类方法(静态方法) return_type instance_method1(); // 实例方法 return_type instance_method2( int p1 ); return_type instance_method3( int p1, int p2 ); }
Objective-C定义一个新的方法时,冒号(:)代表参数传递
- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 方法声明*/ [myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 调用方法*/
这个方法的签名是setColorToRed:Green:Blue:。每个冒号后面都带着一个float类别的参数,分别代表红,绿,蓝三色。
Implementation
实现区块则包含:公开方法的实现,以及定义私有(private)变量及方法。 以关键字@implementation作为区块起头,@end结尾。
@implementation MyObject { int memberVar3; //访问权限默认为private } +(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区块内的变量默认权限为protected,声明于implementation区块的变量则默认为private
创建对象
Objective-C创建对象需通过alloc以及init两个消息。alloc的作用是分配内存,init则是初始化对象。 init与alloc都是定义在NSObject里的方法
MyObject * my = [[MyObject alloc] init];
在Objective-C 2.0里,若创建对象不需要参数,则可直接使用new
MyObject * my = [MyObject new];
方法
方法声明:
调用方法:
消息被中括号( [ ] )包括。中括号中间,接收消息的对象在左边,消息(包括消息需要的任何参数)在右边。例如,给myArray变量传递消息insertObject:atIndex:消息,你需要使用如下的语法:
[myArray insertObject:anObj atIndex:0];
为了避免声明过多的本地变量保存临时结果,Objective-C允许你使用嵌套消息。每个嵌套消息的返回值可以作为其他消息的参数或者目标。例如,你可以用任何获取这种值的消息来代替前面例子里面的任何变量。所以,如果你有另外一个对象叫做myAppObject拥有方法,可以访问数组对象,以及插入对象到一个数组,你可以把前面的例子写成如下的样子:
[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];
虽然前面的例子都是传递消息给某个类的实例,但是你也可以传递消息给类本身。当给类发消息,你指定的方法必须被定义为类方法,而不是实例方法。你可以认为类方法跟C++类里面的静态成员有点像(但是不是完全相同的)。
下面的例子演示了一个类方法如何作为类的工厂方法。在这里,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协议提供的指针算术运算所代替了。