iOS项目开发过程中,是以不断创建文件的形式进行着的。
创建得比较频繁的文件类型是:
这两个类型中创建的文件有:子类、分类、扩展、协议四种文件,如下:
这四类文件是频繁创建的,我们来看一下各自分别的文件结构。认识下(常见的头文件类型):
(一)@interface 类 <协议>
声明一个类 遵守 某协议
(二)@interface 子类 : 父类
声明一个类 继承 某个父类
(三)@interface 集合类<元素类型> : 父类<元素类型>
声明一个确定元素类型的集合类 继承 某个确定元素类型的父集合类
(四)@interface 子类 : 父类 <协议>
声明一个子类 继承 某个父类 并且这个子类遵循 某协议
(五)@protocol 协议
声明一个协议
(六)@protocol 协议 <协议>
声明一个协议 并且合并其他协议
(七)@interface 类 (分类)
声明关于某类 的分类
(八)@interface 类 (分类) <协议>
用一个类 的分类 来遵循 某些协议
(九)@interface 集合类 <元素类型> (分类)
声明一个确定元素类型的集合类 的分类
(十)@interface 集合类 <元素类型> (分类) <协议>
用一个确定元素类型的集合类 的分类 来遵循 某些协议
(十一)@interface 子类 ()
声明一个类 的扩展
(十二)@interface 子类 () <协议>
声明一个类 的扩展 并且遵循某些协议
(十三)附加:用<>标明集合变量的元素类型或者标明遵守的协议
声明一个确定元素类型 的集合类 的实例对象
声明一个遵循 某些协议 的实例对象
为了将上述各种情况下的约束和“规矩”有清晰的了解,先对四个方面(继承、分类、扩展、协议)的的内容有个熟悉的了解后,再回过头来对上述各种情景做详细的解读。
关于类的继承,通过谷歌搜索和CSDN平台的关键字“iOS 继承”的搜索结果大致如下:
归纳起来,关于类的继承需要熟练掌握的方面大致有:
(零)类的继承究竟是什么
(一)继承的优缺点
(二)如何取其精华去其糟粕的使用继承
(三)继承的实现方式
(四)多继承的实现
(五)继承的框架图
(六)继承与分类、扩展、协议相互的使用
(七)使用继承还是分类
(八)属性的继承
(九)类方法、实例方法的继承
(十)继承、分类、扩展的区别
(十一)继承的父类涉及到Xib文件
(十二)UI控件的继承
比较杂乱,理解起来也是不成体系,上面的知识点用于查缺补漏还行,用于建立知识体系不要友好。
======================================
其实我们现在主要要理清楚的无非就是:类、协议、分类、扩展各自的作用,相互叠加后的效果。这个过程当中,围绕的就是“属性”、“方法(类方法、实例方法)、实例变量(各种范围类型)”来进行讨论。
======================================
首先,根据“格物”的思想,我们先去找到比较单一的,比较初始的物来探究。
“分类”和“扩展”都是对一个类的“补充”,因此它们两者不能算是初始的,那就是类和协议了。通过查看父类的方法层层递进,所有类的根类是NSObject,头文件如下:
但是NSObject类后面还接受着一个NSObject协议,因此NSObject类不能称之为单一、单纯的物。NSObject协议的头文件如下:
这样看来,的确比较单纯。因为就算刚刚我们从协议开始层层递进,也会发现,这个过程中的协议基本都是属于“合并协议”,长相就是如下:
当然也有其他比较单纯的非合并协议,比如:
这样看来,我们首先需要理解的应该是“协议”。然后围绕的重点依然是上面所提到的:“属性”、“方法(类方法、实例方法)、实例变量(各种范围类型)”来进行讨论。
创建一个协议文件可以发现,只有头文件(.h文件),没有实现文件。头文件中的初始化内容如下:
根据OOP的封装思想,实例变量是不应该写入头文件中的,因此,接下来考虑的就是将属性和方法写入协议中,究竟意味着什么?
首先协议中声明属性的情况肯定是有的哈,以图为证:
定义好一个协议如下:
“协议”的含义其实就是约束,上面这个协议要表达的约束就是:凡是遵守这个协议的类必须要实现gameScore属性的getter和setter方法,并且实现两个方法,一个是实例方法,另一个是类方法。当一个类遵守这个协议时,从头文件中看到的是什么呢?我令OJFMsgListVC这个类遵守该协议:
为了不出现警告或者是报错,这个类的实现文件中要做出以下处理:
当然我们平时在编程使用系统框架时候,是看不到实现文件的,我将这个类的实现文件写出来的目的是想让大家知道,实现文件是如何与头文件“对应”起来的。知道了它的实现文件,我们就可以放心大胆的使用OJFMsgListVC这个类了,比如:
这个过程下来,说明了一个什么样的事实?
从表面上来看,如果一个类遵守某个协议,协议中的属性和方法就“相当于”是这个类的头文件中声明的属性和方法。
那么从更深一层的含义来说呢?我们可以这样理解,一份协议是有其“自身的完整性”的、一份协议也许就是一种解决方案、一份协议就是一套规范。
从这个层面出发,一个类如果要遵守某个协议,其实不是那么简单的。首先,你需要认真理解协议的含义,接下来就是在这个类的实现文件中将协议蕴含的意思表现出来,将协议内部的逻辑表现出来。(而不是简简单单的重写下协议中属性的getter和setter方法,简单的重载下协议中的方法就行的,一定要实现协议要求的逻辑!这一点很关键)
--------------
既然协议对一个类有如此规范的约束效果,那我可不可以将一份完整的逻辑拆分为两份关联的逻辑呢?比如一份完整的逻辑需要十个方法相互调用来实现,但是我将其中的六个方法做成一份协议,让遵守这个协议的类来实现这六个方法。当然可以,刚刚的那个做法不就是这样的吗,定义一份协议,让OJFMsgListVC类遵守该协议,这样在XYZProtolTestVC类中就可以有逻辑的调用OJFMsgListVC的遵守协议中的方法了。如果上面的代码没有让你看出OJFMsgListVC和XYZProtolTestVC类是在做一份完整的逻辑,我将XYZProtolTestVC实现文件中的代码写成这样:
这个就是“代理”的雏形。接下来我们推演代理的设计模式的代码标准范式是如何得出的。
通过上面的代码示例我们可以理解刚刚所说的“将一份完整的逻辑拆分为两份关联的逻辑”。然而,我们不是为了拆分而去拆分,将一部分逻辑拆分出去目的是让另一个独立的对象去处理,并且这个对象还是会和其他对象打交道的(而不是像上面的代码一样,msgListVC这个对象就直接在此创建,并在此销毁)。也许可以这样理解,另外一个独立的对象,是可以有其他逻辑在身的。因此,这个独立的对象,不需要,也没必要在XYZProtolTestVC类中创建,只要被XYZProtolTestVC类的实例对象所引用就好了。(抱歉,我终于把这个“引用”表达出来了~),并且引用有一个好处就是,当这一份逻辑执行到某些情况下时,能够很好找到另一半逻辑的实现位置。
因此出现了下面的代理的代码范式:(对比图)
------------
到目前为止,单纯的协议和代理知识基本就已经讨论完一部分了。引用文章中前面说的一句话
其实我们现在主要要理清楚的无非就是:类、协议、分类、扩展各自的作用,相互叠加后的效果。这个过程当中,围绕的就是“属性”、“方法(类方法、实例方法)、实例变量(各种范围类型)”来进行讨论。
这样看来,我们只是把某个类遵守某个协议稍微说明白了。但是协议与继承、与分类、与扩展相互叠加后的效果,还没有开始。
通过文章提到的常见的头文件类型,接下来需要讨论的就是:
(1)@interface 类 <协议>
(2)@interface 子类 : 父类 <协议>
(3)@protocol 协议
(4)@protocol 协议 <协议>
(5)@interface 类 (分类) <协议>
(6)@interface 子类 () <协议>
所对应的就是:
(1)一个类遵守协议,这个基本上说清楚了。当然如果是遵守多个协议的话,那就是一个累加效果。同理。
(2)一个子类继承父类,并且这个子类遵守某些协议。这种情况和第1中一样,同理。
(3)声明一个协议,上面基本说清楚了。
(4)这种写法就是合并协议,这个新声明的协议意味着,如果哪个类遵守了这个新协议,那么那个合并协议也同样要遵守。
(5)一个遵守某些协议的分类。这种做法挺值得提倡。因为苹果提出分类的目的也是减轻一个类的“负担”。比如,让某个协议的实现放置在这个类的分类中去实现。这样也增加了代码的可读性。
(6)扩展遵守某些协议。这里会引发出一个讨论,协议的遵守声明应该放在头文件,还是放置在实现文件中的扩展声明中呢?
另外,由于协议中声明了属性和方法,如果协议中的属性名或者方法名与类中、分类中、合并协议中发生重名怎么办?其实没有关系。因为协议的表层约束就是使遵守协议的类或者是分类中要实现协议中属性的getter和setter方法,实例方法和类方法。只要实现文件中实现了这些方法,就满足了协议所带来的的要求,所以,重名问题上没关系。
===========================
接下来就可以讨论类的相关问题了。
===========================
关于类,围绕的重点依然是上面所提到的:“属性”、“方法(类方法、实例方法)、实例变量(各种范围类型)”来进行讨论。
首先我们看看比较单纯简单的类,根类有NSObject和NSProxy,通过查看头文件会发现,虽然这两个根类没有父类,但是这两个根类都遵守NSObject协议。这样看来,我们最开始格物的对象应该就是下面这个了:
观望类的头文件,一般就是属性和方法的声明。进一步讲,声明属性无非就是声明了两个方法:getter和setter方法,因此可以认为类的头文件就是方法的声明。这个也很符合OOP变成的思想,头文件只声明方法,其他对象想读取或者设置实例对象的状态都必须通过方法。
类的实现文件中除了头文件声明方法的实现之外,也有其他方法的实现和实例变量的声明、属性的声明。
===============================
我突然想换一种方式讲解后面的内容了。文章的开始花了很多时间把“协议”、“代理”讲解清楚了,接下来主要是把类、分类、扩展这些内容表述清楚。
我接下来的思路就是:先把属性、实例变量(声明、范围说明)、方法(类方法、实例方法)进行一个讲解,然后就是把分类和扩展也进行分别的讲解,最后,将类的继承、类的分类、类的扩展三者揉在一起进行推演。最终的目的是希望充分的理解Objective-C这门语言设计「类、分类、扩展、协议」的良苦用心。
===============================
首先,一个类最基本的就是:变量 + 方法。
这里的变量就是OC语言中的成员变量、实例变量。
如果将成员变量在头文件中声明,成员变量被访问的权限有三种:
(1)@public:公共
本类(包括分类中)、子类(包括子类的分类中)、外部类都可以直接访问。
(2)@protected:保护(默认)
本类(包括分类中)、子类(包括子类的分类中)可以直接访问,外部类不可以直接访问。
(3)@private:私有
本类(包括分类中)可以直接访问,子类(包括子类的分类中)、外部类不可以直接访问。
如果将成员变量在实现文件中声明,成员变量只能在本类的实现文件中访问。
大家可以将上面的结论用代码验证下,注意成员变量的访问符是“->”。
---------------------------------------
然后,方法有类方法和实例方法两种,类方法也称之为静态方法。区别如下:
如果方法在头文件中声明的,那么:
本类、本类的分类、子类、子类的分类中都是可以调用这个方法的。
如果方法没有在头文件中声明,只是在实现文件中实现出来,那么这个方法只能在本类的实现文件中被调用。
---------------------------------------
将上面的成员变量和方法理清楚以后,再来看属性,就会清晰很多了。因为“属性”只是一种语言特性,“点语法”也是一种语言特性,是一种语法格式。
声明一个属性无非就是三点:声明一个私有的带下划线的成员变量,声明getter和setter方法。
如果在头文件中声明一个属性name,结合上面讲解的成员变量和方法,那么成员变量_name只能在本类的实现文件中被访问。-name和-setName:方法,本类、本类的分类、子类、子类的分类中都是可以调用的。
---------------------------------------
=未完,待续=
参考:
【1】https://www.jianshu.com/p/1c315df9d6ff(iOS中实例变量与属性的区别)
【2】https://www.jianshu.com/p/e4d278e5a254(同时重写getter和setter方法时报错)
【3】https://blog.csdn.net/songchunmin_/article/details/51479866(@synthesize和@dynamic的区别)
【4】
【5】