Programming with Objective-C(三)

这一次的内容主要是对于类的扩充,在OC 中,对于已经定义好的类,我们是可以对它进行扩充的。扩充的方案有两种,一种是通过类别来进行扩充,另外一种就是通过扩展来扩充。

首先说一下扩充现有类的意义吧,有些时候,当我们在设计一个类的时候,可能有一部分的功能,是不具备通用性的,这些功能视其使用的环境,可能会产生变化,这种时候我们就可以通过扩充的方式,在不同的环境下使用不同的方法来解决问题。其次,就是针对 Cocoa 框架中的类,我们需要某部分功能,但是它又没有的时候,我们就可以自己动手来添加。

对类扩充的第一种方法就是直接使用类别来扩充,在使用类别进行扩充的时候,我们是在为相应的类添加方法。它的语法如下:

@interface ClassName (CategoryName)

@end

在使用类别的时候,我们是否拥有源代码并不重要,只要能够确定这个类存在当前的项目中就可以了,当然,这个主要是针对 Cocoa 框架中的类而言。一旦我们使用类别为一个类添加了方法,那么,这个类的所有的实例都会具有这些方法,即便是子类也会继承到这些方法,唯一的限制在于,如果想让类别生效,那么我们必须在使用到的文件中引入相应的头文件,否则我们使用的依旧是原本的类。一般来说,类别文件我们也会写一个头文件和一个实现文件,只是命名上稍微有些不同,通常都是 ClassName+CategoryName 的形式,在使用的时候我们只要引入这个头文件就可以了。

实际上类别除了用于给原本的类进行扩充之外,它也可以作为我们设计类的时候的一种思路。这也就意味着我们可以把一个类的实现分离开来,那么在设计类的时候,就可以考虑把比较复杂的方法,或者说某一个方面的方法抽取出来,然后单独设为一个类别处理,这样类的结构和功能就更加清晰了。

但是在使用类别的时候有几个点还是需要注意的,首先就是属性的问题,从语法上来说,我们在类别中声明一个属性是没有问题的,但是,编译器不会为我们生成相应的实例变量以及访问方法,所以基本上是没有什么作用的。我个人觉得其实即便是在编译器中也应该给出编译时警告的,毕竟在类别中如果去申明一个属性的话,最直接的问题就是重名,尤其是对于 Cocoa 库中的类来说,一旦重名出来了很多问题都不好解决,直接禁用反倒是一种比较好的措施,毕竟对于库中的类来说,需要操作的实例变量早已定义好。还有一个很关键的问题,就是类别的重名问题,因为 Cocoa 库中已经写好的类,其实也采用了类别的形式,将部分操作进行分类,所以极有可能在写的时候会重名,并且 Cocoa 库更新之后,可能你自己之前写的无冲突的类别也会随着更新而产生冲突,所以在给类别命名的时候也需要注意。官方文档给我们的解决方案是,采用三位大写的字母放在最前面,同时类别中的方法名也有可能会出现重名,对于方法名,同样采用三位字母,但是是小写的形式,并且用下划线与实际的方法名分隔,具体情况就像下面这样:

@interface NSSortDescriptor (XYZAdditions)

+ (id)xyz_sortDescriptorWithKey:(NSString *)key ascending:(BOOL)ascending;

@end

这样做的一个好处就是防止重名,另外就是对于方法名,在编译阶段就可以确认调用的方法。(方法的重名并不会出现编译时的警告,但是在调用的时候究竟会调用哪个方法是无法确认的)

上面讲的就是通过类别来扩充一个类,但是这种方式的局限性也很明显,我们是针对自己设定的一个类别来进行扩充,只能扩展方法,并且还有不少的隐患。苹果实际上还为我们提供了另外一种类的扩充方式,那就是直接基于类来扩展,这种方法要求你必须拥有源码才行,实际上,这种方式我们会经常接触到。

扩展从写法上来说,其实和类别是差不多的,但是它要求你必须拥有源码,并且是直接在编译时生效,一同编译到源码中对应的类里面,这就是为什么一定要有源码才可以使用,所以对于 Cocoa 中的类我们是无法使用的。该方式的写法如下:

@interface ClassName ()

@end

没错,这个看起来和类别简直一模一样,只是没有类别的名称而已。但是它所起到的作用还是和类别有所不同的,尤其是它支持属性的添加和实例变量的定义,如果你像这样在扩展中声明一个属性:

@interface XYZPerson ()
@property NSObject *extraProperty;
@end;

编译器会为你生成一个实例变量 _extraProperty 以及它的 setter/getter,并且这些内容是会编译到原始的类中去的。甚至于如果你在扩展中声明了一个方法,那么在类的实现中也是必须实现这个方法的。

既然类的扩展中的方法、属性基本上都和直接在原来的类中声明没有什么区别,那么,为什么我们还需要这样一个扩展呢?我们都知道,类的声明,其实就是提供了一系列的接口给外部的成员来访问,换句话说,这样的一个接口,是一个类的公有接口。其实对于从其他语言转来 OC 的人,有一个最大的疑惑就是,原本的访问权限呢,以往都有 public 和 private,但是在 OC 中偏偏没有了。实际上这就和 OC 本身的思路有关了,最开始定义的那一个接口,就是我们的公共接口,外部可以随意访问,但是一旦我们通过扩展来定义了方法,或者属性,这就是我们所定义的私有成员,外部无法直接访问,这样 OC 就间接实现了访问权限的控制。另外,OC 中还有个比较独特的东西,那就是可读可写只读这些属性,这在一定程度也加大了写代码的麻烦程度,如果一个属性我希望外部变量只能获得它的值而不能修改它,那我得声明成 readonly,然后在类的各个方法中通过实例变量去访问它,但是这偏偏又和 OC 推荐的只在 init 方法中直接访问相悖。这个时候,就可以通过类的扩展来解决这个问题,比如,在类中间,我把属性设置为 readonly,然后在扩展中又设置为 readwrite,这样就可以在对外部的时候只提供 getter 方法,对内的时候又可以使用 setter 方法。(当然,readwrite 是默认的,所以只要重新声明一下就可以了)

值得一提的是,对于编译器来说,它并不会在乎这么多,只要你的属性存在 readwrite,那它就会同时生成 setter 和 getter,所以在实际运行的过程中,你的代码中其实还是存在着 setter,只是对于 readonly 属性的访问会让编译器在编译阶段就不通过,但是,我们其实还有 performSelector 方法可以直接调用 setter,所以在考虑类的继承以及设计的时候需要考虑好这一点,因为在继承体系中对 setter 的调用,有可能最终调用的并不是你所希望的那一个方法。

其实在看到扩展的写法的时候,你应该已经有所察觉,实际上很多源码中都会这么用,就是在类的实现文件的上方,再写上一个类的扩展,在刚开始学习的时候,可能看到这些代码会让人觉得疑惑,但是现在想必这些代码大家已经可以理解了。

另外,对于类别也好,扩展也好,他们并不见得就是扩充类的最佳手段。毕竟 OC 是一门面向对象的语言,我们需要考虑到对象这个概念,需要考虑到怎么提升代码的重用性,所以在一定的情况下,我们未必要对类进行扩充,也许直接去定义子类反而是更好的形式。同时,对于可能会影响到重用性的决策,我们可以把它交给其它的对象类处理,也就是,通过 delegate 来分散部分工作,使得类的重用性更高。所以 OC 这门语言其实并没有那么简单,即便是想设计好一个类,也需要多方面的考虑。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值