swift
官方API
中到处都有面向协议编程的影子.在swift
中,很多数据类型的底层都是struct
结构体,而结构体是不支持继承的.所以就只能通过遵守协议来扩展功能.
比如说Int
类型:
比如说现在有以下两个类,他们有一个共同的方法test
:
class A_VC: UIViewController{
func test{
}
}
class B_VC: UITableViewController{
func test{
}
}
现在要把这个共有的test
方法抽离出来,怎么做呢?
做法一:
写一个RootVC
继承自UIViewController
,再写一个继承自RootVC
的RootTableViewController
:
这种做法可以解决问题,缺点就是需要额外增加两个类.
做法二:
为UIViewController
添加一个分类,把共有的方法写在分类里.
这种做法的弊端就更明显了,UIViewController
会越来越臃肿,并且会影响其他左右的子类.
做法三:
将共有方法抽取到一个单独的类中,A_VC , B_VC
拥有这个类作为属性.
这种做法的缺点就是,会增加很多依赖关系.
做法四:
将共有方法放到协议中,A_VC , B_VC
遵守这个协议.
毫无疑问,第四种做法是最简洁的,最有效的.
面向协议编程有以下几个注意点:
- 优先考虑创建协议,而不是创建父类
- 优先考虑值类型
( struct , enum )
,而不是引用类型( class )
.因为class
可以继承. 而值类型不支持继承,这样就会强迫你是用协议来解决问题. - 巧用协议的扩展功能.
swift
的协议很强大的一点就是支持协议扩展,我们可以利用这点解决很多问题.
利用协议实现前缀效果:
比如说现在要实现一个功能,给一个字符串,统计字符串中数字的个数.
我们可以给String
扩展一个方法,这个方法就是返回数字的个数:
extension String{
func numOfCount() -> Int{
var count = 0
for c in self where ("0"..."9").contains(c){
count += 1
}
return count
}
}
print("123abc".numOfCount())// 3
这样就满足了需求,但是我们扩展的方法有可能和系统自带的方法同名,这种可能性还是存在的.所以为了和系统自带的方法区分开,我们需要给这个方法添加前缀,目的是达到这种效果:
"123abc".pe.numOfCount()
增加一个pe
前缀,从上面调用的层次可以看出来,pe
是String
里面的一个属性,所以我们首先要给String
扩展一个pe
属性:
现在字符串就可以调用pe
属性了,我们还需要将numOfCount
方法放到PE
类型中:
下面我们再为Array
添加一个判断下标是否越界的方法:
extension Array{
var pe: PE{
PE(self)
}
}
struct PE {
var str: String
var arr: Array<Any>
init(_ s: String) {
self.str = s
self.arr = []
}
init(_ a: Array<Any>) {
self.arr = a
self.str = ""
}
func numOfCount() -> Int{
var count = 0
for c in str where ("0"..."9").contains(c){
count += 1
}
return count
}
func safeIndex(_ index: Int) -> Any?{
if (arr.startIndex ..< arr.endIndex).contains(index) {
return arr[index]
}
return nil
}
}
我们首先要给Array
扩展一个pe
属性,然后还要在PE
中添加一个数组类型的属性和safeIndex
方法.
如果我们再给其他类扩展方法,同样要做上面的工作,可以发现有大量的工作是重复的.那么我们怎么简化呢?
首先想到的就是使用泛型:
第一步: 把pe
属性改成泛型:
把pe
属性改成泛型后,PE
中就可以只设置一个属性了:
但是会有另外一个问题,怎么区分numOfCount , safeIndex
中的base
是String
还是Array
呢?
这时候就要用到协议了.
我们定义一个协议,把PE
的base
属性和init
方法抽取到协议中:
//定义一个协议
protocol PrefixAble{}
extension PrefixAble{
var pe: PE<Self>{
PE(self)
}
}
//PE类型
struct PE<Base>{
var base: Base
init(_ s: Base) {
self.base = s
}
}
然后再让String , Array
遵守PrefixAble
协议,遵守PrefixAble
协议后,String , Array
内部就有pe
属性了.
//让 String 遵守协议,达到 string.pe 效果
extension String: PrefixAble{}
//让 Array 遵守协议,达到 array.pe 效果
extension Array: PrefixAble{}
现在就剩下numOfCount
和safeIndex
两个方法了,我们不能像之前那样把两个方法直接放到PE
类型中,如果那样做就无法区分PE
中的base
具体是哪种数据类型.并且如果都放到PE
类型中,那么String
也能调用safeIndex
,Array
也能够调用numOfCount
.功能是混乱的.
怎样做到String
只能调用它自己的numOfCount
,Array
只能调用它自己的safeIndex
方法呢?
我么可以根据PE
的泛型Base
分情况给PE
扩展具体的方法:
//根据 PE 的泛型 Base ,分情况给 PE 扩展方法
// 给 String 扩展 numOfCount 方法
extension PE where Base == String{
func numOfCount() -> Int{
var count = 0
for c in base where ("0"..."9").contains(c){
count += 1
}
return count
}
}
// 给 Array 扩展 safeIndex 方法
extension PE where Base == Array<Any>{
func safeIndex(_ index: Int) -> Any?{
if (base.startIndex ..< base.endIndex).contains(index) {
return base[index]
}
return nil
}
}
这样还不完美,因为现在只扩展了实例方法,如果我们还想要扩展类型方法呢?
很简单,我们只需要在协议中添加类型属性,然后在系统类中扩展类型方法即可,如下图:
现在我们已经给系统类添加了实例方法和类方法,还有一种mutating
方法没有测试.我们知道默认是不可以修改结构体内存的,如果想要修改结构体内存需要添加mutating
关键字.我们现在给PE
扩展一个mutating
方法,测试一下能否正常使用:
可以看到mutating
方法不可使用,从报的错误中可以看到,提示的pe
属性是只读的,所以我们要向修改pe
属性需要把它变为可读可写的:
这样就解决问题了.
还有一个问题,我们虽然为String
添加了前缀,并且扩展了numOfCount
方法,但是NSString , NSMutableString
能使用吗?
显然NSString , NSMutableString
是不能使用numOfCount
方法的,怎样才能让NSString , NSMutableString , String
都能使用这个方法呢?
我们可以这样做:
extension NSString: PrefixAble{}
extension PE where Base == NSString{
func numOfCount() -> Int{
var count = 0
for c in base as String where ("0"..."9").contains(c){
count += 1
}
return count
}
}
让NSString
遵守协议,并且给PE
扩展方法.但是这样做和String
有大量重复的代码.
怎样优化呢?这时候就要找String , NSString , NSMutableString
的共同点,它们的共同点就是都能通过字面量来初始化,也就是它们都遵守了ExpressibleByStringLiteral
协议,所以我们只需要在扩展PE
时限制Base
就可以了:
//让 String 遵守协议,达到 string.pe 效果
extension String: PrefixAble{}
extension NSString: PrefixAble{}
//通过协议限制 Base 类型
extension PE where Base: ExpressibleByStringLiteral{
func numOfCount() -> Int{
var count = 0
for c in base as! String where ("0"..."9").contains(c){
count += 1
}
return count
}
mutating func mutatingTextFunc(){
}
//扩展类方法
static func testStaticFunc(){
}
}
巧妙利用协议实现类型判断
如果我们要判断某个实例对象是不是某种类型可以像下面这样直接判断:
//判断一个实例对象是不是某种类型
func isArray(_ value: Any) -> Bool{
value is [Any]
}
let arr1: Array = [1,2,3]
let arr2: NSArray = [1,2,3]
let arr3: NSMutableArray = [1,2,3]
print(isArray(arr1)) //true
print(isArray(arr2)) //true
print(isArray(arr3)) //true
如果我们想判断某个类型是不是某种类型,我们用上面的方法试一下:
//判断一个类型是不是某种类型
func isArrayType(_ value: Any.Type) -> Bool{
value is Array<Any>.Type
}
print(isArrayType([Any].self)) //true
print(isArrayType([Int].self)) //false
print(isArrayType(NSArray.self)) //false
print(isArrayType(NSMutableArray.self)) //false
可以看到,出了Any].self
判断正确以外,其他的都判断失败.为什么会这样呢?因为swift
认为[Int].Type
并不是[Any].Type
类型.
所以我们可以完善一下判断条件:
func isArrayType(_ value: Any.Type) -> Bool{
if value is Array<Any>.Type || value is Array<Int>.Type || value is NSArray.Type || value is NSMutableArray.Type{
return true
}
return false
}
print(isArrayType([Any].self)) //true
print(isArrayType([Int].self)) //true
print(isArrayType(NSArray.self)) //true
print(isArrayType(NSMutableArray.self)) //true
这样就能解决问题了,但是如果数组中装的是Double , String
呢,我们是不是还要继续增加判断条件?显然这种做法是不科学的.
我们可以利用协议来进行类型推断:
//定义一个协议
protocol ArrayType {}
//让 Array, NSArray 遵守协议
extension Array: ArrayType{}
extension NSArray: ArrayType{}
func isArrayType(_ value: Any.Type) -> Bool{
//根据协议推断类型
value is ArrayType.Type
}
print(isArrayType([Any].self)) //true
print(isArrayType([Int].self)) //true
print(isArrayType(NSArray.self)) //true
print(isArrayType(NSMutableArray.self)) //true
这样就不用做很多判断了.
其实上面给Array
扩展safeIndex
方法的时候也有这种问题:
如果我们在定义arr
变量时没有定义类型var arr: [Any] = [1,2,3,4]
就会报错,报的错误是[Int] 和 [Any]
不匹配.
现在我们可以使用协议来解决这个问题:
//定义一个协议
protocol ArrayType {}
//让 Array, NSArray 遵守协议
extension Array: ArrayType{}
extension NSArray: ArrayType{}
// 给 Array 扩展 safeIndex 方法
extension PE where Base: ArrayType{
func safeIndex(_ index: Int) -> Any?{
let arr = (base as! Array<Any>)
if (arr.startIndex ..< arr.endIndex).contains(index) {
return arr[index]
}
return nil
}
}
网友评论