Swift开发实战程序员编程知识点

Swift泛型

2017-04-13  本文已影响68人  蓝色达风

前言

OC缺乏一个重要特性,不支持泛型。Swift拥有了这一特性,是灵活性的语法,在函数、结构体、类、枚举中都可以应用,相当于暂位符的作用,当类型暂时不确定,只有等到调用函数时才能确定具体类型的时候可以引入泛型。

简单理解泛型就是先占位,具体占位做什么,用的时候再说。Swift的Array和Dictionary类型都是泛型集

应用场景

简单模仿一个栈操作,主要实现出栈、入栈两个功能

// MARK: - 模仿栈
class TestStack {
    var valueArray: [Int] = []
    
    /// 压栈
    func push(value: Int) -> () {
        valueArray.append(value)
    }
    
    /// 出栈
    func pop() -> (Int?) {
        if let lastValue = valueArray.last {
            valueArray.removeLast()
            return lastValue
            
        }else {
            return nil
        }
    }
}

这样实现的栈只能操作Int类型,如果需求变更,要处理String类型,怎么解决?替换所有Int为String可以解决,但这种代码明显上不了台面。此外另外我们还会想到Any类型,如下

// MARK: - 模仿栈
class TestStack {
    var valueArray: [Any] = []
    
    /// 压栈
    func push(value: Any) -> () {
        valueArray.append(value)
    }
    
    /// 出栈
    func pop() -> (Any?) {
        if let lastValue = valueArray.last {
            valueArray.removeLast()
            return lastValue
            
        }else {
            return nil
        }
    }
}

如此TestStack可操作类型就不局限于一种了,但是带来了两个问题:1、数据类型安全问题;2、每次对栈进行操作时,都需要进行一系列繁琐的类型转换(casting操作,使用as来进行类型转换)

这里简单介绍下Any和AnyObject的区别:

在 Swift 3 之前,我们可以写完一个项目都只用 AnyObject 来代表大多数实例,好像不用与 Any 类型打交道。但事实上,Any 和 AnyObject 是有明显区别的,因为 Any 可以代表 struct、class、func 等等几乎所有类型,而 AnyObject 只能代表 class 生成的实例。

那为什么之前我们在 Swift 2 里可以用 [AnyObject] 声明数组,并且在里面放 Int、String 等 struct 类型呢?这是因为 Swift 2 中,会针对这些 Int、String 等 struct 进行一个 Implicit Bridging Conversions,在 Array 里插入他们时,编译器会自动将其 bridge 到 Objective-C 的 NSNumber、NSString 等类型,这就是为什么我们声明的 [AnyObject] 里可以放 struct 的原因。

但在 Swift 3 当中,为了达成一个门真正的跨平台语言,相关提案将 Implicit Bridging Conversions 给去掉了。所以如果你要把 String 这个 struct 放进一个 [AnyObject] 里,一定要 as NSString,这些转换都需要显示的进行了——毕竟 Linux 平台默认没有 Objective-C runtime。这样各平台的表现更加一致。当然这是其中一个目标。

参照泛型的特性,定义一个泛型类型。使用泛型后的示例代码如下:
`
// MARK: - 模仿栈
class TestStack<T> {
var valueArray: [T] = []

/// 压栈
func push(value: T) -> () {
    valueArray.append(value)
}

/// 出栈
func pop() -> (T?) {
    if let lastValue = valueArray.last {
        valueArray.removeLast()
        return lastValue
        
    }else {
        return nil
    }
}

}
`

使用泛型:在初始化时通过明确的类型(这里用Int示例)来定义参数,之后编译器会将所有的泛型(T)替换成Int类型(如此实现的栈,最大优势在于能够匹配任何类型)。

       // 示例1:Int
        let myStack1 = TestStack<Int>()
        myStack1.push(value: 1)
        myStack1.push(value: 2)
        myStack1.push(value: 3)
        print(myStack1.pop() ?? "0")
        
        // 示例2:String
        let myStack2 = TestStack<String>()
        myStack2.push(value: "a")
        myStack2.push(value: "b")
        myStack2.push(value: "c")
        print(myStack2.pop() ?? "0")

泛型约束

泛型约束大致分为以下几种:

协议约束,泛型类型必须遵循某些协议
继承约束,泛型类型必须是某个类的子类类型
条件约束,泛型类型必须满足某种条件

协议约束

还拿TestStack栈说事儿,这里添加一个函数isContainValue,要判断栈中是否包含传入的元素,需要用到等式符(==)和不等符(!=)对任何两个该类型进行比较。Swift标准库中定义了一个Equatable协议,只有支持了这个协议才可以使用等式符(==)和不等符(!=),否则报红。所有的Swift标准类型自动支持Equatable协议,但是泛型由于不确定类型,系统不知对象是否支持Equatable协议,所以直接使用==或!=报红


泛型协议约束.png

解决方案,让TestStack栈中,让泛型T遵循Equatable协议


泛型协议约束1.png
继承约束

这里定义了三个类Person、Leader、Coder,其中Leader是继承Person,

// MARK: - 人
class Person: NSObject {
    func watchTV() -> () {
        print("Person看电视:人民的名义")
    }
}


// MARK: - 领导
class Leader: Person {
    override func watchTV() -> () {
        print("Leader看电视:人民的名义")
    }
}


// MARK: - 程序员
class Coder {
    func watchTV() -> () {
        print("coder看电视:人民的名义")
    }
}
泛型继承约束1.png
    func testWatchTV<T: Person>(obj:T) -> () {
        obj.watchTV()
    }

testWatchTV函数,接受一个泛型参数,要求该泛型类型必须继承Person,否则爆红。

条件约束

在类型名后面使用where来指定对类型的特殊需求,比如限定类型实现某一个协议,限定两个类型是相同的,或者限定某个类必须有一个特定的父类等

// 定义一个协议
@objc protocol TestProtocol {
    @objc optional func testOne() -> ()
}

// MARK: - 人
class Person: NSObject, TestProtocol {
    var name: String?
    
    func watchTV() -> () {
        print("Person看电视:人民的名义")
    }
}


// MARK: - 领导
class Leader: NSObject {
    var name: String?
    
    func watchTV() -> () {
        print("Leader看电视:人民的名义")
    }
}
泛型条件约束1.png

testCondition函数要求传入的参数都是P类型的继承自Person,L类型继承自Leader,同时还附加了条件(where)这两个类都遵守了TestProtocol协议,由于Leader类型没有遵守TestProtocol协议,条件不满足所以爆红。修改方法则是Leader类遵守TestProtocol。

参考链接:http://www.jianshu.com/p/a907f0c09a60
参考链接:http://swift.gg/2015/09/16/swift-generics/

上一篇下一篇

猜你喜欢

热点阅读