站在OC的基础上快速理解Swift的类与结构体
首先我发现在编写Swift代码的时候,经常会遇到Xcode不能提示,卡顿,直接闪退等问题,尤其是在Swift和OC混编时。(不知道其他开发者是否也有这样的经历,但是我相信这样的问题,很快会得到解决
然后感觉Swift并不像网上很多朋友说的那样简单。有很多细节问题是值得注意的,甚至有很多思路是颠覆了传统的开发语言的!又有很多特性是结合了其他编程语言的优点!
Swift,我个人觉得是趋势,是目前最前卫的开发语言,结合了众多开发语言的优点!
网上已经有很多Swift相关的论文和博客了,这里我不做推荐和转载了!我归纳一下类和结构体,以及相关的知识吧!
Swift中,类和结构体都是对数据和方法进行封装的常用做法!首先我们来看看他们的共同之处:
都可以有属性和方法;
都有构造器;
都支持附属脚本;
都支持扩展;
都支持协议。
然后我们来看看他们的不同之处:
类有继承;
结构体有一个自动生成的逐一初始化构造器;
在做赋值操作时,结构体总是被拷贝(Array有特殊处理);
结构体可以声明静态的属性和方法;
从设计模式的角度来分析,类的设计更侧重于对功能的封装,而结构体的设计更侧重于对数据的封装。(汽车与书架来区分类与结构体)
一、构造过程
1. 默认值
在OC 类和结构的成员属性会隐式的给出默认值,对象的默认值是nil,基本数据类型的默认值是0。但是在 Swift 中属性必须显示的设置默认值,Swift会在调用构造器之前调用默认值构造器对所有在设置了默认值的属性进行初始化。
当一个构造过程完成的时候,被构造的类或者结构体的实例的存储属性都会是有值的,否则编译错误!
class A : SuperClass { var _a = 1 // 默认值 = Int(1) var _b: Int? // 默认值 = nil var _c: Int // 无默认值,必须在构造器中赋值 var _d: Int! // 默认值 = nil init() { _c = 1 // 在调用父类的构造器之前,必须给_c赋值,否则编译错误 super.init() } ... }
2. 构造器
类似与OC的 -(instance)init 方法。和OC最大的区别是 OC 初始化完成后返回的是这个对象的指针,而Swift初始化完成后返回的是这个对象的引用。
根据构造器的实际构造过程可将构造器分为 便利构造器 和 指定构造器,但只有指定构造器才是真实的构造器,便利构造器只是为指定构造器添加一个便利的参数传入,或者增加一些附加操作。
以下几点在开发的时候需要注意:
类和结构体都需要在构造器完成对所有存储属性的初始化;
子类在调用父类的构造器之前必须确保本类声明的所有的存储属性都已经有值了;
便利构造器必须直接或者间接的调用本类的指定构造器。
class A : SomeClass { var _a: Int var _b = 1_000 // 指定构造器 init() { self._a = 5 // 如果是这样声明的 'var _a: Int = 5' or 'var _a = 5',那么init()构造器可以不写而直接调用 super.init() } // 指定构造器 init(a: Int) { self._a = a // 因为 _a 属性没有默认值,所以在调用 super.init() 前必须给 _a 赋值,否则编译错误! super.init() } // 便利构造器 convince init(a: Int, b: Int) { self.init(a: a + b) // 直接调用指定构造器 } // 便利构造器 convince init(a: Int, b: Int, c: Int) { self.init(a: a, b: b + c) // 间接调用指定构造器 } ... }
3. 析构器
和OC的 dealloc 很像,这里不多做解释了。
class A { ... deinit { //... 析构器内部处理 } ... }
二、属性
OC中的类有属性,也有实例变量。但Swift中将类的实例变量和属性统一用属性来实现。
1. 存储属性
简单来说就是一个成员变量/常量。Swift可以为存储属性增加属性监视器来响应属性值被设置时的变化。
class SomeClass { let _a = 100 // 常量 var _b: Int // 没有默认值的变量 var _c = 200 // 默认值=200的Int变量 var _d: Int = 200 { // 默认值=200的Int变量,如果增加了属性监视器,必须显示的声明变量类型 didSet { println("A 的 _c 属性 didSet = old:\(oldValue) -> \(self._c)") // oldValue 表示曾经的值 } willSet { println("A 的 _c 属性 willSet = \(self._c) -> new:\(newValue)") // newValue 表示将会被设置的值 } } }
2. 计算属性
计算属性并不存储任何数据,他只是提供 set/get 的便利接口。
class A { class var a: Int { // 这是类的计算属性 (区别于下面类的实例的计算属性) return 1001 } private(set) var _b = 100 // 外部可以访问,但不能修改 private var _a = 100 // 私有变量,外部不能访问 var a: Int { // 只读计算属性 return _a } var b: Int { // 可读可写的计算属性 get { retrun _a } set { _a = newValue // newValue 表示的是输入的值 } } }
3. 延迟存储属性
相信大家都听说过延迟加载(懒加载),就是为了避免一些无谓的性能开销,在真正需要该数据的时候,才真正执行数据加载操作。 Swift可以使用关键字 lazy 来声明延迟存储属性,延迟存储属性在默认值构造器中不会被初始化,而是在第一次使用前进行初始化! 虽然没被初始化,但是编译器会认为他是有值的。
全局的常量或者变量都是延迟加载的, 包括结构体的静态属性也是延迟加载的。
let some = A() class A { lazy var view = UIView(frame: CGRectZero) // 定义了一个延迟存储属性 ... }
4. 静态属性
结构体可以使用关键字 static 来声明静态存储属性。(枚举也可以有) Swift的类不支持静态属性,也不支持静态临时变量。 这可以作为Swift中声明单例的一种实现方:
class Tools { class func sharedInstance() -> Tools { struct Static { static let sharedInstance = QNTools() } return Static.sharedInstance } }
三、方法
Swift的类和结构体都可以定义自己的方法!(OC的结构体是没有方法的)
1. 参数
一个方法的参数有局部参数名称和外部参数名称,一样时写一个即可! Swift的方法的参数非常灵活,可以是值,可以是引用,可以是闭包,可以是元组... Swift的方法的返回值跟参数一样灵活
这里给出一个简单的加法方法来展示方法的参数:
class A { // eg. 一个简单的相加方法 // 完整版 class func sum1(let a/*外部参数名称*/ aValue/*内部参数名称*/: Int, let b/*外部参数名称*/ bValue/*内部参数名称*/: Int) -> Int { return aValue + bValue } // 当函数参数的外部和内部参数相同的时候,可以只写一个, 此时第一个参数的名称在调用时是隐藏的 class func sum2(a: Int, b: Int) -> Int { return a + b } // 使用 _ 符号,可以在调用的时候隐藏函数参数名称,第一个参数默认就是隐藏的 class func sum3(a: Int, _ b: Int) -> Int { // 内嵌函数的参数名称,都是可以隐藏的。而不用使用 _ 符号声明 func sum4(a: Int, b: Int) -> Int { return a + b } return sum4(a, b) } // 可使用 let/var 关键字来声明参数是作为常量还是变量被传入,(如果是常量,可以不用显示的写 let) class func sum4(let a: Int, _ b: Int) -> Int { // 内嵌函数的参数名称,都是可以隐藏的。而不用使用 _ 符号声明 func sum4(a: Int, b: Int) -> Int { return a + b } return sum4(a, b) } // 可使用 let/var 关键字来声明参数是作为常量还是变量被传入,(如果是常量,可以不用显示的写 let) class func sum5(let a: Int, let _ b: Int) -> Int { // 内嵌函数的参数名称,都是可以隐藏的。而不用使用 _ 符号声明 return a + b } class func sum6(var a: Int, var _ b: Int) -> Int { // 内嵌函数的参数名称,都是可以隐藏的。而不用使用 _ 符号声明 a++ b++ return a + b } // 可使用 inout 关键字,传引用 class func add(inout value: Int) { value++ } }
A.sum1(a: 1, b: 2) // result: 3 A.sum2(1, b: 2) // result: 3 A.sum3(1, 2) // result: 3 var aTemp: Int = 1001 // aTemp = 1001 A.add(&aTemp) aTemp // aTemp = 1002 A.sum5(1, 2) // result: 3 A.sum6(1, 2) // result: 5
2. 实例方法
类或者结构体的实例所拥有的方法!
class A { private(set) var a: Int = 100 func reset() -> Void { self.a = 100 } } struct S { var a: Int = 100 mutating func reset() -> Void { // 注意: 在结构体和枚举的实例方法中如果需要修改属性,则需要增加 mutating 字段 self.a = 100 } }
3. 类方法
Swift 中类的本身也是一个实例。他没有存储属性、但拥有计算属性和方法!
参考 “1.参数” 中的示例
4. 静态方法
结构体可以使用关键字 static 来声明静态方法。
struct S { static func name() -> String { return "Liuyu" } }
四、附属脚本
Swift 提供了附属脚本的语法来简化类似查询的行为。如访问一个数组的第n个元素,array[n], 访问一个字典的值 dictionary[“key”],这些都可以通过附属脚本来实现。 这里给出一个通过字符串类型的索引来查询数组中的元素的例子。
// 扩展一个通过字符串类型的位置来访问数据的附属脚本 extension Array { subscript(index: String) -> T? { if let iIndex = index.toInt() { return self[iIndex] } return nil } } let array = ["Liu0", "Liu1", "Liu2"] array[1] // result : Liu1 array["1"]! // result : Liu1
五、继承
和OC一样,Swift类可以通过继承来获取父类的属性和方法(有限制)。 Swift的类的在继承上增加了很多安全措施
class SomeSuperClass { func someFunc1() { ... } // 定义不能被子类重写的方法,需要加上 final 关键字 final func someFunc2() { ... } } class SomeClass : SomeSuperClass { // 重载父类的方法,需要加上 override 关键字 override func someFunc1() { ... super.someFunc1() } // 不能被子类重写的方法 override func someFunc2() { // Error ... super.someFunc2() } }
六、扩展
扩展就是向一个已有的类、结构体或枚举类型添加新功能。 这包括在没有权限获取原始源代码的情况下扩展类型的能力(即逆向建模)。 扩展和OC中的类别(categories)类似,但又有很多细微的区别:
扩展没有名字,一旦扩展就会应用到整个工程(模块)
在扩展中如果要重载现有的方法,需加上override关键字 (不建议修改现有的方法)
可定义新的嵌套类型