• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Swift中final、extension、override、@objc等关键字的使用

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

1. final

final关键字在大多数的编程语言中都存在,表示不允许对其修饰的内容进行继承或者重新操作。
Swift中,final关键字可以在class、func和var前修饰。

1.1 final正确的使用场景 - 权限控制
也就是说这个类或方法不希望被继承和重写,具体情况如下:

(1)类或者方法的功能确实已经完备了
这种通常是一些辅助性质的工具类或者方法,特别那种只包含类方法而没有实例方法的类。比如MD5加密类这种,算法都十分固定,我们基本不会再继承和重写。

(2)避免子类继承和修改造成危险
有些方法如果被子类继承重写会造成破坏性的后果,导致无法正常工作,则需要将其标为final加以保护。

(3)为了让父类中某些代码一定会执行
父类的方法如果想要其中一些关键代码在继承重写后仍必须执行(比如状态配置、认证等)。我们可以把父类的方法定义成final,同时将内部可以继承的部分剥离出来,供子类继承重写。

2. @discardableResult

swift正常的方法如果有返回值的话,调用的时候必须有一个接收方,否则的话编译器会报一个警告,如果在方法前加上 @discardableResult 不处理的时候就不会有警告了。也可以用一个通配符接收方法返回值,可以达到同样的目的。
@discardableResult:

通配符:

3. override

override关键字: 在Swift语法中派生类要覆盖基类方法、下标脚本和属性的get/set时,要使用override关键字。

3.1 override 关键字:
1.在子类中重写父类的实例方法, 使用子类的实例对象调用.
class关键字是写在父类中的, 声明这个方法是类方法, 并且可以被子类重写.
override关键字是写在子类中的, 声明这个方法是重写父类的方法.

3.2 class 关键字:
在方法(func 关键字)前 写class关键字有两个作用:

1.在父类中声明这个方法是类方法, 使用类名调用, 本类和子类的类名都可以调用.
2.这个类方法可以被子类重写, 重写后的方法使用子类的实例对象调用.

4. extension

在swift中,extension与Objective-C的category有点类似,但是extension比起category来说更加强大和灵活,它不仅可以扩展某种类型或结构体的方法,同时它还可以与protocol等结合使用,编写出更加灵活和强大的代码。
swift可以为特定的class, strut, enum或者protocol添加新的特性。
swift的extension可以做如下几件事,

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

4.1 语法

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

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

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

extension SomeType: AnotherProtocol {
    // implentations of AnotherProtocol
}

4.2 添加计算属性

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"

4.3 添加方法

extension Int {
    func repetitions(task: () -> Void) {
        for _ in 0..<self {
            task()
        }
    }
}
3.repetitions {
    print("Hello!")
}
// Hello!
// Hello!
// Hello!

4.4 添加突变方法 - 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关键字与extension, protocol结合使用,可以用更简洁的代码实现更复杂的功能。
4.5 添加附属脚本 - 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

4.6 添加嵌套类型 - 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 + "

5. @objc

在Swift代码中,使用@objc修饰后的类型,可以直接供Objective-C调用。
在swift 中 如果一个按钮添加点击方法 如果定义为Private 或者 定义为 FilePrivate 那么会在Addtaget方法中找不到私有方法

但是又不想把方法暴露出来,避免外界访问 ,那么可以在私有方法前加 @objc 修饰 那么它就能找到那个方法了

@objc 作用

5.1 fileprivate 或者 private 保证方法私有 能在同一个类 或者 同一个文件(extension)中访问这个方法 如果定义为private 那么只能在一个类中访问 不能在类扩展中访问

5.2 允许这个函数在“运行时”通过oc的消息机制调用

6. 懒加载(lazy)

懒加载定义: var 变量名: 类型 = 闭包()
示例

private lazy var customNav: UIView = {
        let customNav = UIView(frame: CGRectMake(0, 0, AppWidth, NavigationH))
        customNav.backgroundColor = UIColor.whiteColor()
        customNav.alpha = 0.0
        return customNav
    }()

6.1 简单表达式

lazy var first = NSArray(objects: "1","2")

6.2 闭包

 lazy var second:String = { 
        return "second" 
 }()

7.Designated、Convenience和Required

7.1 在 OC 中 init 方法是非常不安全的,没人能够保证 init 只被调用一次,也没有人保证在初始化方法调用以后实例的各个变量都完成初始化,甚至如果在初始化里使用属性进行设置的的话,还可能会造成各种问题。Swift 强化了 designated 初始化方法的地位。swift 中不加修饰的 init 方法都需要在方法中保证所有非 Optional 得实例变量被赋值初始化,而在子类中也强制(显示或隐式的)调用 super 版本的 designated 初始化,所以无论怎样被初始化的对象总是可以完成完整的初始化的。

class ClassA {
    let numA: Int
    init(num: Int) { // 不加修饰的 init 方法都需要在方法中保证所有非 Optional 得实例变量被赋值初始化
        numA = num
    }
}

class ClassB: ClassA {
    let numB: Int
    override init(num: Int) {
        numB = num + 1  // 在 init 里我们可以对 let 的实例常量进行赋值,这是初始化方法的重要特点。正常情况下 let 声明的值是不可变的,无法被赋值,这对构建线程安全的 API 十分有用。而 init 只可能被调用一次,所以在 init 里我们可以为不变量进行赋值,而不会引起任何线程安全的问题
        super.init(num: num)
    }
}

7.2 与 designated 初始化方法啊对应的是在 init 前加上 convenience 关键字的初始化方法。这类方法只作为补充和提供使用上的方便。所有的 convenience 初始化方法都必须调用同一个类中的 designated 初始化完成设置,另外 convenience 的初始化方法是不能被子类重写或者是从子类中以 super 的方式被调用的。

class ClassAA {
    let numA: Int
    init(num: Int) {
        numA = num
    }
    convenience init(bigNum: Bool) {
        self.init(num: bigNum ? 10000 : 1) // 所有的 convenience 初始化方法都必须调用同一个类中的 designated 初始化完成设置
    }
}

class ClassBB: ClassAA {
    let numB: Int
    override init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}

只要在子类中实现重写了父类 convenience 方法所需要的 init 方法的话,我们在子类中就可以使用父类的 convenience 初始化方法了。比如上面我们及时在 ClassBB 中没有 bigNum 版本的 convenience init(bigNum: Bool),我们仍然是可以是用这个方法来完成子类初始化的:

let anObj = ClassBB(bigNum: true)
print(anObj.numA, anObj.numB)
总结:初始化方法永远遵循以下两个原则

1.初始化路径必须保证对象完全初始化,这可以通过调用本类型的 designated 初始化方法得到保证;

2.子类的 designated 初始化方法必须调用父类的 designated 方法,以保证父类也完成初始化。

7.3 对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样做的最大的好处是可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。

下面的代码中如果希望初始化方法对于子类一定可用,就将

init(num: Int) 声明为必须。
class ClassAAA {
    let numA: Int
    required init(num: Int) {
        numA = num
    }
    required convenience init(bigNum: Bool) {
        self.init(num: bigNum ? 10000 : 1)
    } }
class ClassBBB: ClassAAA {
    let numB: Int
    required init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}
let sencondObj = ClassBB(bigNum: true)
print(sencondObj.numA, sencondObj.numB)

8. mutating

Swift中protocol的功能比OC中强大很多,不仅能再class中实现,同时也适用于struct、enum。
使用 mutating 关键字修饰方法是为了能在该方法中修改 struct 或是 enum 的变量,在设计接口的时候,也要考虑到使用者程序的扩展性。所以要多考虑使用mutating来修饰方法。
先定义一个protocol

protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

一个class来遵守这个协议

class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class"
    var anotherProperty: Int = 110
    // 在 class 中实现带有mutating方法的接口时,不用mutating进行修饰。因为对于class来说,类的成员变量和方法都是透明的,所以不必使用 mutating 来进行修饰
    func adjust() {
        simpleDescription += " Now 100% adjusted"
    }
}
// 打印结果
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription

struct中实现协议ExampleProtocol

struct SimpleStruct: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += "(adjusted)"
    }
}

enum中实现协议ExampleProtocol

enum SimpleEnum: ExampleProtocol {
    case First, Second, Third
    var simpleDescription: String {
        get {
            switch self {
            case .First:
                return "first"
            case .Second:
                return "second"
            case .Third:
                return "third"
            }
        }

        set {
            simpleDescription = newValue
        }
    }
    
    mutating func adjust() {

    }
}

错误信息
如果将ExampleProtocol中修饰方法的mutating去掉,编译器会报错说没有实现protocol。如果将struct中的mutating去掉,则会报错不能改变结构体的成员。


鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Swift-Dictionary发布时间:2022-07-13
下一篇:
iOS-SwiftNSData数据发布时间:2022-07-13
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap