Objective-c的类目、延展、协议

概述

OC用于拓展已存在类的内置功能是它最强大的功能之一。类目、延展、协议提供了可以让你扩展类功能的方式。使用他们,无需继承便可以扩展类功能。需要注意的是,这些手段只能增加类的方法,并不能用于增加实例变量,要想增加实例变量,还是需要定义子类来实现。

1、类目(Category):指向已知的类,增加新的方法,不会破坏封装性。已知的类既包括已定义的类,也包括系统已有的类。

2、延展(Extension):即通过在自己类的实现文件中添加类目来声明私有方法。

3、协议(Protocol):声明一些方法,但让别的类来实现,也能为类增加方法。

类目 Category

类目简介

类目也称为分类。通过定义类目,你可以为已知的类增加新的方法,哪怕是那些你没有源码的类。这是OC提供的一种强大的功能,使得你不用定义子类就能扩展一个类的功能。使用类目,你还可以将你自己的类的定义分开放在不同的源文件里。

  • 可以为已知的类添加方法,哪怕是你没有源码的类;

  • 通过类目添加方法会成为原始类的一部分,调用与其他方法一致;

  • 与原类中的方法同级;

  • 在父类中添加类目子类会继承该类目中的方法,但在子类中添加类目父类无法拥有这些方法;

  • 把类中的方法归类,可以更好地管理和维护类;

类目的声明和实现

1、类目的命名规则:类名+扩展方法,如“ClassName+CategoryName”。类目不继承于父类,但接口与定义类很相似,用一个括号表示用途。注意定义类目的时候一定要把原类包含进来。

#import "ClassName.h" 

@interface ClassName(categoryName)

// methods declarations

@end

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2、实现

// 注意实现时引入的.h文件
#import "ClassName + CategoryName.h" 

@implementation ClassName(CategoryName)

// methods definitions

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

类目的创建

  • 步骤1:在工程目录中按 command + N > 选择OS X环境 >选择 Objective-C File 模板 >点击Next进入配置界面

    这里写图片描述

  • 步骤2:在File项输入类目名 >File Type项选择文件类型(Category、Extension、Protocol)>Class项选择拓展类(Category) >点击Next

    这里写图片描述

  • 创建之后,工程目录列表如下,Person + Handle.h 以及 Person + Handle.m 文件为创建的类目文件。

    这里写图片描述

类目的使用

通过类目加入的方法,会成为原始类的一部分。例如:通过类目给Person类增加方法,编译器会把这些方法加到Person类的定义里面。通过类目加入的方法,使用起来和原始类里面的方法没有等级差别,同等对待。类目里定义的方法会被原始类的子类所继承,就跟原始类的其他方法一样。使用类目的最大好处就是可以扩展别人实现的类,而且不需要获得原始类的源代码。但需要注意以下几点:

  • 不能在类目中添加实例变量;

  • 可以为同一类添加多个类目,但类目名和方法名不能重复;

  • 如果添加的方法和系统重名,优先实现类目中的方法,因此,不能随意重写类的方法;

现在在刚刚创建的Person + Handle.h文件中,声明方法SayHi()

#import "Person.h"

@interface Person (Handle)

- (void)sayHi;

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

实现方法

#import "Person+Handle.h"

@implementation Person (Handle)

- (void)sayHi {
    NSLog(@"Hi!");
}

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

调用方法

#import <Foundation/Foundation.h>

#import "Person.h"
#import "Person+Handle.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [[Person alloc] init];

        [person sayHi]; // 输出 Hi!

    }
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

类目的局限性

  • 无法向类中添加实例变量,如果需要添加实例变量则只能在子类中添加;

  • 如果在类目中重写父类方法,可能导致super消息的断裂,因为在类目中的方法优先级高于父类。

类目中的属性

在类目中不能声明实例变量,但是允许声明属性,值得注意的是,如果声明成属性,并不会生成setter( )、getter( ) 方法,并且在类目中不能使用关键字@synthesize,只能通过@dynamic动态合成变量和属性,在原始类中必须存在对应的实例变量;

假设在刚创建的类目中添加一个属性address,代码如下:

#import "Person.h"

@interface Person (Handle)

@property (nonatomic, retain) NSString *address; // 类目中的属性无法生成setter、getter的实现体部分

- (void)sayHi;

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在main函数通过点语法调用此属性时,程序奔溃,因为系统并未生成对应的setter( )、getter( )方法,改进方法如下,首先在Person.h文件中声明对应的实例变量_address,代码如下:

#import <Foundation/Foundation.h>

@interface Person : NSObject {

    NSString *_address;
}

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

其次在Person + Handle.m文件中,动态合成address属性,并实现对应的setter( )、getter( )方法,代码如下:

#import "Person+Handle.h"

@implementation Person (Handle)

@dynamic address; //动态合成成员变量和属性,动态绑定

- (void)sayHi {
    NSLog(@"Hi!");
}

#pragma mark - setter & getter

- (void)setAddress:(NSString *)address {
    if (_address != address) {
        [_address release];
        _address = [address retain];
    }
}

- (NSString *)address {
    return _address;
}

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

现在在main函数中通过类目访问address就没有问题啦。

延展 Extension

类的私有方法:

1、objective-c中没有绝对意义上的私有方法;

2、 在.h文件中声明的方法都属于公开的方法,意味着开放给别人调用;

3、如果不想公开某些方法,可以不在.h文件中声明;

4、 这样的方法可以被本类中的其他方法所调用;

5、如果在类外面强行调用这些未公开的方法,也能调用,但是会有编译器警告;

延展简介

  • 延展是匿名类目,为当前类添加私有方法;

  • 使用类目增加的方法是让外部可见,而延展的目的是增加方法,让外部不可见;

  • 不可见的目的更多的是封装代码,对于想要隐藏的算法和接口,可以使用延展;

  • 延展接口部分写在当前类的.m文件中,实现部写在当前类的实现中;

@interface Person ()

@end
 
 
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

延展使用

#import "Person.h"

@interface Person ()

// 1、声明方法
- (void)sayHello;

@end

@implementation Person

// 2、实现方法
- (void)sayHello {
    NSLog(@"Hello, China!");
}


- (instancetype)init {
    self = [super init];
    if (self) {
        // 3、调用方法
        [self sayHello];
    }
    return self;
}

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

延展补充

  • 在延展中,允许声明实例变量、属性以及方法,但是这些都是私有的,只能在对应类的实现文件访问。声明顺序如下:
@interface Person () 

{
   1、实例变量声明部分
}

2、属性声明部分
3、方法声明部分

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

协议 Protocol

协议简介

  • 协议是一个命名的方法列表,是对象之间的交互原则与共识,一个类使用协议称作该类遵守了协议;

  • 协议中的所有内容,只是作为一个声明,由遵守该协议的类的实现部分去实现协议方法(和我们生活中一样,如果没有人履行它,协议就是无用的纸而已);

  • 协议可以声明属性和方法,协议的目的是告诉外部我一定会有,所以它无法用来声明全局变量,而实际上,你声明的属性和方法具体是否存在,得看你是否正确的履行了协议中的所有内容;

  • 协议声明的属性和方法可以选择是否必须,关键字分别为@required必须和@optional可选,默认为必须;

  • 苹果同样提供了专门的协议文件模版,它只有一个.h文件。其创建方法与延展类似,只需将File Type项选择为Protocol即可;当然还有更简便的创建方法,通过@protocol指令来定义一个协议,后文主要讲解通过@protocol指令创建协议的方式。

协议的定义

@protocol ProtocolName <NSObject>

@optional

// optional methods

@required

// required methods

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

协议的遵守

1、接受协议在某些方面与声明父类很相似:

  • 它们都为类带来了额外的方法声明;

  • 它们都写在类的接口的类名后;

2、当一个类把一个协议的名字列在它的父类名后的尖括号中,它即被称为“接受”了这个协议

@interface ClassName : Superclass <protocol list>
 
 
  • 1
  • 1

3、一个类可同时遵守多个协议,协议名之间以逗号隔开。

@interface Person : NSObject <protocol1, protocol2, protocol3...>
 
 
  • 1
  • 1

4、一个类实现了协议中声明的方法,称为“确认”了这个协议。协议需要被其他类所“确认”,否则这个协议就没有什么意义了。

5、可通过如下方法检查对象是否实现协议或协议方法

// 1、检查对象是否实现某个协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;

// 2、检查对象是否实现某个方法
- (BOOL)respondsToSelector:(SEL)aSelector;
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

Tips

1、协议同样可以用于对对象进行类型指定。在使用协议进行类型指定是时,协议名写在类名后的尖括号中。

2、id <MyProtocol> anyObject;,这表明anyObject是接受了MyProtocol协议的id类型的对象。

协议下的代理委托模式

该示例涉及类为Teacher类与Student类,在Person类中定义一个协议,并在协议中指定买书的方法,Teacher类不直接实现买书,而是指定代理,委托Student类实现。日常生活中也会经常遇到这种情况,比如你现在正在做饭,突然发现没盐,而你不能抽身,此时你将会选择别人帮你实现下楼买盐的步骤,这就设计到代理委托模式。如下将具体讲解协议下的代理委托模式的实现。

1、第一步,在"Teacher.h"文件中定义一个协议,指定买书方法,设置代理属性delegate,并在Teacher类的接口部分声明一个buyBooks()方法;

#import <Foundation/Foundation.h>

// 协议声明
@protocol TeacherDelegate <NSObject>

@optional // 选择实现的

- (void)teacher:(Teacher *)teacher arrangeWork:(NSString *)work;


@required // 必须实现的


- (void)teacher:(Teacher *)teacher buyBooks:(NSArray < NSString * > *)books;

@end

@interface Teacher : NSObject

// 声明代理
@property (nonatomic, weak) id <TeacherDelegate> delegate;

- (void)doTask;

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2、第二步,在Teacher.m文件中,实现buyBooks()方法,判断代理是否存在并且实现了协议,如果代理存在并且实现了协议方法,则让代理人执行买书操作。

#import "Teacher.h"

@implementation Teacher

- (void)buyBooks {


    // 判断代理人是否存在并且遵守协议

    // conformsToProtocol:判断当前对象是否遵守该协议
    // respondsToSelector:判断当前对象是否实现该方法

    if (self.delegate && [self.delegate respondsToSelector:@selector(teacher:buyBooks:)]) {

    }

    if (self.delegate && [self.delegate conformsToProtocol:@protocol(TeacherDelegate)]) {
        [self.delegate teacher:self buyBooks:@[@"ChineseBook", @"EnglishBook", @"MathBook"]];
    }
}

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3、第三步,在main函数中,引入Teacher类与Student类,并实例化相应对象,将teacher对象的代理属性指定为student对象。

#import <Foundation/Foundation.h>

#import "Teacher.h"
#import "Student.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {

#pragma mark - 协议

        Teacher *teacher = [[Teacher alloc] init];
        Student *student = [[Student alloc] init];

        teacher.delegate = student;

    }
    return 0;
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

4、第四步,在Student.h文件中,导入Teacher.h,并遵守TeacherDelegate协议。

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

@interface Student : NSObject <TeacherDelegate>

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5、第五步,在Student.m文件中,实现协议方法。

#import "Student.h"

@implementation Student

- (void)teacher:(Teacher *)teacher buyBooks:(NSArray<NSString *> *)books {
    __block NSMutableString *string = [NSMutableString string];
    [books enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [string appendFormat:@"%@、", obj];
    }];

    NSString *result = [string substringToIndex:string.length - 1];

    NSLog(@"老师,您要买的‘%@’已经买好了。", result);

}

@end
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

Tips

声明delegate属性需定义成weak,而非strong,因为两者之间必须为“非拥有关系”。通常情况下,扮演delegate的那个对象也要持有本对象。例如在本例中,想使用Teacher的那个对象就会持有本对象,直到用完本对象之后,才会释放。假如声明属性的时候用strong将本对象与委托对象之间定为“拥有关系”,那么就会引入“保留环”。因此,本类中存放委托对象的这个属性要么定义成weak,用么定义成unsafe_unretained,如果需要在相关对象销毁时自动清空,则定义为前者,若不需要自动清空,则定义为后者。

6、第六步,在main函数指定teacher对象代理之后,调用buyBooks()方法,观察控制台输出情况,输出如下:

2015-11-18 22:34:16.321 CategoryExtensionAndProtocol[1947:287990] 老师,您要买的‘ChineseBookEnglishBookMathBook’已经买好了。
Program ended with exit code: 0
 
 
  • 1
  • 2
  • 1
  • 2

类目与非正式协议

  • 创建一个NSObject的类目而不实现称为“创建一个非正式协议”;

  • 因为一般情况下类都从NSObject继承,所以NSObject的类目中所声明的方法,这个类可以实现也可以不实现;

  • 因为NSObject的特殊性,所以NSObject的类目声明称为非正式协议

    原文:http://blog.csdn.net/hierarch_lee/article/details/49922925

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值