如果我们想给一个struct或者class拓展功能,最直白的方式就是修改它们的代码,把我们想追加的函数代码写进去即可。但在一些情况下,我们没有这样做的机会,最典型的场景就是我们在使用别人提供的类库,源代码不是我们写的已经被别人封装过了,自然我们也没有机会修改别人的代码来实现我们想要的功能了。

这个时候,就该我们今天的主角Extension出场了。有了Extension我们就可以对已经存在的struct或者class拓展功能了。比如,我们想判断一个整数是不是奇数,通常简单的做法是这样:

func isOdd(num:Int) -> Bool{
    return num%2 != 0
}

print(isOdd(num: 4))
print(isOdd(num: 5))

但是如果有了Extension,我们就可以把isOdd函数直接写到Int里了,至少用起来是这样的。

extension Int {
    func isOdd() -> Bool{
        return self%2 != 0
    }
}


print(4.isOdd())
print(5.isOdd())

明显上面的写法在最终调用时更加符合直觉,也更优雅。

考虑到extension中也可以扩展计算属性(Computed Properties),我们还可以再进一步:

extension Int {
    var isOdd:Bool {
        return self%2 != 0
    }   
}

print(4.isOdd)
print(5.isOdd)

extension不光可以扩展普通的函数,也可以扩展构造函数。比如下面这个例子:

import Foundation

extension Date{
    init (_ str:String){
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
        
        let target = dateFormatter.date(from: String(str))!
        self.init(timeIntervalSince1970: target.timeIntervalSince1970)
    }
}


var aDate = Date("2022-06-01")

print(aDate)

在上面的例子中,我们给Date增加了一个新的构造函数,可以传入形如yyyy-MM-dd的字符串构建一个Date

除了普通函数、计算属性以及构造函数。还有一种能够扮演逻辑功能的特殊语法我们之前还没有讲过,就是Subscripts(下标)。我们最常遇到的场景就是数组,你应该已经接触过了,比如这样:

var nums = [1,2,3,4,5]
print(nums[0])

除了数组之外,即使是struct或者class,只要源码在自己手上,都可以很容易的通过添加subscript获得对下标的支持,以达到类似于数组的使用效果。但是当源码不掌握在自己手里的时候,我们就可以借助extension对现有的structclass进行改造了。比如下面这段代码:

var str:String = "abc"
print(str[0])

我们期望程序返回a,但事实上程序会报错。这时我们就可以通过extension自己实现对String功能的增强了:

extension String {
    subscript(digitIndex: Int) -> String {
        if digitIndex < 0 || digitIndex >= self.count{
            return ""
        }
        
        let arr = self.map{String($0)}
        return arr[digitIndex]
    }
}

print("abc"[0])

有了上面的代码,我们就可以把String当成Array一样,优雅地通过下标的方式获取到对应位置的单个字符串了。如果我们传入的下标不在支持的范围内,那程序就返回一个""(空字符串)。

extension可以跟我们上次讲的protocol结合在一起使用,比如这样:

protocol Hello {
   func say()
}

extension Int:Hello{
    func say() {
        print("Hello, \(self)")
    }
}

4.say()

有了这样的特性,我们就可以让别人写的struct或者class遵照我们定义的protocol行事了。

最后再说一下,extension除了可以拓展功能外,可以拓展静态属性,一个常见的场景是在使用SwiftUI时,需要对一些公共的颜色进行预知,方便之后的使用。那么就可以这样:

import SwiftUI

extension Color {
    static  var background = Color("background")
    static  var separator = Color("separator")
    static  var textColor = Color("text_color")
}

这样我们在编写UI代码时就可以优雅地访问颜色了:

var body: some View {
    VStack{
        Text("hello")
            .foregroundColor(.textColor)
        Color.separator.frame(width:100, height:2)
    }
    .background(.background)
}

参考资料:

往期回顾: