美文网首页
从零学习Swift 17: 面向协议编程

从零学习Swift 17: 面向协议编程

作者: 小心韩国人 | 来源:发表于2020-08-06 18:49 被阅读0次

    swift官方API中到处都有面向协议编程的影子.在swift中,很多数据类型的底层都是struct结构体,而结构体是不支持继承的.所以就只能通过遵守协议来扩展功能.

    比如说Int类型:

    Int 类型遵守2个协议

    比如说现在有以下两个类,他们有一个共同的方法test:

    
    class A_VC: UIViewController{
        func test{
            
        }
    }
    
    
    class B_VC: UITableViewController{
        func test{
            
        }
    }
    
    

    现在要把这个共有的test方法抽离出来,怎么做呢?

    做法一: 写一个RootVC继承自UIViewController,再写一个继承自RootVCRootTableViewController:

    这种做法可以解决问题,缺点就是需要额外增加两个类.

    做法二:UIViewController添加一个分类,把共有的方法写在分类里.

    这种做法的弊端就更明显了,UIViewController会越来越臃肿,并且会影响其他左右的子类.

    做法三: 将共有方法抽取到一个单独的类中,A_VC , B_VC拥有这个类作为属性.

    这种做法的缺点就是,会增加很多依赖关系.

    做法四: 将共有方法放到协议中,A_VC , B_VC遵守这个协议.

    毫无疑问,第四种做法是最简洁的,最有效的.

    面向协议编程有以下几个注意点:
    1. 优先考虑创建协议,而不是创建父类
    2. 优先考虑值类型( struct , enum ),而不是引用类型( class ).因为class可以继承. 而值类型不支持继承,这样就会强迫你是用协议来解决问题.
    3. 巧用协议的扩展功能.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前缀,从上面调用的层次可以看出来,peString里面的一个属性,所以我们首先要给String扩展一个pe属性:

    增加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中的baseString还是Array呢?

    这时候就要用到协议了.

    我们定义一个协议,把PEbase属性和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{}
    
    

    现在就剩下numOfCountsafeIndex两个方法了,我们不能像之前那样把两个方法直接放到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
        }
    }
    
    

    相关文章

      网友评论

          本文标题:从零学习Swift 17: 面向协议编程

          本文链接:https://www.haomeiwen.com/subject/ntjfrktx.html