Objective-C是C语言的严格超集,--任何C语言程序不经修改就可以直接通过Objective-C编译器,在Objective-C中使用C语言代码也是完全合法的。Objective-C被描述为盖在C语言上的薄薄一层,因为Objective-C的原意就是在C语言主体上加入面向对象的特性。Objective-C的面向对象语法源于Smalltalk消息传递风格。所有其他非面向对象的语法,包括变量类型,预处理器(preprocessing),流程控制,函数声明与调用皆与C语言完全一致。但有些C语言语法合法代码在objective-c中表达的意思不一定相同,比如某些布尔表达式,在C语言中返回值为true,但在Objective-C若与true直接相比较,函数将会出错,因为在Objective-C中true的值只表示为1.
Objective-C中强制要求将类的接口(interface)与实现(implementation)分为两个部分。
类的定义文件遵循C语言之惯例以.h为后缀,实现文件以.m为后缀。
值得一提的是不只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)
- (id) init {
if ( self=[super init] ) { // 必须调用父类的init
// do something here ...
}
return self;
}
协议
协议是一组没有实现的方法列表,任何的类均可采纳协议并具体实现这组方法。
Objective-C在NeXT时期曾经试图引入多重继承的概念,但由于协议的出现而没有实现之。
协议类似于Java与C#语言中的“接口”。在Objective-C中,有两种定义协议的方式:由编译器保证的“正式协议”,以及为特定目的设定的“非正式协议”。
非正式协议为一个可以选择性实现的一系列方法列表。非正式协议虽名为协议,但实际上是挂于NSObject上的未实现分类(Unimplemented Category)的一种称谓,Objetive-C语言机制上并没有非正式协议这种东西,OSX 10.6版本之后由于引入@optional关键字,使得正式协议已具备同样的能力,所以非正式协议已经被废弃不再使用。
正式协议类似于Java中的"接口",它是一系列方法的列表,任何类都可以声明自身实现了某个协议。在Objective-C 2.0之前,一个类必须实现它声明符合的协议中的所有方法,否则编译器会报告错误,表明这个类没有实现它声明符合的协议中的全部方法。Objective-C 2.0版本允许标记协议中某些方法为可选的(Optional),这样编译器就不会强制实现这些可选的方法。
协议经常应用于Cocoa中的委托及事件触发。例如文本框类通常会包括一个委托(delegate)对象,该对象可以实现一个协议,该协议中可能包含一个实现文字输入的自动完成方法。若这个委托对象实现了这个方法,那么文本框类就会在适当的时候触发自动完成事件,并调用这个方法用于自动完成功能。
Objective-C中协议的概念与Java中接口的概念并不完全相同,即一个类可以在不声明它符合某个协议的情况下,实现这个协议所包含的方法,也即实质上符合这个协议,而这种差别对外部代码而言是不可见的。正式协议的声明不提供实现,它只是简单地表明符合该协议的类实现了该协议的方法,保证调用端可以安全调用方法。
语法
协议以关键字@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表示“foo”可以是任何类的实例,但必须采纳“aProtocol”协议。
- setMyValue:(NSNumber*) foo;
该声明表示“foo”必须是“NSNumber”的实例。
动态类型是一种强大的特性。在缺少泛型的静态类型语言(如Java 5以前的版本)中实现容器类时,程序员需要写一种针对通用类型对象的容器类,然后在通用类型和实际类型中不停的强制类型转换。无论如何,类型转换会破坏静态类型,例如写入一个“整数”而将其读取为“字符串”会产生运行时错误。这样的问题被泛型解决,但容器类需要其内容对象的类型一致,而对于动态类型语言则完全没有这方面的问题。
和C++不同,Objective-C不支持运算符重载(它不支持特设多态)。亦与C++不同,但和Java相同,Objective-C只容许对象继承一个类别(不设多重继承)。Categories和protocols不但可以提供很多多重继承的好处,而且没有很多缺点,例如额外执行时间过重和二进制不兼容。
在Objective-C里,selector主要用来做两类事情:
绑定控件触发的动作
@implementation DemoViewController
- (void)downButtonPressed:(id)sender {//响应“按钮被按下事件”的方法
UIButton *button = (UIButton*)sender;
[button setSelected:YES];
}
- (void)drawAnButton {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = _frame;
btn.tag = 1;
btn.backgroundColor = [UIColor clearColor];
[btn addTarget: self
action: @selector(downButtonPressed:)
forControlEvents: UIControlEventTouchUpInside];//当这个按钮被按下时,触发downButtonPressed:方法
}
@end
延时异步执行
@implementation ETHotDealViewController
- (void)viewDidLoad {
//获取数据源
HotDealDataSource *ds = [[HotDealDataSource alloc]init];
[ds reload];
_items = ds.items;
[self performSelector: @selector(refreshTable)
withObject: self
afterDelay: 0.5];//延迟0.5秒调用refreshTable方法
}