ObjC的语法主要基于smalltalk进行设计的,除了提供常规的面向对象特性外,还增加了很多其他特性,这一节将重点介绍ObjC中一些常用的语法特性。当然这些内容虽然和其他高级语言命名不一样,但是我们都可以在其中找到他们的影子,在文章中我也会对比其他语言进行介绍,这一节的重点内容如下:
在ObjC中使用@protocol定义一组方法规范,实现此协议的类必须实现对应的方法。熟悉面向对象的童鞋都知道接口本身是对象行为描述的协议规范。也就是说在ObjC中@protocol和其他语言的接口定义是类似的,只是在ObjC中interface关键字已经用于定义类了,因此它不会再像C#、Java中使用interface定义接口了。
假设我们定义了一个动物的协议AnimalDelegate,人员Person这个类需要实现这个协议,请看下面的代码:
AnimalDelegate.h
// // AnimalDelegate.h // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // //定义一个协议 @protocol AnimalDelegate <NSObject> @required //必须实现的方法 -(void)eat; @optional //可选实现的方法 -(void)run; -(void)say; -(void)sleep; @end
Person.h
// // Person.h // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "AnimalDelegate.h" @interface Person : NSObject<AnimalDelegate> -(void)eat; @end
Person.m
// // Person.m // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "Person.h" @implementation Person -(void)eat{ NSLog(@"eating..."); } @end
这里需要说明几点:
事实上在ObjC中协议的更多作用是用于约束一个类必须实现某些方法,而从面向对象的角度而言这个类跟接口并不一定存在某种自然关系,可能是两个完全不同意义上的事物,这种模式我们称之为代理模式(Delegation)。在Cocoa框架中大量采用这种模式实现数据和UI的分离,而且基本上所有的协议都是以Delegate结尾。
现在假设需要设计一个按钮,我们知道按钮都是需要点击的,在其他语言中通常会引入事件机制,只要使用者订阅了点击事件,那么点击的时候就会触发执行这个事件(这是对象之间解耦的一种方式:代码注入)。但是在ObjC中没有事件的定义,而是使用代理来处理这个问题。首先在按钮中定义按钮的代理,同时使用协议约束这个代理(事件的触发者)必须实现协议中的某些方法,当按钮处理过程中查看代理是否实现了这个方法,如果实现了则调用这个方法。
KCButton.h
// // KCButton.h // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> @class KCButton; //一个协议可以扩展另一个协议,例如KCButtonDelegate扩展了NSObject协议 @protocol KCButtonDelegate <NSObject> @required //@required修饰的方法必须实现 -(void)onClick:(KCButton *)button; @optional //@optional修饰的方法是可选实现的 -(void)onMouseover:(KCButton *)button; -(void)onMouseout:(KCButton *)button; @end @interface KCButton : NSObject #pragma mark - 属性 #pragma mark 代理属性,同时约定作为代理的对象必须实现KCButtonDelegate协议 @property (nonatomic,retain) id<KCButtonDelegate> delegate; #pragma mark - 公共方法 #pragma mark 点击方法 -(void)click; @end
KCButton.m
// // KCButton.m // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCButton.h" @implementation KCButton -(void)click{ NSLog(@"Invoke KCButton's click method."); //判断_delegate实例是否实现了onClick:方法(注意方法名是"onClick:",后面有个:) //避免未实现ButtonDelegate的类也作为KCButton的监听 if([_delegate respondsToSelector:@selector(onClick:)]){ [_delegate onClick:self]; } } @end
MyListener.h
// // MyListener.h // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> @class KCButton; @protocol KCButtonDelegate; @interface MyListener : NSObject<KCButtonDelegate> -(void)onClick:(KCButton *)button; @end
MyListener.m
// // MyListener.m // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "MyListener.h" #import "KCButton.h" @implementation MyListener -(void)onClick:(KCButton *)button{ NSLog(@"Invoke MyListener's onClick method.The button is:%@.",button); } @end
main.m
// // main.m // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "KCButton.h" #import "MyListener.h" int main(int argc, const char * argv[]) { @autoreleasepool { KCButton *button=[[KCButton alloc]init]; MyListener *listener=[[MyListener alloc]init]; button.delegate=listener; [button click]; /* 结果: Invoke KCButton's click method. Invoke MyListener's onClick method.The button is:<KCButton: 0x1001034c0>. */ } return 0; }
我们通过例子模拟了一个按钮的点击过程,有点类似于Java中事件的实现机制。通过这个例子我们需要注意以下几点内容:
属性中的(nonatomic,retain)不是这篇文章的重点,在接下来的文章中我们会具体介绍。
在C#异步编程时我们经常进行函数回调,由于函数调用是异步执行的,我们如果想让一个操作执行完之后执行另一个函数,则无法按照正常代码书写顺序进行编程,因为我们无法获知前一个方法什么时候执行结束,此时我们经常会用到匿名委托或者lambda表达式将一个操作作为一个参数进行传递。其实在ObjC中也有类似的方法,称之为代码块(Block)。Block就是一个函数体(匿名函数),它是ObjC对于闭包的实现,在块状中我们可以持有或引用局部变量(不禁想到了lambda表达式),同时利用Block你可以将一个操作作为一个参数进行传递(是不是想起了C语言中的函数指针)。在下面的例子中我们将使用Block实现上面的点击监听操作:
KCButton.h
// // KCButton.h // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> @class KCButton; typedef void(^KCButtonClick)(KCButton *); @interface KCButton : NSObject #pragma mark - 属性 #pragma mark 点击操作属性 @property (nonatomic,copy) KCButtonClick onClick; //上面的属性定义等价于下面的代码 //@property (nonatomic,copy) void(^ onClick)(KCButton *); #pragma mark - 公共方法 #pragma mark 点击方法 -(void)click; @end
KCButton.m
// // KCButton.m // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "KCButton.h" @implementation KCButton -(void)click{ NSLog(@"Invoke KCButton's click method."); if (_onClick) { _onClick(self); } } @end
main.m
// // main.m // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "KCButton.h" int main(int argc, const char * argv[]) { KCButton *button=[[KCButton alloc]init]; button.onClick=^(KCButton *btn){ NSLog(@"Invoke onClick method.The button is:%@.",btn); }; [button click]; /*结果: Invoke KCButton's click method. Invoke onClick method.The button is:<KCButton: 0x1006011f0>. */ return 0; }
上面代码中使用Block同样实现了按钮的点击事件,关于Block总结如下:
当我们不改变原有代码为一个类扩展其他功能时我们可以考虑继承这个类进行实现,但是这样一来使用时就必须定义成新实现的子类才能拥有扩展的新功能。如何在不改变原有类的情况下扩展新功能又可以在使用时不必定义新类型呢?我们知道如果在C#中可以使用扩展方法,其实在ObjC中也有类似的实现,就是分类Category。利用分类,我们就可以在ObjC中动态的为已有类添加新的行为(特别是系统或框架中的类)。在C#中字符串有一个Trim()方法用于去掉字符串前后的空格,使用起来特别方便,但是在ObjC中却没有这个方法,这里我们不妨通过Category给NSString添加一个stringByTrim()方法:
NSString+Extend.h
// // NSString+Extend.h // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> @interface NSString (Extend) -(NSString *)stringByTrim; @end
NSString+Extend.m
// // NSString+Extend.m // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import "NSString+Extend.h" @implementation NSString (Extend) -(NSString *)stringByTrim{ NSCharacterSet *character= [NSCharacterSet whitespaceCharacterSet]; return [self stringByTrimmingCharactersInSet:character]; } @end
main.m
// // main.m // Protocol&Block&Category // // Created by Kenshin Cui on 14-2-2. // Copyright (c) 2014年 Kenshin Cui. All rights reserved. // #import <Foundation/Foundation.h> #import "NSString+Extend.h" int main(int argc, const char * argv[]) { NSString *name=@" Kenshin Cui "; name=[name stringByTrim]; NSLog(@"I'm %@!",name); //结果:I'm Kenshin Cui! return 0; }
通过上面的输出结果我们可以看出已经成功将@” Kenshin Cui ”两端的空格去掉了。分类文件名一般是“原有类名+分类名称”,分类的定义是通过在原有类名后加上”(分类名)”来定义的(注意声明文件.h和实现文件.m都是如此)。