1.1 创建并描述一个类
开发中经常需要创建类文件,可以说在项目的开发过程中,很大一部分是由类组成的,要在Objective-C中创建一个类,需要继承NSObject或者其子类。NSObject及其子类就是Objective-C语言中对对象的实现。
打开NSObject的定义,可以看到在其头文件中仅有一百行代码,即定义了对象及其基本方法。
NSObject其实是一个实现了同名协议的接口(Interface),参数只有一个isa,指向当前所属类。在NSObject协议中,不同的NSObject类,需要根据实际情况来重写部分方法。
在实际开发中,如何去继承一个NSObject类呢?看似简单,却有许多需要注意的地方。可以通过下面一个简单例子来说明。首先创建一个类,如图1-1所示。
简单地创建一个继承自NSObject的对象后,文件的结构目录上会多出两个Person的文件,一个是头文件(.h),另一个是实现(.m)。头文件一般都是对这个类起到一个介绍的作用,开发者可以直接通过头文件来获取该类的一些基本信息,包括类的属性和方法,从而不需要关心该类是如何实现的。而与之对应的就是其实现文件,相对于头文件,实现文件的代码量大多会比头文件多一些,针对头文件中介绍的属性和方法,实现文件应当提供具体的实现,从而保证这个类可以被很好地使用。打个比方,一个类就相当于是一家餐厅,而头文件就是它的菜单,食客(开发者)通过菜单就能了解到这家餐厅有哪些菜品食物可以点,而对应的这些菜品食物都是在餐厅的厨房进行具体的实现,厨房可以做一些菜单上没有的菜品,但是菜单上却不能有厨房做不了的菜品。
图1-1 创建一个类
首先制作菜单,打开Person.h文件,内容如下。
导入Foundation框架。Foundation框架是系统的基于CoreFoundation框架的封装,即常说的Cocoa框架,包含数据集合、日期、文件处理,KVC/KVO,Runloop以及网络通信等一系列基本功能的类集合。
在头文件中导入框架尽量不要重复,如果Person类有一个UIImage的属性headImage,则需要在头文件中加入UIKit框架。
此时,加入UIKit后,可以不再需要“# import <Foundation/Foundation.h>”了,因为UIKit是默认包含Foundation框架的,重复的包含是无意义的,并且会使代码看起来并不简洁明了。但是毕竟Person是一个模型类,并不是展示类,导入UIKit会给开发者造成困扰。通常来说,第一个# import的框架可以很明确地让开发者明白该类是属于哪一种类型。所以此时的做法应该为所需要的UIImage添加一个前向声明:
接下来,我们会想到这个Person类会需要一个NSString类型的name属性,而且是必需的,而刚才提到的headImage,可能为nil。在这种情况下,为了兼容Swift的可选值类型,需要在对应的Property中加上特定的修饰符:nullable或nonnull。这样在Swift中使用会桥接成更明确的方法。
如果一个类的属性很多,这样做则会很麻烦,所以可以利用系统提供的NS_ASSUME_NONNULL_BEGIN、NS_ASSUME_NONNULL_END这两个宏来将默认未注明nullable还是nonnull的类的属性都默认设置为nonnull。
属性写完后,就需要写初始化方法。对于Person类,我们需要在初始化的时候传递一个NSString的name,并且最好能给headImage赋上一个默认的头像。
很简单,似乎并没有什么复杂的地方,可是当代码提交之后,协同开发的同事需要使用你的Person类创建实例时,却发现并没有默认的头像,这个时候你就会很疑惑,仔细一看同事的代码是这样写的:
Person * tom = [[Person alloc]init]; tom.name = @"Tom";
同事并没有使用你想让他使用的初始化方法,只是使用最基本的alloc和init。没有使用我们提供的初始化方法就会未设置默认头像。这是协同开发中经常会遇到的问题。如何让同事也使用自己提供的方法,而不是使用其他的初始化方法呢?
通过UNAVAILABLE_ATTRIBUTE修饰以在头文件中禁用其他的初始化方法,可保证只有自己提供的初始化方法才是唯一途径。此时,如果要创建Person实例,只能通过-initWithName:初始化方法。
本节小结
(1)创建类应尽量明确类型,必要时加上前向声明;
(2)属性应当标明可选还是必选,在与Swift混编时会生成更加明确的Swift方法;
(3)通过UNAVAILABLE_ATTRIBUTE禁用相关方法。