GMTC全球大前端技术大会(北京站)门票9折特惠截至本周五,点击立减¥480 了解详情
写点什么

Objective-C——消息、Category 和 Protocol

2012 年 6 月 26 日

面向对象永远是个可以吐槽的话题,从开始提出到推崇备至,到充满质疑,一路走来让人唏嘘不已。面向对象的思想可谓历史悠久,20 世纪 70 年代的 Smalltalk 可以说是面向对象语言的经典,直到今天我们依然将这门语言视为面向对象语言的基础。

面向对象是大部分编程语言的基本特性,像 C++、Java、Objective-C 这样的静态语言,Ruby、Python 这样的动态语言都是面向对象的语言。但是如何编写面向对象的程序却一直是困扰人们的话题,即使是 Smalltalk,也有人认为这是一个有缺陷的面向对象的语言实现。

我在 2010 年翻译过的一篇 InfoQ 的文章,《面向对象编程──走错了路》中提到,面向对象编程的三个原则是:基于消息传递机制,对象分离和多态。文章中还举了Erlang 例子,认为Erlang 具备了这些原则,“所以可能是唯一的面向对象语言”。除了之前提到的三个特征,单继承和动态类型也被引用为面向对象语言的“绝对需求”。基于这些考虑,文章指出,Smalltalk 在实现对象思想时的“错误”──例如,只关注状态和行为,在类和基于映像的语言里缺乏良好的并发模型和消息机制。

这篇文章中的核心就是,面向对象思想中除了对象的状态、行为,还应该关注其并发机制、消息机制,后者更为重要。这一点事实上是我在接触了Objective-C 之后才有了更深入的体会。

Ojbective-C 的语法设计主要基于 Smalltalk,除了提供传统的面向对象编程特性之外,还增加了很多类似动态语言 Ruby、Python 才具有的特性,例如动态类型、动态加载、动态绑定等等,同时强化了消息传递机制和表意(Intention Revealing Interface)接口的概念。

消息

消息传递模型(Message Passing)是 Objective-C 语言的核心机制。在 Objective-C 中,没有方法调用这种说法,只有消息传递。在 C++ 或 Java 中调用某个类的方法,在 Objective-C 中是给该类发送一个消息。在 C++ 或 Java 里,类与类的行为方法之间的关系非常紧密,一个方法必定属于一个类,且于编译时就已经绑定在一起,所以你不可能调用一个类里没有的方法。而在 Objective-C 中就比较简单了,类和消息之间是松耦合的,方法调用只是向某个类发送一个消息,该类可以在运行时再确定怎么处理接受到的消息。也就是说,一个类不保证一定会响应接收到的消息,如果收到了一个无法处理的消息,那么程序就是简单报一个错。甚至你可以向一个值为 nil 的空对象发送消息,系统都不会出错或宕掉。这种设计本身也比较符合软件的隐喻。

在表意接口(Intention Revealing Interface)方面,Objective-C 也是设计的比较出色的语言。面向对象语言的特性之一就是通过 API 把实现封装起来,为上层建筑提供服务。但是需要注意的一点就是,你封装的 API 最好能够让调用者看到接口描述就知道怎么使用。如果为了使用一个 API 必须要去研究它的实现,那么就失去了封装的意义。Objective-C 通过显式的 API 描述,让开发者不自觉的写出满足表意接口的 API,比如下图中的 API 描述。

图 1:传统实例方法

上图中描述了一个传统意义的实例方法,但和 Java 或 C++ 不同的是,其方法关键字由多个字符串组成,在这个例子是 insertObject 和 atIndex,(id)anObject 和 (NSUInterger)index 分别表示参数类型和参数名称。整个方法看上去就像一个英语句子,我们可以很容易的知道,这个方法就是在索引为 index 处插入一个对象。如果你是从其他语言转到 Objective-C,那么开始的时候会感觉这种写法有些繁复,但是一旦理解并习惯了你会感受到其巨大的好处,这种写法会强制你写出优美易读的代码和 API,而且有了 XCode 强大的提示功能,再长的方法也是一蹴而就。

下面我们来说说多态和继承。 与 Java 一样,Objective-C 一样不支持多重继承,但是通过类别(Category)和协议(Protocol)可以很好的实现代码复用和扩展。

Category

首先我们来谈谈 Category。

Objective-C 提供了一种与众不同的方式——Catagory,可以动态的为已经存在的类添加新的行为。这样可以保证类的原始设计规模较小,功能增加时再逐步扩展。使用 Category 对类进行扩展时,不需要访问其源代码,也不需要创建子类。Category 使用简单的方式,实现了类的相关方法的模块化,把不同的类方法分配到不同的分类文件中。

实现起来很简单,我们举例说明。

复制代码
SomeClass.h
@interface SomeClass : NSObject{
}
-(void) print;
@end

这是类 SomeClass 的声明文件,其中包含一个实例方法 print。如果我们想在不修改原始类、不增加子类的情况下,为该类增加一个 hello 的方法,只需要简单的定义两个文件 SomeClass+Hello.h 和 SomeClass+Hello.m,在声明文件和实现文件中用“()”把 Category 的名称括起来即可。声明文件代码如下:

复制代码
#import "SomeClass.h"
@interface SomeClass (Hello)
-(void)hello;
@end

实现文件代码如下

复制代码
#import "SomeClass+Hello.h"
@implementationSomeClass (Hello)
-(void)hello{
NSLog (@"name:%@ ", @"Jacky");
}
@end

其中 Hello 是 Category 的名称,如果你用 XCode 创建 Category,那么需要填写的内容包括名称和要扩展的类的名称。这里还有一个约定成俗的习惯,将声明文件和实现文件名称统一采用“原类名 +Category”的方式命名。

调用也非常简单,毫无压力,如下: 首先引入 Category 的声明文件,然后正常调用即可。

复制代码
#import "SomeClass+Hello.h"
SomeClass * sc =[[SomeClass alloc] init];
[sc hello]

执行结果是:

复制代码
name:Jacky

Category 的使用场景:

  1. 当你在定义类的时候,在某些情况下(例如需求变更),你可能想要为其中的某个或几个类中添加方法。
  2. 一个类中包含了许多不同的方法需要实现,而这些方法需要不同团队的成员实现
  3. 当你在使用基础类库中的类时,你可能希望这些类实现一些你需要的方法。

遇到以上这些需求,Category 可以帮助你解决问题。当然,使用 Category 也有些问题需要注意,

  1. Category 可以访问原始类的实例变量,但不能添加变量,如果想添加变量,可以考虑通过继承创建子类。
  2. Category 可以重载原始类的方法,但不推荐这么做,这么做的后果是你再也不能访问原来的方法。如果确实要重载,正确的选择是创建子类。
  3. 和普通接口有所区别的是,在分类的实现文件中可以不必实现所有声明的方法,只要你不去调用它。

用好 Category 可以充分利用 Objective-C 的动态特性,编写出灵活简洁的代码。

Protocol

下面我们再来看 Protocol。

Protocol,简单来说就是一系列不属于任何类的方法列表,其中声明的方法可以被任何类实现。这种模式一般称为代理(delegation)模式。你通过 Protocol 定义各种行为,在不同的场景采用不同的实现方式。在 iOS 和 OS X 开发中,Apple 采用了大量的代理模式来实现 MVC 中 View 和 Controller 的解耦。

定义 Protocol 很简单,在声明文件(h 文件)中通过关键字 @protocol 定义,然后给出 Protocol 的名称,方法列表,然后用 @end 表示 Protocol 结束。在 @end 指令结束之前定义的方法,都属于这个 Protocol。例如:

复制代码
@protocol ProcessDataDelegate <NSObject>
@required
- (void) processSuccessful: (BOOL)success;
@optional
- (id) submitOrder: (NSNumber *) orderid;
@end

以上代码可以单独放在一个 h 文件中,也可以写在相关类的 h 文件中,可以视具体情况而定。该 Protocol 包含两个方法,processSuccessful 和 submitOrder。这里还有两个关键字,@required 和 @optional,表示如果要实现这个协议,那么 processSuccessful 方法是必须要实现的,submitOrder 则是可选的,这两个注解关键字是在 Objective-C 2.0 之后加入的语法特性。如果不注明,那么方法默认是 @required 的,必须实现。

那么如何实现这个 Protocol 呢,很简单,创建一个普通的 Objective-C 类,取名为 TestAppDelegate,这时会生成一个 h 文件和 m 文件。在 h 文件中引入包含 Protocol 的 h 文件,之后声明采用这个 Protocol 即可,如下:

复制代码
@interface TestAppDelegate : NSObject<ProcessDataDelegate>;
@end

用尖括号(<…>)括起来的 ProcessDataDelegate 就是我们创建的 Protocol。如果要采用多个 Protocol,可以在尖括号内引入多个 Protocol 名称,并用逗号隔开即可。例如 <ProcessDataDelegate,xxxDelegate>

m 文件如下:

复制代码
@implementation TestAppDelegate
- (void) processSuccessful: (BOOL)success{
if (success) {
NSLog(@" 成功 ");
}else {
NSLog(@" 失败 ");
}
}
@end

由于 submitOrder 方法是可选的,所以我们可以只实现 processSuccessful。

Protocol 一般使用在哪些场景呢?Objective-C 里的 Protocol 和 Java 语言中的接口很类似,如果一些类之间没有继承关系,但是又具备某些相同的行为,则可以使用 Protocol 来描述它们的关系。不同的类,可以遵守同一个 Protocol,在不同的场景下注入不同的实例,实现不同的功能。其中最常用的就是委托代理模式,Cocoa 框架中大量采用了这种模式实现数据和 UI 的分离。例如 UIView 产生的所有事件,都是通过委托的方式交给 Controller 完成。根据约定,框架中后缀为 Delegate 的都是 Protocol,例如 UIApplicationDelegate,UIWebViewDelegate 等,使用时大家可以留意一下,体会其用法。

使用 Protocol 时还需要注意的是:

1、Protocol 本身是可以继承的,比如:

复制代码
@protocol A
-(void)methodA;
@end
@protocol B <A>
-(void)methodB;
@end

如果你要实现 B,那么 methodA 和 methodB 都需要实现。

2、Protocol 是类无关的,任何类都可以实现定义好的 Protocol。如果我们想知道某个类是否实现了某个 Protocol,还可以使用 conformsToProtocol 进行判断,如下:

复制代码
[obj conformsToProtocol:@protocol(ProcessDataDelegate)]

好吧,具体的语言特性这次就介绍这么多。从某种意义上来说,Objective-C 是一门古老的语言,发明于 1980 年。1988 年,乔布斯的 Next 公司获得了 Objective-C 语言的授权,并开发出了 Objective-C 的语言库和 NEXTSTEP 的开发环境。NextStep 是以 Mach 和 BSD 为基础,Objective-C 是其语言和运行库,后来的事大家都清楚,苹果买了 Next,乔布斯回归苹果,开始神奇的苹果振兴之路,NextStep 成了 Max OS X 的基础。以后发展越来越好,Objctive-C 成了 Apple 的当家语言,现在基本上是 Apple 在维护 Objctive-C 的发展。

在苹果的 AppStore 推出之前,Objective-C 一直相对小众,但是其优秀的语言特性似乎一直在为后面的爆发积蓄力量,当苹果平台级的应用出现之后,Objective-C 开始大放异彩,静态语言的效率和动态语言的特性得到众多程序员的喜爱,目前它已经以火箭般的速度蹿升 TIOBE 语言排行版第四位。

对于喜爱苹果技术的技术人员来说,Objective-C 是你必须深入了解和值得学习的一门语言,希望以后有机会多写一些相关的文章。

原文链接: Objective-C——消息、Category 和 Protocol


感谢崔康对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2012 年 6 月 26 日 00:007727

评论

发布
暂无评论
发现更多内容

测试开发网络篇-网络协议简介

禅道项目管理

软件测试 自动化测试 测试开发

BitMap 转置算法:不一样的 Count 求解方式

GrowingIO技术专栏

BitMap

JavaScript+TensorFlow.js让你在视频中瞬间消失

不脱发的程序猿

JavaScript 人工智能 开源项目 TensorFlow.js

微服务项目部署实践:使用GitLab Runner实现微服务项目的持续集成,持续交付和持续部署

攻城狮Chova

持续集成 gitlab ci 持续部署 5月日更

28天读完349页,这份阿里Java面试通关手册,助我“闯进”字节跳动拿下offer

Crud的程序员

Java 编程 架构

现货量化交易系统开发app,量化马丁策略交易平台搭建

WX13823153201

基于 Qt Quick Plugin 快速构建桌面端跨平台组件

网易云信

音视频 qt

阿里内部百亿级高并发系统(全彩版小册开源):基础篇、数据库篇、缓存篇、消息队列篇、分布式服务篇、维护篇、实战篇;带你从基础到实战

云流

Java 程序员 架构 高并发

无敌!阿里p8大佬强推的这款IDEA高效插件,检测代码漏洞一键修复

java专业爱好者

Java IDEA

520 单身福利|获奖名单公布~

InfoQ写作平台官方

活动专区 520单身福利

分布式RPC框架Dubbo实现服务治理:集成Kryo实现高速序列化,集成Hystrix实现熔断器

攻城狮Chova

dubbo Hystrix Kryo 高速序列化 熔断器

iOS开发底层原理技术~RAC深度解析

ios cocoa 程序员 移动开发

「Adobe国际认证」Adobe Illustrator排版字体设计

Adobe国际认证

架构实战营模块3课后作业-基于“自研集群+MySQL存储”的消息队列架构设计方案

吴建中

架构实战营

Flume的负载均衡load balancer

大数据技术指南

flume 5月日更

新的公链技术领袖?海外社区媒体热议全能公链Thinkium

北熊说链

太空猫社区联盟 Thinkium

腾讯三面落马+拒网易、CVTE后,字节四面成功拿下Java岗offer,你们准备好跳槽吗?

Crud的程序员

Java 编程 架构 java程序员

520 表白,因一个分号被拒

悟空聊架构

520单身福利

Serverless:这真的是未来吗?(二)

Serverless Devs

Serverless 运维 云原生 后端 无服务器

阿里分布式大神亲码“redis核心技术笔记”,没有废话,全是干货!

Java架构追梦

Java redis 阿里巴巴 架构 架构分布式

情指勤一体化管控系统开发方案,大数据情报研判平台建设

WX13823153201

勒索病毒Kraken2.0.7分析

Machine Gun

网络安全 信息安全 渗透测试

看完了京东年薪150万的大佬扔给我的“阿里内部Java 成长笔记”,差距不止一点点

云流

Java 程序员 架构 面试 计算机

膜拜!Github访问量破百万,阿里内部首次公布的Java10W字面经有多强?

云流

Java 程序员 架构 面试

Why WebRTC|前世今生

声网Agora

WebRTC RTC

测试开发专题-开篇

禅道项目管理

软件测试 自动化测试 测试开发

SparkStreaming知识点总结

五分钟学大数据

大数据 5月日更

普通代码块 静态代码块 构造代码块......傻傻分不清

麦洛

Java

HIVE跑个insert into select xxx 为什么CPU飙高

InfoQ_Springup

hadoop

基础设施设施即代码(IaC)平台 Pulumi | 混合云管理利器

郭旭东

基础设施即代码 IaC

聊聊那些小而美的开源搜索引擎

代码先生

搜索引擎 elasticsearch meilisearch

Objective-C——消息、Category和Protocol-InfoQ