Swift - 基础之extension

swift-extension.jpg

在swift中,extension与Objective-C的category有点类似,但是extension比起category来说更加强大和灵活,它不仅可以扩展某种类型或结构体的方法,同时它还可以与protocol等结合使用,编写出更加灵活和强大的代码。

0. 概述 - extension summary

在swift中,swift可以为特定的class, strut, enum或者protocol添加新的特性。当你没有权限对源代码进行改造的时候,此时可以通过extension来对类型进行扩展。extension有点类似于OC的类别 -- category,但稍微不同的是category有名字,而extension没有名字。

swift的extension可以做如下几件事,

  • 添加计算属性 - computed properties
  • 添加方法 - methods
  • 添加初始化方法 - initializers
  • 添加附属脚本 - subscripts
  • 添加并使用嵌套类型 - nested types
  • 遵循并实现某一协议 - conform protocol

在swift中,你甚至可以对一个协议protocol进行扩展,实现其协议方法或添加额外的功能,以便于实现该协议的类型可以使用,在swift中,这叫做协议扩展 - protocol extension,后面的内容会举例说明。

注意:extension可以为类型添加新的特性,但是它不能覆盖已有的特性。例如Animal已经有eat的方法,我们不能使用extension覆盖Animal的eat方法。

1. 语法 - extension syntax

定义extension的语法非常简单,只需要使用extension关键字,如下代码,

extension SomeType {
    // new functionality to add to SomeType goes here
}

extension可以让一个特定的类型实现一个或多个协议,也就是说无论对于class, structure或enum等类型而言,都可以实现一个或多个协议,如下代码所示,

extension SomeType: SomeProtocol, AnotherProtocol {
    // implementation of protocol requirements goes here
}

上面的代码表示了简单的功能,即SomeType服从了SomeProtocol和AnotherProtocol协议,并实现两个协议中的方法,我们可以对上面的代码进行简单的拆分,让代码更加简洁易读,如下代码,

extension SomeType: SomeProtocol {
    // implentations of SomeProtocol
}

extension SomeType: AnotherProtocol {
    // implentations of AnotherProtocol
}

2. 添加多种特性

在前面已经列举了extension可以为类型添加诸多特性,下面逐一做简单解释,并举例说明。

2.1 添加计算属性 - computed properties

extension可以为已经存在的类型添加计算属性(computed properties),下面的demo为swift内置的Double类型添加了5个计算属性,分别是km, m, cm, mm, ft,用来提供基础的计算距离的功能,如下代码所示,

extension Double {
    var km: Double { return self * 1_000.0 }
    var m: Double { return self }
    var cm: Double { return self / 100.0 }
    var mm: Double { return self / 1_000.0 }
    var ft: Double { return self / 3.28084 }
}

// usage of Double extension
let oneInch = 25.4.mm
print("One inch is \(oneInch) meters")
// Prints "One inch is 0.0254 meters"
let threeFeet = 3.ft
print("Three feet is \(threeFeet) meters")
// Prints "Three feet is 0.914399970739201 meters"

什么是计算属性呢?这大概算是swift语言的特性吧,在swift中属性有两种类型,一种是存储属性,另一种是计算属性。存储属性就是存储在特定的class, struct中的一个常量或变量,可以在定义存储属性的时候指定默认值,也可以在构造过程中设置或修改存储属性的值,需要注意的是enum中并不能定义存储属性;而计算属性不直接存储值,而是提供一个getter, setter来间接获取和设置其他属性和变量的值。

归纳一下,swift中存储属性和计算属性的区别如下表,

存储属性 计算属性
存储常量或变量属性 用来计算数值,不是存储数值
定义在class, struct中 定义在class, struct, enum中

在上面的demo中,这些计算属性表示一个Double值应该被当做一个特定的长度单位,即千米、米、厘米、毫米等。虽然它们被当做计算属性来实现,但可以将这些属性名称通过.操作符放在浮点值的后面。Double类型的浮点值1.0表示“一米”,这也是为什么.m计算属性返回了该Double值本身。同样,1米约等于3.28084寸(feet),所以ft计算属性转换成"米"时候需要乘以3.28084。

这些属性都是只读(read-only)的计算属性,所以为了方便,在定义它们的时候只需要使用get关键字,不过为了简洁和直观,这个demo直接省略了get关键字。这些计算属性的返回值是Double类型,所以可以用在任何Double类型适用的地方进行数学计算,例如下面的代码,

let aMarathon = 42.km + 195.m
print("A marathon is \(aMarathon) meters long")
// Prints "A marathon is 42195.0 meters long"

注意:extension可以添加计算属性,但是不能添加存储属性,也不能为当前属性添加观察者。

2.2 添加构造器 - initializers

extension可以为已经存在的类(class)添加便捷初始化方法。这样你就可以对其他的类型进行扩展,以便该类型在初始化时可以接受你自定义的类型作为初始化的参数,这样就为该类型添加了额外的初始化方法,在创建该类型的对象时提供了更多的选择。

下面的例子定义了一个结构体类型Rect,它表示一个长方形几何图形;同样我们定义了另外两个结构体类型Size和Point,两者的所有属性都设置了0.0默认值。如下代码所示,

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
}

因为结构体Rect为它所有属性都提供了默认值,所以它自动拥有一个默认初始化方法和一个成员逐一(memberwise)初始化方法,我们可以使用这两个初始化方法来创建Rect结构体的实例,如下代码,

let defaultRect = Rect()
let memberwiseRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))

现在我们可以通过extension为结构体Rect添加一个额外的初始化方法,该初始化方法接受一个point和size作为参数,如下代码,

extension Rect {
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

这个自定义的初始化方法实现的逻辑并不复杂,首先它通过传入的Point参数center和Size参数size计算出(originX, originY),然后调用该结构体的逐一成员初始化方法,即init(origin:size:),该初始化方法将origin和size参数存储在对应的属性中,如下代码所示,

let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

注意:如果你使用extension来添加初始化方法,你同样需要保证该类型的初始化方法结束时,它的每一个属性被完全的初始化了。

2.3.1 添加方法 - methods

跟OC的category类似,通过extension,可以为某一类型添加实例方法(instance method)和类型方法(type method),如下代码为Int类型添加了一个名为repetitions的实例方法,

extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}

新增的repetitions(task:)方法接受一个闭包()->Void作为参数,该闭包作为一个函数,并且该函数没有入参和返回值。

如下代码所示,我们来进行一个简单的测试,

3.repetitions {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!

2.3.2 添加突变方法 - mutating method

通过extension添加的实例方法同样可以修改(modify)或突变(mutate)该实例本身,如果结构体和枚举定义的方法想要改变自身或自身的属性,那么该实例方法必须被标记为突变(mutating)的。

下面的例子为Int类型添加了一个名为square的突变方法,它的作用是计算原始值的平方,如下代码所示,

extension Int {
    mutating func square() {
        self = self * self
    }
}

var someInt = 3
someInt.square()
// someInt is now 9

这里针对mutating关键字再啰嗦一句,如果我们把mutating关键字删除,则编译器会报错,只有mutating修饰的方法才能更改实例属性和实例本身,mutating关键字与extension, protocol结合使用,可以用更简洁的代码实现更复杂的功能。笔者建议读者搜索资料,写写demo,加深印象和理解。

2.4 添加附属脚本 - subscripts

extension可以为某一个特定类型添加附属脚本subscript。那么什么是附属脚本呢?附属脚本可以定义在class, struct, enum中,可以认为是访问对象,集合或序列的快捷方式,不需要在调用实例的特定的赋值方法和访问方法。举例来说,用附属脚本访问一个Array实例中的元素可以写为someArray[index],访问Dictionary实例中的元素可以写为someDictionary[key],读者可能已经注意到,通过这种快捷方式对siwft中Array和Dictionary元素进行访问我们经常使用,所以可以推断,swift已经默认帮开发者实现了附属脚本的特性。

下面的例子为Int类型添加了整数下标的附属脚本,该附属脚本[n]返回该数字对应的十进制(decimal)的第n位的数字,当然计数方式从最右侧开始,例如,123456789[0]返回9,而123456789[1]返回8,该方法的具体实现如下代码所示,

extension Int {
    subscript(digitIndex: Int) -> Int {
        var decimalBase = 1
        for _ in 0..<digitIndex {
            decimalBase *= 10
        }
        return (self / decimalBase) % 10
    }
}

746381295[0]
// returns 5
746381295[1]
// returns 9
746381295[2]
// returns 2
746381295[8]
// returns 7

仔细分析一下subscript方法,实现的逻辑其实就是小学生的练习题,这里不做赘述。

如果一个Int数字没有足够多的位数,那么它会在最左边添加0来补全,然后返回0给调用方,如下代码,

746381295[9]
// returns 0, as if you had requested:
0746381295[9]

2.5 添加嵌套类型 - nested types

extension可以为类(class)、结构体(structure)和枚举(enumation)添加嵌套类型,如下代码,

extension Int {
    enum Kind {
        case negative, zero, positive
    }
    var kind: Kind {
        switch self {
        case 0:
            return .zero
        case let x where x > 0:
            return .positive
        default:
            return .negative
        }
    }
}

上面的demo为Int添加了嵌套的枚举类型,这个枚举名为Kind,用来表示一个数字是正数、复数还是0,之所以说是嵌套,是因为该枚举定义在Int的extension内部。(这里,我可能对嵌套的理解有误,这段理解暂时保留,欢迎读者指正。)

这个demo还为Int添加了一个计算属性(computed property),名为kind,针对数字的值不同,分别返回.zero, .positive或.negative。

现在该嵌套的枚举类型可以在任意的Int值中使用,如下代码所示,

func printIntegerKinds(_ numbers: [Int]) {
    for number in numbers {
        switch number.kind {
        case .negative:
            print("- ", terminator: "")
        case .zero:
            print("0 ", terminator: "")
        case .positive:
            print("+ ", terminator: "")
        }
    }
    print("")
}
printIntegerKinds([3, 19, -27, 0, -6, 0, 7])
// Prints "+ + - 0 - 0 + "

上面的代码简单易懂,printIntegerKinds(_:)接受一个Int类型的数组,然后遍历数组并判断每个元素的kind来判断数组元素的正、负还是0,代码很简单,不多做解释。

3. 参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,066评论 4 62
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,784评论 1 10
  • 2014年的苹果全球开发者大会(WWDC),当Craig Federighi向全世界宣布“We have new ...
    yeshenlong520阅读 2,275评论 0 9
  • 花绽放,用色彩装点世界,感动所拥有美丽世界的荣幸;阳光普照,用温情回拥大地,感恩所能够温暖他人的力量。芸芸众生的世...
    mm默mm阅读 305评论 2 6
  • 七七是一个内心成熟、做事稳重的女生,。一伙人当中,只要谁心情不好找她,她绝对能够让那个人从绝望到觉得世界充满爱!当...
    孤帆子阅读 928评论 0 3