您的位置:首页 > 移动开发 > Objective-C

【IOS 开发学习总结-OC-20】★★★objective-c面向对象——协议(protocol)

2015-09-26 10:53 447 查看
协议(protocol)的作用类似于其他语言的接口,用于定义多个类应该遵守的规范。

协议定义了某些类所需遵守的规范,它不关心这些类的内部状态数据和实现细节,只规定这些类必须提供某些方法。提供你呢这些方法的类就可满足实际需要。

协议不提供任何实现

协议体现的是规范和实现分离的设计哲学。让规范与实现分离是一种松耦合设计,这正是协议的好处。

协议不提供任何实现,通常是定义一组公用方法。方法的实现交给类来完成。

使用类别实现非正式协议

为 NSObject 创建类别,创建类别时,就需要实现该类别下的所有方法。这种基于NSObject定义的类别即可认为是非正式协议。

示例程序:

FKApple.h

#import <Foundation/Foundation.h>
#import "NSObject+Eatable.h"

// 定义类的接口部分
@interface FKApple : NSObject
@end


FKApple.m

#import "FKApple.h"

// 为FKApple提供实现部分
@implementation FKApple
- (void) taste
{
    NSLog(@"苹果营养丰富,口味很好!");
}
@end


NSObject +Eatable.h

#import <Foundation/Foundation.h>

// 以NSObject为基础定义Eatable类别
@interface NSObject (Eatable)
- (void) taste;
@end


EatableTest.m

#import <Foundation/Foundation.h>
#import "FKApple.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        FKApple* app = [[FKApple alloc] init];
        [app taste];
    }
}


运行结果:
2015-09-26 11:31:18.230 923[1709:72468] 苹果营养丰富,口味很好!


特别提示:

对于实现非正式协议的类而言,objective-c 并 不强制实现该协议中的所有方法,也就是说,上面的 FKApple 类也可以 不实现 taste 方法。

但是,如果 FKApple 类不实现 taste 方法,且非正式协议本身也没有实现该方法,运行该程序就会引起错误。

错误信息如下:

2015-09-26 12:23:47.369 923[2190:88420] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[FKApple taste]: unrecognized selector sent to instance 0x78e0a930'


注意:虽然 OC 编译器并不强制遵守非正式协议的类必须实现该协议中所有的方法,,但如果该类没有实现协议中的 某个方法,那么程序运行时如果调用该方法,就会引发 unrecognized selector 错误。

正式协议

定义正式协议

和定义类不同,正式协议使用@protocol 关键字。

定义正式协议的基本语法格式如下:

@protocol 协议名<父协议 1,父协议2>
{
    0到多个方法定义....
}


语法说明:

1. 协议名应与类名采用相同的命名规则。——由多个有意义的单词连接而成,每个单词首字母大写,单词间无需任何分隔符。

2. 一个协议可以有多个直接父协议,,但协议只能继承协议,不能继承类。

3. 协议中 定义的方法中只有方法签名,没有方法实现。协议中包含的方法可以是类方法,也可以是实例方法。

4. 协议里所有的方法都是公开的访问权限——协议定义的是多个类共同单单公共行为规范。

5. 协议的继承与类的继承不一样,协议支持多继承——一个协议可以有多个直接的 父协议。与类的继承相似的是:子协议继承多个父协议,将会获得父协议里定义的所有方法。

6. 一个协议继承多个父协议时,多个父协议排在<>之间,彼此间以英文逗号隔开。

实现(遵守)协议

在类定义的接口部分可指定该类继承的父类,以及遵守的协议 。语法如下:

@interface 类名:父类<协议1,协议2...>


一个类可以同时遵守多个协议。

示例代码:

FKOutput.h

#import <Foundation/Foundation.h>

// 定义协议
@protocol FKOutput
// 定义协议的方法
@optional
- (void) output;
@required
- (void) addData: (NSString*) msg;
@end


FKProductable.h

#import <Foundation/Foundation.h>

// 定义协议
@protocol FKProductable
// 定义协议的方法
- (NSDate*) getProduceTime;
@end


FKPrintable.h

#import <Foundation/Foundation.h>
#import "FKOutput.h"
#import "FKProductable.h"

// 定义协议,继承了FKOutput、FKProductable两个协议
@protocol FKPrintable <FKOutput , FKProductable>
@required
// 定义协议的方法
- (NSString*) printColor;
@end


FKPrinter.h

#import <Foundation/Foundation.h>
#import "FKPrintable.h"

// 定义类的接口部分,继承NSObject,遵守FKPrintable协议
@interface FKPrinter : NSObject <FKPrintable>
@end


FKPrinter.m

#import "FKPrinter.h"
#define MAX_CACHE_LINE 10

// 为FKPrinter提供实现部分
@implementation FKPrinter
{
    // 使用数组记录所有需要缓存的打印数据
    NSString* printData[MAX_CACHE_LINE];
    // 记录当前需打印的作业数
    int dataNum;
}
- (void) output
{
    //只要还有作业,继续打印
    while(dataNum > 0)
    {
        NSLog(@"打印机使用%@打印:%@" , self.printColor , printData[0]);
        // 将剩下的作业数减1
        dataNum--;
        // 把作业队列整体前移一位
        for(int i = 0 ; i < dataNum ; i++)
        {
            printData[i] = printData[i + 1];
        }
    }
}
- (void) addData: (NSString*) msg
{
    if (dataNum >= MAX_CACHE_LINE)
    {
        NSLog(@"输出队列已满,添加失败");
    }
    else
    {
        // 把打印数据添加到队列里,已保存数据的数量加1。
        printData[dataNum++] = msg;
    }
}
- (NSDate*) getProduceTime;
{
    return [[NSDate alloc] init];
}
- (NSString*) printColor
{
    return @"红色";
}
@end


如果实现类实现了协议 中所有的方法,这样程序就可以调用该实现类所实现的方法。测试代码如:

FKPrinterTest.m

#import <Foundation/Foundation.h>
#import "FKPrinter.h"

int main(int argc , char * argv[])
{
    @autoreleasepool{
        // 创建FKPrinter对象
        FKPrinter* printer = [[FKPrinter alloc] init];
        // 调用FKPrinter对象的方法
        [printer addData:@"疯狂iOS讲义"];
        [printer addData:@"疯狂XML讲义"];
        [printer output];
        [printer addData:@"疯狂Android讲义"];
        [printer addData:@"疯狂Ajax讲义"];
        [printer output];
        // 创建一个FKPrinter对象,当成FKProductable使用
        NSObject<FKProductable>* p = [[FKPrinter alloc] init];
        // 调用FKProductable协议中定义的方法
        NSLog(@"%@" , p.getProduceTime);
        //创建一个FKPrinter对象,当成FKOutput使用   ①使用协议定义变量
        id<FKOutput> out = [[FKPrinter alloc] init];
        // 调用FKOutput协议中定义的方法  而②使用协议定义变量
        [out addData:@"孙悟空"];
        [out addData:@"猪八戒"];
        [out output];
    }
}


运行结果:

2015-09-26 13:52:03.357 923[2671:111493] 打印机使用红色打印:疯狂iOS讲义
2015-09-26 13:52:03.359 923[2671:111493] 打印机使用红色打印:疯狂XML讲义
2015-09-26 13:52:03.359 923[2671:111493] 打印机使用红色打印:疯狂Android讲义
2015-09-26 13:52:03.360 923[2671:111493] 打印机使用红色打印:疯狂Ajax讲义
2015-09-26 13:52:03.367 923[2671:111493] 2015-09-26 05:52:03 +0000
2015-09-26 13:52:03.368 923[2671:111493] 打印机使用红色打印:孙悟空
2015-09-26 13:52:03.368 923[2671:111493] 打印机使用红色打印:猪八戒


我们看到上面最后一段代码中变量 p 和 out 是使用协议来定义的变量。那么这些变量只能调用该协议中声明的方法,否则编译器 会提示错误。

用协议来定义变量的语法

那么用协议来定义变量的语法是怎样的呢?

用协议来定义变量的语法格式有2种:

1.
NSObject <协议1,协议2..>* 变量;


2.
id < 协议1,协议2...> 变量;


通过上面的语法格式定义的变量,它们的编译时类型仅仅只是所遵守的协议类型,因此只能调用该协议中定义的方法。

正式协议与非正式协议的区别

[thead]
[/thead]
区别非正式协议正式协议
1通过为 NSObject创建类别来实现直接使用@protocol 创建
2遵守(实现)非正式协议通过继承带特定类别的 NSObject 来实现遵守(实现)正式协议必须实现协议中定义的所有方法
3遵守(实现)非正式协议不需实现协议中定义的所有方法遵守(实现)正式协议必须实现定义中所有的方法

正式协议为提高灵活性增加的关键字

为了弥补遵守正式协议必须实现协议的所有方法造成的灵活性不足,OC增加了2个关键字:@optional,@required。

通过在正式协议中使用@optional,@required关键字,正式协议完全可以代替非正式协议的功能。

2个关键字作用如下:

- @optional:位于该关键字后,@required或@end之前声明的方法是可选的——实现类既可以选择实现这些方法,也可以不实现这些方法

- @required:位于该关键字后,@optional或@end之前声明的方法是必须实现的。如果没有实现这些方法,编译器会提示警告。@required是默认的行为。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: