FCon7折倒计时最后一周:日程已上线70%!查看详情>>> 了解详情
写点什么

Objective-C——消息、Category 和 Protocol

  • 2012-06-26
  • 本文字数:4413 字

    阅读完需:约 14 分钟

面向对象永远是个可以吐槽的话题,从开始提出到推崇备至,到充满质疑,一路走来让人唏嘘不已。面向对象的思想可谓历史悠久,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-06-26 00:008032

评论

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

开源一夏 | STM32对接涂鸦wifi模块项目(智能插座-开源)

矜辰所致

开源 stm32 WiFi物联网智能插座 8月月更 涂鸦智能

字节内部MySQL宝典意外流出!堪称数据库的天花板

退休的汤姆

Java、 面经 Java工程师 秋招 MySQL 数据库

从InfluxDB到TDengine,阳光氢能为什么会做出这个选择?

TDengine

数据库 tdengine 时序数据库

如何做好分支管理,保证高效CI/CD?

华为云开发者联盟

git 开发

开源一夏 | 如何在 JavaScript 中创建虚拟键盘

海拥(haiyong.site)

JavaScript 开源 前端 8月月更

教育行业运维审计用什么堡垒机好?有什么作用?

行云管家

网络安全 教育 堡垒机 IT运维 运维审计

开源一夏 |企业内部应用接入钉钉获取部门及人员信息

六月的雨在InfoQ

开源 钉钉 API 钉钉开放平台 8月月更

J2EE进阶(三)struts2 <s:action>标签的用法及Spring在web.xml中的配置

No Silver Bullet

spring Struts2 8月月更 <s:action>

企业引进外部专家合作开发时,如何保证数字资产既开放又安全?

ModelWhale

数字化转型 数据安全 资产安全 技术专家 协同开发

SAP AMDP 介绍 - ABAP 托管的 HANA 数据库过程

Jerry Wang

数据库 SAP abap 8月月更 AMDP

ArkID 企业级开源 IDaaS/IAM 统一身份认证授权管理解决方案

龙归科技

开源项目 iam SSO Idaas

企业数字化转型,如何实现业务部门与算法部门共同探索模型开发优化

ModelWhale

数据分析 工作流 数字化转型 业务思维 协同开发

MSE 费芮新金融行业标杆案例

阿里巴巴中间件

阿里云 微服务 云原生

2022年中国生鲜电商年度综合分析

易观分析

电商 生鲜

MAUI + Masa Blazor 开发带自动更新功能的安卓App

MASA技术团队

.net blazor MASA MAUI Xamarin

云会议玩法升级

sofiya

零故障支持数百场重大会议成功举办,HW云会议做了这些事

科技怪咖

企业如何跨部门实现模型应用全生命周期管理

ModelWhale

数字化转型 应用模型 迭代管理 跨部门沟通 算法模型

企业如何将自身的数字技术及研究成果快速对外发布应用

ModelWhale

数字化转型 部署 应用模型 对外接口 协同开发

SAP Fiori Launchpad Tile,UI5 应用,和 PFCG Role 的对应关系

Jerry Wang

SAP Fiori Launchpad ui5 8月月更

「GitLab篇」如何用Git平台账号登录建木CI

Jianmu

开源 持续集成 CI/CD 持续部署 流水线

即时通讯安全篇(十):IM聊天系统安全手段之通信连接层加密技术

JackJiang

网络安全 https 网络编程 即时通讯 SSL/TLS

【有奖评测局】阿里云容器镜像 ACR 测评团限时招募中!

阿里巴巴中间件

阿里云 云原生 容器镜像

【IT运维】Linux运维需要掌握哪些技能?

行云管家

Linux 运维 linux运维 IT运维

封仲淹:OceanBase社区版4.0未来畅想

OceanBase 数据库

Go-Excelize API源码阅读(十七)——GetPageLayout、SetPageMargins

Regan Yue

Go 开源 源码解析 8月日更 8月月更

ModelBox开发体验:使用YOLOv3做口罩检测

华为云开发者联盟

人工智能 ModelBox

阿里云 EMAS Serverless 重磅发布

hum建应用专家

云原生

Spring Security + Vue + Flowable 怎么玩?

江南一点雨

Java spring springsecurity flowable

发展靠扩大人力规模,而不是技术研发创新,国内软件行业如何破局?

龙归科技

开源项目 Idaas 龙归科技 统一软件市场 ArkID

阿里的职级是如何上升的,是工作经验还是能力?(附阿里面试题)

程序知音

Java 阿里巴巴 java面试 后端技术 八股文

  • 扫码添加小助手
    领取最新资料包
Objective-C——消息、Category和Protocol_Android/iOS_池建强_InfoQ精选文章