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去掉,则会报错不能改变结构体的成员。
|
请发表评论