【iOS】协议与分类


前言

协议与分类:

OC语言中的协议类似于java语言中的接口,OC不支持多重继承,因此我们把某个类应该实习那的一系列方法定义在其协议里。协议最常见的是委托模式,
分类也是OC语言的一项重要有v眼特性。利用好分类机制,我们无需继承子类即可为当前类添加方法。而在其他编程语言中,需要继承子类来实现。


通过委托与数据源协议进行对象间通信

对象之间经常需要通信,OC中有名为“委托模式”的编程设计模式来实现对象间的通信。
委托模式的主旨是:定义一套接口,某对象若想接受另一个对象的委托,遵从此接口,使其成为“委托对象”。而这“另一个对象”可以给委托对象回传一些消息,使其在发生相关消息时通知委托对象。
次模式可以将数据与业务逻辑解耦。、

  • 试图对象的属性中,可以包含负责数据与事件处理的对象,这两个对象叫做“数据源”和“委托”
  • 在OC中,通过协议来实现此种特性。
  • 获取网络数据的类含有一个“委托对象”,在获取完数据后,它会回调这个委托对象。请添加图片描述
@protocol EOCNetworkFetcherDelegate

- (void)networkFetcher:(EOCNetworkFetcher *)fetcher didReceiveDate:(NSDate *)data;
- (void)networkFetcher:(EOCNetworkFetcher *)fether didFaillWithError:(NSError *)error;

@end
  • 使用一个属性来存放委托对象。这个属性需要定义成weak。不能为strong。两者之间为“非拥有关系”。通常情况下,扮演delegate的那个对象也要持有对象。
@interface EOCNetworkFetcher : NSObject
@property (nonatomic, weak) id<EOCNetworkFetcherDelegate> delegate;
@end
  • 实现委托协议的方法是声明某个类遵从委托协议,然后把协议中想实现的方法在类里实现出来。若类药遵从委托协议,可以在其接口中声明,也可以在class-continuation分类中声明。如果要向外界公布此类实现了某协议,那么就在接口中声明,而如果这个歌协议是委托协议,通常只会在类的内部使用,这种情况下都是在class-continuation分类里声明

  • 委托协议中的方法一般都是“可选的”,因为扮演“受委托者”角色的这个对象未必关心其中所有的方法。为了指明可选方法,委托协议经常使用@optional关键字来标注方法。

  • 如果要在委托对象上调用可选方法,那么必须提前使用类型信息查询方法判断这个委托对象是否能响应相关选择子。这样的话,delegate对象就可以完全按照其需要实现委托协议中的方法了,不用担心因为哪个方法没实现导致程序出问题,即使没有配置委托对象,程序也能照常运行,因为nil发送消息使if语句的值称为false

  • delegate中的方法名也一定要起的很恰当才行,方法名应该准确描述当前发生的事件以及delegate对象为何要获知此事件。调用delegate中的方法时,把发起委托的实例也一并传入方法中,这样,delegate对象在实现相关方法时,就能根据传入的实例分别执行不同的代码块。delegate中的方法也可以用于从获取委托对象中获取信息。

  • delegate里的方法也可以用于从获取委托对象中获取信息。

  • 也可以用协议定义一套接口,令某类经由该接口获取其所需的数据。委托模式的这一用法旨在向类提供数据,孤儿又称“数据源模式”。此模式中,信息从数据源流向类,在常规的委托模式中,信息从类流向委托者。请添加图片描述

  • 若有必要,可以实现含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存至其中

将类的实现代码分散到便于管理的各个分类之中

类中经常容易填满各种方法,而这些方法的代码则全部堆在一个巨大的时间文件里。我们可以通过OC的分类机制,把类代码按逻辑划入几个分区中。

//  EOCOPerson.h
//  text。12.14
//
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface EOCOPerson : NSObject


@property (nonatomic, copy, readonly) NSString* firstName;
@property (nonatomic, copy, readonly) NSString* lastName;
@property (nonatomic, strong, readonly) NSSet* friends;

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString *)lastName;


- (void)addFriend:(EOCOPerson *)person;
- (void)removeFriend:(EOCOPerson *)person;
- (BOOL)isFriendsWith:(EOCOPerson *)person;


-(void)performnDaysWork;
- (void)takeVacationFromWork;


- (void)goToTheCinema;
- (void)goToSportGames;
@end

NS_ASSUME_NONNULL_END

  • 我们把类的实现代码分为好几个部分。这项语言特性叫“分类”。类的基本要素(属性和初始化方法等)都声明在“主实现”里。指向不同类型的操作所用的另外几套方法归入各个分类中。
  • 使用分类机制后,依然可以把整个类都定义在一个接口文件里,将实现代码也可以写在一个文件里。
//  EOCOPerson.h
//  text。12.14
//
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface EOCOPerson : NSObject


@property (nonatomic, copy, readonly) NSString* firstName;
@property (nonatomic, copy, readonly) NSString* lastName;
@property (nonatomic, strong, readonly) NSSet* friends;

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString *)lastName;

@end

@interface EOCOPerson(Friendsship)


- (void)addFriend:(EOCOPerson *)person;
- (void)removeFriend:(EOCOPerson *)person;
- (BOOL)isFriendsWith:(EOCOPerson *)person;
@end


@interface EOCOPerson(Work)

-(void)performnDaysWork;
- (void)takeVacationFromWork;
@end

@interface EOCOPerson(Play)

- (void)goToTheCinema;
- (void)goToSportGames;

@end
NS_ASSUME_NONNULL_END

  • 通过分类机制,可以把类代码分成很多个易于管理的小块,以便单独检视。使用分类机制后,如果想使用分类中的方法,要引入分类头文件。
//  EOCOPerson+Friendship.h
//  text。12.14
//
//

#import "EOCOPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface EOCOPerson_Friendship : EOCOPerson



- (void)addFriend:(EOCOPerson *)person;
- (void)removeFriend:(EOCOPerson *)person;
- (BOOL)isFriendsWith:(EOCOPerson *)person;


@end

NS_ASSUME_NONNULL_END



//  EOCOPerson+Friendship.m
//  text。12.14
//
//

#import "EOCOPerson+Friendship.h"

@implementation EOCOPerson_Friendship

- (void)addFriend:(EOCOPerson *)person{
    return 0;
}
- (void)removeFriend:(EOCOPerson *)person{
    return 0;
}
- (BOOL)isFriendsWith:(EOCOPerson *)person{
    return YES;
}
@end

  • 即使分类本身不是很大,我们也可以利用分类机制将其切割成几块,吧相应代码归入不同的功能区。
  • 将应该视为“私有”的方法归入名为Private的分类中,以隐藏实现细节。

总是为第三方类的分类名称加前缀

分类机制通常用于向无源码的既有类中新增功能。分类中的方法是直接添加在类里面的,他们就好比这个类中的固有方法。将分类方法加入类中这一操作是在运行期系统加载分类时完成的。如果类中本来就有此方法,分类又实现了一次,那么分类中的方法就会覆盖原来那一份实现代码。

  • 如果分类里有相同名称的方法,代码和我们自己所添加的大同小异,但是不能正确实现我们想要的功能,如果这个分类加载的时机晚于正确的类,代码就会吧我们所写的覆盖掉。执行结果和我们预期的就不一样。自己所写的代码就无法正确运行了。
  • 要解决此问题,一般是以命名空间开区分各个分类的名称与其中所定义的方法。给相关名称都加上某个公用的前缀。
  • 即使家了前缀,也难保其他分类不会覆盖我们所写的方法,然而几率小了很多,因为其他程序库很少会选择和我们相同的前缀。这样做也能避免类的开发者以后在更新类时所添加的方法和我们在分类中添加的方法相同
  • 如果向某个类的分类中添加方法,那么在应用程序中,该类的每个实例均可调用这些方法。如果无意间把分类的方法名起的和其他分类一样,或者是与第三方库所添加的分类方法重名,可能会出现奇怪的bug。

勿在分类中声明属性

属性是封装数据的方式。我们要尽量避免从坟里里声明属性。因为除了class-continuation分类之外,其他分类都无法向类中新增实例变量,因此他们无法把实现属性所需的实例变量合成出来。

  • 如果我们把属性放在分类里是心啊,会警告:此分类无法合成xxx属性相关实例变量。开发者需要在其分类中为属性实现存取方法。此时可以把存取方法声明为@dynamic。也就是说等到运行期在提供这些方法,编译器目前是看不见的
//  EOCOPerson.h
//  text。12.14
//
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface EOCOPerson : NSObject


@property (nonatomic, copy, readonly) NSString* firstName;
@property (nonatomic, copy, readonly) NSString* lastName;

- (id)initWithFirstName:(NSString*)firstName andLastName:(NSString *)lastName;

@end

@interface EOCOPerson(Friendsship)


@property (nonatomic, strong) NSArray* friends;
- (void)addFriend:(EOCOPerson *)person;
- (void)removeFriend:(EOCOPerson *)person;
- (BOOL)isFriendsWith:(EOCOPerson *)person;
@end


@interface EOCOPerson(Work)

-(void)performnDaysWork;
- (void)takeVacationFromWork;
@end

@interface EOCOPerson(Play)

- (void)goToTheCinema;
- (void)goToSportGames;

@end
NS_ASSUME_NONNULL_END

请添加图片描述

  • 关联对象能过解决在分类中不能合成实例变量的问题。这样做可行,但是不太理想,要把相似的代码写很多遍。而且在内存管理上容易出问题。因为我们在为属性实现存取方法时,经常会忘记遵从其内存管理语义。
  • 把封装数据所用的全部属性都定义在主接口里。
  • 在class- continuation分类之外的其他分类中,可以定义存取方法,但是进来不要定义属性。

使用class-continuation分类隐藏实现细节

类中经常包含一些无需对外公布的方法和实例变量。可以将这些内容写为私有,开发者不依赖他们。OC的动态消息系统决定了其不可能实现真正的私有方法或者私有实例变量,

  • class- continuation分类和普通的分类不同,它必须定义在其所接续的那个类的实现文件里。其重要之处在于,这是唯一能声明实例变量的分类,而且此分类没有特定的实现文件,其中的方法都用过定义在类的主实现文件里。class-continuation分了没有名字。

@interface EOCOPerson()


@end
  • 这是其写法。这种类里可以定义方法和实例变量。有“稳固的ABI”这一机制,是我们无需知道对象大小就可以调用它。类的使用者不一定知道实例变量的内存布局,所以他们呢也就未必非得定义哦在公共接口中了。
@interface EOCOPerson() {
    NSString* _anInstanceVariable;
}
- (void)addPerson:(EOCOPerson *)person;

@end
  • 公共接口里本来就可以定义实例变量,但是把它们定义在class-ccontinuation分类或“实现块”中可以将其隐藏起来,只供本类使用。即使在公共接口里将其标注为“private”,也还是会泄漏实现细节。
  • 实例变量也可以定义在实现块里,从语法上说,这和将其添加到class-continuation框架里等效。
  • 编写Objective-C++代码时,class-continuation分类也尤其有用。Objective-C++是Objective-C和C++的混合体,由于兼容性原因,游戏后段一般用C++来写,另外,有时候要使用的第三方库可能只有C++绑定,此时也必须用C++来编码。在这些情况下,使用class-continuation分类会很方便。
  • 网页浏览器框架的WebKit,七大部分代码都以C++编写,然而对外展示出来的却是一套整洁的Objective-C接口。
  • class-continuation分类还有一种合理用法,就是将public接口中声明为“只读”的属性拓展为“可读写”,以便在类的内部设置其值。我们一般不直接访问实例变量,而是通过设置访问方法来做。这样能够触发“键值观测”通知,其他对象可能正在监听此事件。
  • 若对象所遵从的协议只应该视为私有,则可以在class-continuation分类中声明。有时对象所遵从的某个协议在私有API中,我们可能不太想在公开接口中泄漏这一信息。

通过协议提供匿名对象

协议定义了一系列方法,遵从次协议的对象以应该实现它们。(如果这些方法不可选,那么就必须实现)我们可以利用协议把自己所写的API之中的实现细节隐藏起来,将返回的对象设计为遵从此协议的纯id类型。这样的话,想要隐藏的类名就不会出现在API之中了。若是接口背后有多个不同的实现类,而我们又不想具体指明使用那歌类,那么可以考虑这个办法,因为有时候这些类可能会变,有时候它们又无法容纳于标准的类的继承体系中。

  • 此概念经常称为“匿名对象”,在其他语言中,匿名对象是指以内联形式所创建出来的无名类(也叫匿名类),在OC中不是这个意思,
  • @property(nonatomic, weak) id delegate;
  • 这个属性的类型是id , 所以实际上任何类的队形都能充当这一属性,即使该类不继承于NSObject也可以,只要遵循EOCDelegate协议即可。==对于具备此属性的类来说,delegate就是“匿名的”==可以在运行期查处此对象所属的类型。
  • 在字典中,键的标准内存管理语义是“设置时拷贝”,值的语义是“设置时保留”。在可变版本的字典中,设置键值的方法签名是:
  • -(void)setObject:(id)object forKey:(id)key
  • 表示键的那个参数类型为id,做为参数值的对象,他可以是任何类型,遵从NSCopying协议即可。这个key可以视为匿名对象。与delegate属性一样,字典也不必关心key对象所属的具体类型,而且他也决不应该依赖于此。字典对象只要能确定她可以给此实例发送拷贝消息就行。
  • 处理数据库连接程序也是这样。以匿名对象来表示另一个库返回的对象。对于处理连接所用的那个类,你也许不想让外人知道其名字,因为不同的数据库可能要用不同的类来处理。如果没法令其都继承自同一个类,那么就得返回id类型的东西。我们可以把所有数据库连接都具备的那些方法放到协议里,令返回的对象遵从此协议。
  • 如果具体类型不重要,重要的是对象能够响应(定义在协议里的)特定方法,那么可以使用匿名对象来表示。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山河丘壑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值