介绍
很多只使用过 Objective-C 的程序员对构造函数并没有太多概念。个人理解 Swift 中的构造函数类似于 Objective-C 的初始化方法,和 Java 的构造函数非常像,但 Swift 中的构造函数有些特殊的写法,也有很多新的功能。
在Swift中我们经常会使用到 () 来创建一个类,比如说创建一份员工档案类 Staff(),这个 () 就相当于在 Objective-C 中的 alloc 它的作用是在 堆 中开辟出一块内存用于存储本类。
必选参数的构造函数
在 Swift 中一个类可以有多个构造函数,其中默认会有一个隐式的构造函数 init()。
class Staff : NSObject {
var name : String
override init() {
name = ""
super.init()
}
}



重载构造函数
一个类可以有多个重载构造函数,重载构造函数与构造函数的函数名相同,参数和参数数量不同,在实际开发中,系统会按照参数选择一个最合适的构造函数进行初始化操作。
import UIKit
class Staff : NSObject {
var name : String
override init() {
name = ""
super.init()
}
// 定义重载构造函数
init(name : String) {
// 使用参数赋值给属性
self.name = name
}
}
import UIKit
class ViewController : UIViewController {
override viewDidLoad() {
super.viewDidLoad()
let s1 = Staff()
print(“s1.name = " + s1.name)
let s2 = Staff(name : "张三")
print(“s1.name = " + s2.name)
}
}

通过重载构造函数和必选构造函数的对比可以看出,重载构造函数可以在外部设置初始值。需要注意:当提供了重载构造函数并且没有重写默认构造函数,这时系统将不再提供默认构造函数,因为默认构造函数无法给必选属性分配内存空间。
构造函数之KVC模式
在实际开发中以上两种构造函数的使用频率都比较低,一般情况下我们都会使用KVC的方式来给类的属性赋值,所以学习使用KVC构造函数是非常必要的。KVC 即 Key-Value Coding,是一种非正式的协议,可以直接通过字符串的名字 ( Key ) 来访问类属性的机制,KVC 是 Objective-C 中的方法,它的目的是在运行时给对象发送消息从而给对象的属性动态的赋值。
import UIKit
class Staff : NSObject {
// 注意:在Swift4.0必须要在定义之前用 @objc 修饰属性为 OC 属性
@objc var name : String?
// 定义KVC构造函数,KVC构造函数也是一个重载构造函数
init(dict : [String : Any]) {
// 必须先初始化父类
super.init()
// 使用字典动态改变属性值
setValuesForKeys(dict)
}
}
import UIKit
class ViewController : UIViewController {
override viewDidLoad() {
super.viewDidLoad()
let s = Staff([name : "张三"])
print("s.name = " + (s.name ?? ""))
}
}

特别注意:
- 使用这种方式一定要保证接收字典的模型里必须有对应字典
key的属性,否则程序会crash; - 使用
KVC方法之前必须调用super.init()确保对象实例化完成; - 在
Swift4.0中凡是使用KVC的属性必须使用@objc来修饰,否则会报错this class is not key value coding-compliant for the key xxx.; - 在
Swift4.0中对可选值的安全性加强了,每次使用可选值必须经过相应的解包操作,推荐使用guard let。
构造函数中基本数据类型
在 Swift 中基本数据类型是一个结构体,所以在定义基本数据类型的时候不能以Objective-C的方式来考虑。
import UIKit
class Staff : NSObject {
@objc var name : String?
@objc var age : Int = 0
init (dict : [String : Any]) {
super.init()
setValuesForKeys(dict)
}
}
import UIKit
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let s = Staff(["name" : "张三", "age" : 20])
guard let name = s.name else {
return
}
print("s.name = \(name), s.age = \(s.age)")
}
}

从上面代码在定义基本数据类型
Int 的时候设置了初始值 0,那么如果把基本数据类型设置成为可选值会发生什么,以下做分析:
当把
Int值替换为可选值报错 Property cannot be marked @objc because its type cannot be represented in Objective-C,大概意思是说:该属性不能被 @objc 修饰,因为在 Objective-C 没有这个属性类型。问题原因是:Objective-C 中没有可选属性。接下来把 @objc 去掉:
在去掉
@objc 后函数可以正常编译通过,运行试试看:
运行之后程序crash在了app入口,做
Objective-C的程序员对这样的报错信息并不陌生,没错,这就是 Objective-C 的报错信息,再来看工作台抛出的异常信息:
this class is not key value coding-compliant for the key age,意思是:在该类中无法找到兼容 KVC 的 age 属性。原因是在 Swift中 Int 是一个基本类型的结构体,Objctive-C 中并没有这个数据类型,所以无法兼容。由此可见在定义对象属性时基本数据类型不能设置成可选值,而且必须设置初始值并且修饰符 @objc 不能省略,否则 KVC 无法正常工作。
子类构造函数的继承
import UIKit
class Person : NSObject {
@objc var name : String?
@objc var age : Int = 0
init(dict : [String : Any]) {
super.init()
setValuesForKeys(dict)
}
}
import UIKit
class Staff : Person {
@objc var number : String?
}
import UIKit
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let s = Staff(["name" : "张三", "age" : 20, "number" : "1001"])
guard let name = s.name,
let number = s.number else {
return
}
print("s.name = \(name), s.age = \(s.age), s.number = \(number)")
}
}

以上代码中
Staff 类继承自 Person 类,并且子类 Staff 并没有重写父类的构造函数,在开发中我们经常会使用这种继承的方式简化代码。这个时候,如果子类没有重写父类的构造函数,系统会默认调用父类的构造函数并且一并会给子类的属性进行赋值。
解决外部字典的key与自定义模型属性名称或数量不一致
在实际开发及应用过程中,经常会遇到通过外部数据构造的字典的键与自定义数据模型类中属性的名称或数量不一致的情况。在 Objective-C 中的做法是重写 NSObject 的 - (void)setValue:(id)value forUndefinedKey:(NSString *)key {} 方法,并在方法体内部不调用super。Swift 中的解决方式与 Objective-C 相同。
override func setValue(_value: Any?, forUndefinedKey key: String) {
// 只要不在此方法体内部调用super的方法就可以解决外部字典的key与自定义模型属性名称或数量不一致
}
总结
- 构造函数是用来给对象属性做初始化操作的;
-
Swift中默认会有一个隐式的构造函数,添加了重载构造函数后该隐式构造函数将不再提供; - 一个对象可以有多个重载构造函数,函数名相同,参数和参数数量不同,系统会根据参数信息选择最合适的构造函数进行初始化;
- 重载构造函数可以在外部给对象设置初始值;
- 在
Swift中基本数据类型是一个结构体,在定义对象属性时基本数据类型不能定义为可选值而且必须设置初始值; - 子类如果没有重写父类的构造函数系统会默认调用父类的构造函数进行初始化操作;
-
Swift中解决外部字典的key与自定义模型属性名称或数量不一致的方法与Objective-C一样。