【Effective Objective - C 2.0】——读书笔记(四)

文章目录


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

要点:

  • 委托模式为对象提供了一套接口,使其可由此将相关事件告知其他对象。
  • 将委托对象应该支持的接口定义成协议,在协议中把可能需要处理的事件定义成方法。
  • 当某对象需要从另外一个对象中获取数据时,可以使用委托模式。这种情况下,该模式亦称"数据源协议"
  • 若有必要,可实现含有位段的结构体,将委托对象是否能响应相关协议方法这一信息缓存至其中。

二十四、将类的实现代码分散到便于管理的数个分类之中

类中经常容易填满各种方法,而这些方法的代码则全部堆在一个巨大的实现文件里。 有时这么做是合理的,因为即便通过重构把这个类打散,效果也不会更好。在此情况下,可 以通过Objective-C的" 分类" 机制,把类代码按逻辑划人几个分区中,这对开发与调试都 有好处。

比如说,我们把个人信息建模为类。那么这个类就可能包含下面几个方法:

bash 复制代码
#import <Foundation/Foundation.h>

@interface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, strong, readonly) NSArray *friends;

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

/ * Friendship methods * /
- (void) addFriend:(EOCPerson *)person;
- (void) removeFriend:(EOCPerson *)person;
- (BOOL) isFriendsWith:(EOCPerson *)person;

/ * Work methods * /
- (void) performDaysWork;
- (void) takeVacationFromWork;

/ * Play methods * /
- (void) goToTheCinema;
- (void) goToSportsGame;

@end

现在,类的实现代码按照方法分成了好几个部分。所以说,这项语言特性就叫做"分类"。本例中,类的基本要素(诸如属性与初始化方法等)都声明在"主实现"里。可是,随着分类数量增加,当前这份实现文件很快就膨胀得无法管理了,此时就可以把每个分类提取到各自的文件中去,以EOCPerson为例,可以按照其分类拆分成下列几个文件:

bash 复制代码
EOCPerson + Friendship(.h/.m)
EOCPerson + Work(.h/.m)
EOCPerson + Play(.h/.m)
bash 复制代码
//EOCPerson + Friendship.h
#import "EOCPerson.h"

@interface EOCPerson (Friendship)
- (void) addFriend:(EOCPerson *)person;
- (void) removeFriend:(EOCPerson *)person;
- (void) isFriendsWith:(EOCPerson *)person;
@end

//EOCPerson + Friendship.m
#import "EOCPerson + Friendship.h"

@implementation EOCPerson (Friendship)
- (void) addFriend: (EOCPerson *)person {
	/* ... */
}
- (void) removeFriend:(EOCPerson *)person {
	/* ... */
}
- (BOOL) isFriendsWith:(EOCPerson *)person {
	/* ... */
}

@end

并且我们之前有说过私有方法的命名,通过特殊的前缀将私有方法指示出来,那么我们学了分类规划之后,我们还可以通过创建一个分类,这个分类其中全是私有方法,通过这种方法将这些私有方法都规划到一个类中,当然其还是的遵循之前的命名规则。

要点

  • 使用分类机制把类的实现代码划分成易于管理的小块。
  • 将应该视为"私有"的方法归入名叫Private的分类中,以隐藏实现细节。

二十五、总是为第三方的分类名称加前缀

分类机制通常用于向无源码的既有类中新增功能,但是他也存在相应的问题:分类中的方法是直接添加在类里面的,他们就好比这个类中的固有方法。将分类方法加入类中这一操作是在运行期系统加载分类时完成的。运行期系统会把分类中所实现的每个方法都加入类的方法列表中。如果类中本来就有此方法,而分类又实现了一次,那么分类中的方法就会覆盖原来的那一份实现代码。并且多次覆盖的结果以最后一个分类为准。

所以我们要做的就是总是为第三方的分类名称加前缀,以如下代码为例子

要点

  • 向第三方类中添加分类时,总应给其名称加上你专用的前缀。
  • 向第三方类中添加分类时,总应给其中的方法名加上你专用的前缀。

二十六、切勿在分类里面声明属性

分类只能添加新的方法,但是不能添加属性(成员变量),我们尝试添加成员变量会出现警告

bash 复制代码
Property 'age1' requires method 'setAge1:' to be defined - use @dynamic or provide a method implementation in this category属性"age1"需要定义方法"setAge1:"---请使用@dynamic或在此类别中提供方法实现

这个警告只是需要我们给用@property关键字添加的属性手动完成setter getter方法,但是当我们在写setter,getter方法的时候一旦涉及到我们在类别定义的属性的时候就会报错

当然我们使用关联对象便可以解决这个问题,但是并不建议那么做

我们有时候可以在分类中添加一些只读的属性(readonly),但是需要获取方法并不访问数据,且属性也不需要实例变量来实现

要点

  • 类别和扩展都可以为 原来的类添加新的方法,但是类别的方法不实现系统不会提供警告,扩展的方法不提供实现系统会提示警告。原因是扩展在编译的时候阶段被添加到类中,分类在运行的时候才添加到类中
  • 把封装数据所用的全部属性都定义在主接口里。
  • 在"class-continuation分类"之外的其他分类中,可以定义存取方法,但尽量不要定义属性

二十七、使用"class-continuation分类"隐藏实现细节

class-continuation分类:

OC动态消息系统的工作方式决定了其不可能实现真正的私有方法或者私有实例变量。那么怎么实现私有变量和私有方法呢?这就要用到特殊的"class-continuation分类"了。

"class-continuation分类"和普通的分类不同,他必须定义在其所接续的那个类的实现文件里,并且这个类没有名字。

bash 复制代码
@interface EOCPerson ()
// Methods here
@end

这样你就可以在其中定义你的私有方法和私有变量了,这样有什么好处呢?公共接口里本来就能定义实例变量。不过,把它们定义在"class-continuation分类"或"实现块"中可以将其隐藏起来,只供本类使用。这些实例变量也并非真的私有,因为在运行期总可以调用某些方法绕过此限制,不过,从一般意义上来说,他们还是私有的。此外,由于没有声明在公共头文件里,所以将代码作为程序库的一部分来发行时,其隐藏程度更好。

"class-continuation分类"的合理用法:

"class-continuation分类"还有一种合理用法,就是将public接口中声明为"只读"的属性扩展为"可读写",以便在类的内部设置其值。

就是说,你在外部**.h**文件中定义一个"只读"的属性,然后你又在"class-continuation分类"将其的"==只读"属性改为"可读写"==的,那么这样下来,在外部看来他就是一个"只读"的属性,但是你可以在其内部自定义的设置其值了,他在内部来说就是"可读写"的了。

这样做很有用,既能令外界无法修改对象,又能在其内部按照需要管理其数据。这样,封装在类中的数据就由实例本身来控制,而外部代码则无法修改其值。

还有一种用法:若对象所遵从的协议只应视为私有,则可在"class-continuation分类"中声明名,这样就不会泄漏我们所遵从的协议

bash 复制代码
#import "EOCPerson.h"
#import "EOCSecretDelegate.h"

@interface EOCPerson () <EOCSecretDelegate>
@end

@implementation EOCPerson
/* ... */
@end

要点:

  • 通过"class-continuation分类"向类中新增实例变量。
  • 如果某属性在主接口中声明为"只读",而类的内部又要用设置方法修改此属性,那么就在"class-continuation分类"中将其扩展为"可读写"。
  • 把私有方法的原型声明在"class-continuation分类"里面。
  • 若想使类所遵循的协议不为人所知,则可于"class-continuation分类"中声明。

二十八、通过协议提供匿名对象

利用协议 把自己写的API的细节隐藏起来,将返回的对象设置为遵从协议的纯id 类型,这样就达到了匿名对象的效果,因为在OC里id 类可以指代任何的一个类型,此概念就叫匿名对象。

bash 复制代码
@property (nonatomic, weak) id<EOCDelegete> delegate;

这个delegate就是"匿名的",因为当你调用这个delegate的时候你并不知道它指的是那个类,而你却又能使用它所指代类的方法,这就把那个类给隐藏起来了,匿名对象也是同样的原理。

因为你可能定义很多的类,但是我们不能将它们都继承于同一个类,并且在OC中只有id类型可以将这些类的随便一个类都返回,所以我们在使用匿名对象的时候一定是返回的id类型。比如:我们将所有数据库都具备的那些方法放到协议中,令返回的对象遵从此协议。

先定义一个协议其中包括数据库都有的方法:

bash 复制代码
@protocol EOCDatabaseConnection
- (void)connect;
- (void)disconnect;
- (BOOL)isConneceted;
- (NSArray *)performQuery:(NSString *)query;
@end

提供一个单例接口:

bash 复制代码
#import <Foundation/Foundation.h>

@protocol EOCDatabaseConnection;

@interface EOCDatabaseConnection;
+ (id)sharedInstance;
- (id<EOCDatabaseConnection>)connectionWithIdentifier: (NSString *)identifier;

@end

这样的话,处理数据库连接的类名称就不会暴露了,来自不同框架的那些类限制就都可以使用同一个方法来返回了,而不用对每个类都写一个这种协议。

要点

  • 协议可在某种程度上提供匿名类型。具体的对象类型可以淡化成遵从某协议的id类型,协议里规定了对象所实现的方法。
  • 使用匿名对象来隐藏类型名称(或类名 )。
  • 如果具体类型不重要,重要的是对象能够相应(定义在协议里的)特定方法,那么可使用匿名对象来表示。
相关推荐
一点媛艺3 小时前
Kotlin函数由易到难
开发语言·python·kotlin
姑苏风3 小时前
《Kotlin实战》-附录
android·开发语言·kotlin
奋斗的小花生4 小时前
c++ 多态性
开发语言·c++
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
老猿讲编程5 小时前
一个例子来说明Ada语言的实时性支持
开发语言·ada
Chrikk6 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*6 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue6 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man6 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang