概述

ObjC的语法主要基于smalltalk进行设计的,除了提供常规的面向对象特性外,还增加了很多其他特性,这一节将重点介绍ObjC中一些常用的语法特性。当然这些内容虽然和其他高级语言命名不一样,但是我们都可以在其中找到他们的影子,在文章中我也会对比其他语言进行介绍,这一节的重点内容如下:

  1. 协议protocol
  2. 代码块block
  3. 分类category

协议protocol

在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

这里需要说明几点:

  1. 一个协议可以扩展自另一个协议,例如上面AnimalDelegate就扩展自NSObject,如果需要扩展多个协议中间使用逗号分隔;

  2. 和其他高级语言中接口不同的是协议中定义的方法不一定是必须实现的,我们可以通过关键字进行@required和@optional进行设置,如果不设置则默认是@required(注意ObjC是弱语法,即使不实现必选方法编译运行也不会报错);
  3. 协议通过<>进行实现,一个类可以同时实现多个协议,中间通过逗号分隔;
  4. 协议的实现只能在类的声明上,不能放到类的实现上(也就是说必须写成@interface Person:NSObject<AnimalDelegate>而不能写成@implementation Person<AnimalDelegate>);
  5. 协议中不能定义属性、成员变量等,只能定义方法;

事实上在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中事件的实现机制。通过这个例子我们需要注意以下几点内容:

  1. id可以表示任何一个ObjC对象类型,类型后面的”<协议名>“用于约束作为这个属性的对象必须实现该协议(注意:使用id定义的对象类型不需要加“*”);

  2. MyListener作为事件触发者,它实现了KCButtonDelegate代理(在ObjC中没有命名空间和包的概念,通常通过前缀进行类的划分,“KC”是我们自定义的前缀)
  3. 在.h文件中如果使用了另一个文件的类或协议我们可以通过@class或者@protocol进行声明,而不必导入这个文件,这样可以提高编译效率(注意有些情况必须使用@class或@protocol,例如上面KCButton.h中上面声明的KCButtonDelegate协议中用到了KCButton类,而此文件下方的KCButton类声明中又使用了KCButtonDelegate,从而形成在一个文件中互相引用关系,此时必须使用@class或者@protocol声明,否则编译阶段会报错),但是在.m文件中则必须导入对应的类声明文件或协议文件(如果不导入虽然语法检查可以通过但是编译链接会报错);
  4. 使用respondsToSelector方法可以判断一个对象是否实现了某个方法(需要注意方法名不是”onClick”而是“onClick:”,冒号也是方法名的一部分);

属性中的(nonatomic,retain)不是这篇文章的重点,在接下来的文章中我们会具体介绍。

代码块Block

在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总结如下:

  1. Block类型定义:返回值类型(^ 变量名)(参数列表)(注意Block也是一种类型);

  2. Block的typedef定义:返回值类型(^类型名称)(参数列表)
  3. Block的实现:^(参数列表){操作主体}
  4. Block中可以读取块外面定义的变量但是不能修改,如果要修改那么这个变量必须声明_block修饰;

分类Category

当我们不改变原有代码为一个类扩展其他功能时我们可以考虑继承这个类进行实现,但是这样一来使用时就必须定义成新实现的子类才能拥有扩展的新功能。如何在不改变原有类的情况下扩展新功能又可以在使用时不必定义新类型呢?我们知道如果在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都是如此)。

iOS开发系列--Objective-C之协议、代码块、分类的更多相关文章

  1. iOS开发系列--Swift语言

    概述 Swift是苹果2014年推出的全新的编程语言,它继承了C语言.ObjC的特性,且克服了C语言的兼容性问题.Swift发展过程中不仅保留了ObjC很多语法特性,它也借鉴了多种现代化语言的特点,在 ...

  2. iOS开发系列文章(持续更新……)

    iOS开发系列的文章,内容循序渐进,包含C语言.ObjC.iOS开发以及日后要写的游戏开发和Swift编程几部分内容.文章会持续更新,希望大家多多关注,如果文章对你有帮助请点赞支持,多谢! 为了方便大 ...

  3. iOS开发系列--App扩展开发

    概述 从iOS 8 开始Apple引入了扩展(Extension)用于增强系统应用服务和应用之间的交互.它的出现让自定义键盘.系统分享集成等这些依靠系统服务的开发变成了可能.WWDC 2016上众多更 ...

  4. iOS开发系列--Swift进阶

    概述 上一篇文章<iOS开发系列--Swift语言>中对Swift的语法特点以及它和C.ObjC等其他语言的用法区别进行了介绍.当然,这只是Swift的入门基础,但是仅仅了解这些对于使用S ...

  5. iOS开发系列--网络开发

    概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博.微信等,这些应用本身可能采用iOS开发,但是所有的数据支撑都是基于后台网络服务器的.如今,网络编程越来越普遍,孤立的应用通常是没有生命力 ...

  6. iOS开发系列--让你的应用“动”起来

    --iOS核心动画 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建 ...

  7. iOS开发系列--并行开发其实很容易

    --多线程开发 概览 大家都知道,在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算.可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行.但是机器码是按顺序执行的, ...

  8. iOS开发系列--让你的应用“动”起来

    --iOS核心动画 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建 ...

  9. IOS开发系列 --- 核心动画

    原始地址:http://www.cnblogs.com/kenshincui/p/3972100.html 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥i ...

  10. iOS开发系列--让你的应用“动”起来【转载】

    概览 原文链接:http://www.cnblogs.com/kenshincui/p/3972100.html 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥i ...

随机推荐

  1. Conditional project or library reference in Visual Studio

    Conditional project or library reference in Visual Studio In case you were wondering why you haven’t ...

  2. Xocde一次版本升级遇到的问题 (Code Sign Error)

    因为Xcode对ios版本的支持问题,我对XCode进行了一次升级,导致原来还好的项目代码出现了编译时错误. Code Sign Error failed with exit code 1 问题就在于 ...

  3. eclipse项目上面有个红叉,但是没有任何地方有错误

    eclipse项目上面有个红叉,但是没有任何地方有错误,clear,refresh,重启都试过了,依然没用, 后来我换了一个workspace,编译的时候提示: Description Resourc ...

  4. js获取css中的样式

    众所周知,obj.style只能够获取 <div id="a" style="width:100px;"></div> 结构上的样式 如 ...

  5. echarts之tooltip

    tooltip:提示框,鼠标悬浮交互时的信息提示. 当trigger为'item'时 tooltip : { trigger: 'item' }, 当trigger为'axis'时 tooltip : ...

  6. python学习 2数学公式

    递归 def fact(n): if n <= 1: return 1 else: return n * fact(n - 1) 斐波那契数列: 第0项是0,第1项是1,从第2项开始,每一项都等 ...

  7. 机器学习之K-近邻算法

    机器学习可分为监督学习和无监督学习.有监督学习就是有具体的分类信息,比如用来判定输入的是输入[a,b,c]中的一类:无监督学习就是不清楚最后的分类情况,也不会给目标值. K-近邻算法属于一种监督学习分 ...

  8. 丢掉慢吞吞的AVD吧,android模拟神器:Genymotion

    安装图文介绍: http://padhz.com/how-to-run-android-simulator-on-pc.html; 刚好手头上有visual box,亲自试用了一下.新建了一个Gala ...

  9. 使用HttpClient来异步发送POST请求并解析GZIP回应

    .NET 4.5(C#): 使用HttpClient来异步发送POST请求并解析GZIP回应 在新的C# 5.0和.NET 4.5环境下,微软为C#加入了async/await,同时还加入新的Syst ...

  10. Javascript的自执行函数

    自执行函数其实也就是"立即执行的函数",它有四个特点:提高性能.利于压缩.避免冲突.依赖加载: 1.减少作用域查找 JS代码: // Anonymous function that ...