类与对象

类与对象

定义与实例化

类的基本的定义方式如下所示:

class Vehicle {
var color: String?
var maxSpeed = 80
func description() -> String {
return "A \(self.color) vehicle"
}
func travel() {
print("Traveling at \(maxSpeed) kph")
}
}

属性(Properties)

类的类型属性:

class SomeClass {
class var computedTypeProperty: Int {
// 这里返回一个 Int 值
}
}

lazy:延迟存储属性

延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用lazy来标示一个延迟存储属性。

注意

必须将延迟存储属性声明成变量(使用var关键字),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

最简单的lazy的用法:

//直接赋值变量
lazy var players = String[]()
//赋值为初始化的某个对象
class DataImporter {
/*
DataImporter 是一个负责将外部文件中的数据导入的类。
这个类的初始化会消耗不少时间。
*/
var fileName = "data.txt"
// 这里会提供数据导入功能
}
class DataManager {
//初始化某个对象
lazy var importer = DataImporter()
var data = [String]()
// 这里会提供数据管理功能
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 实例的 importer 属性还没有被创建
//使用闭包方法
lazy var players: String[] = {
var temporaryPlayers = String[]()
temporaryPlayers.append("Mike Buss")
return temporaryPlayers
}()
//使用类方法
lazy var players = MultipeerManager.initialPlayers()
class func initialPlayers() -> String[] {
var players = ["Mike Buss"]
return players
}

适合延迟加载的场景,一般而言有两种:

(1)属性的初始值需要进行大量计算之时

就如上面代码中提及的DataImporter而言,完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。由于使用了lazy,importer属性只有在第一次被访问的时候才被创建。比如访问它的属性fileName时:

print(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出 "data.txt”

(2)对象的属性的初始值依赖与其它的属性

举例来说,你有一个Person类以及一个personalizedGreeting属性。这个personalizedGreeting属性需要在对象创建完成后才延迟加载,因为只有在对象创建完成后它才能知道问候的人是谁(person的name)。请看代码:

class Person {
var name: String
@lazy var personalizedGreeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
}

注意,你必须使用 [unowned self]来避免循环引用。[unowned self]定义了一个在闭包中需要使用的、存在于闭包外的属性/变量列表,又叫捕获列表(capture list)。

当你实例化一个person时,他的问候语greeting此时并没有创建:

let person = Person(name: "Robert Redford”)
// person.personalizedGreeting is nil

但是当你尝试打印出问候语时,这句问候语会自动生成出来:

NSLog(person.personalizedGreeting)
// personalizedGreeting is calculated when used
// and now contains the value "Hello, Robert Redford!"

Property Observer(属性监控)

利用属性监视器可以在属性设置之前(willSet)以及值变动之后(didSet)进行一系列地操作,如下这个例子是Apple官方的例子,即是在进度条值设置变化之后进行重渲染。

@IBInspectable var progress: Float = 0.75 {
didSet {
setNeedsDisplay()
}
}

方法(Methods)

访问控制

在将一个方法或属性声明为 public 时,App 中的所有人都能看到它:

// 可供所有人访问
public var publicProperty = 123
//如果将一个方法或属性声明为 private,那只能在声明它的源文件内部看到它:
// 只能在这个源文件中访问
private var privateProperty = 123
// 仅能供本模块访问
// 这里的'internal'是默认的,可以省略
internal var internalProperty = 123

静态属性/方法

Swift声明静态属性/方法的方式有两种,一种是利用class关键字,声明为类属性/方法,另一种是用static关键字。

In a class declaration, the keyword static has the same effect as marking the declaration with both the class and final declaration modifiers.

举例来说:

class ClassA {
class func func1() -> String {
return "func1"
}
static func func2() -> String {
return "func2"
}
/* same as above
class final func func2() -> String {
return "func2"
}
*/
}

static即是class final,即在Swift中,一旦声明为了static,就不可以在子类中进行复写了:

class ClassB : ClassA {
override class func func1() -> String {
return "func1 in ClassB"
}
// ERROR: Class method overrides a 'final` class method
override static func func2() -> String {
return "func2 in ClassB"
}
}

对象

实例构造

在init方法及其重载方法中,可以对于使用let声明的变量进行赋值。在 Swift 中 let 声明的值是不变量,无法被写入赋值,这对于构建线程安全的 API 十分有用。而因为 Swift 的 init 只可能被调用一次,因此在 init 中我们可以为不变量进行赋值,而不会引起任何线程安全的问题。最简单的实例化方法:

var redVehicle = Vehicle()
redVehicle.color = "Red"
redVehicle.maxSpeed = 90
redVehicle.travel() // 输出"Traveling at 90 kph" redVehicle.description() // = "A Red vehicle"

也可以重载类的构造函数,使之能够有不同的参数:

class InitAndDeinitExample {
// 指定的初始化器(也就是主初始化器)
init() {
print("I've been created!")
}
// 便捷初始化器,是调用上述指定初始化器所必需的
convenience init (text: String) {
self.init() // 这是必需的
print("I was called with the convenience initializer!")
}
// 反初始化器
deinit {
print("I'm going away!")
}
}
var example : InitAndDeinitExample?
// 使用指定的初始化器
example = InitAndDeinitExample() // 输出"I've been created!"
example = nil // 输出"I'm going away"
// 使用便捷初始化器
example = InitAndDeinitExample(text: "Hello")
// 输出"I've been created!"
// 然后输出"I was called with the convenience initializer"

创建一个可以返回 nil 的初始化器(也称为可以失败的初始化器),就在 init 关键字的后面放上一个问号,并在初始化器确定它不能成功地构造该对象时,使用 return nil:

convenience init? (value: Int) {
self.init()
if value > 5 {
// 不能初始化这个对象;返回nil,表示初始化失败 return nil
} }

在使用一个可以失败的初始化器时,任何可以在其中存储该结果的变量都是可选的:

let failableExample = InitAndDeinitExample(value: 6)
// = nil

required:父类指定子类必须实现的初始化函数

如果子类需要添加异于父类的初始化方法时,必须先要实现父类中使用required修饰符修饰过的初始化方法,并且也要使用required修饰符而不是override。

class MyClass {
var str:String
required init(str:String) {
self.str = str
}
}
class MySubClass:MyClass
{
required init(str:String) {
super.init(str: str)
}
init(i:Int) {
super.init(str:String(i))
}
}
MySubClass(i: 10)

如果子类中不需要添加任何初始化方法,我们则可以忽略父类的required初始化方法:

class MyClass {
var str:String
required init(str:String) {
self.str = str
}
}
class MySubClass:MyClass
{
}
MySubClass(str: "hello swift")

在这种情况下,编译器不会报错,因为如果子类没有任何初始化方法时,Swift会默认使用父类的初始化方法。在Apple的文档中也有相关描述:

You do not have to provide an explicit implementation of a required initializer if you can satisfy the requirement with an inherited initialiser. required修饰符的使用规则

required修饰符只能用于修饰类初始化方法。 当子类含有异于父类的初始化方法时(初始化方法参数类型和数量异于父类),子类必须要实现父类的required初始化方法,并且也要使用required修饰符而不是override。 当子类没有初始化方法时,可以不用实现父类的required初始化方法。

convenience:调用其他方法保证非Optional对象初始化

在 Objective-C 中,init 方法是非常不安全的:没有人能保证 init 只被调用一次,也没有人保证在初始化方法调用以后实例的各个变量都完成初始化,甚至如果在初始化里使用属性进行设置的话,还可能会造成各种问题,虽然 Apple 也明确说明了不应该在 init 中使用属性来访问,但是这并不是编译器强制的,因此还是会有很多开发者犯这样的错误。

所以 Swift 有了超级严格的初始化方法。一方面,Swift 强化了 designated 初始化方法的地位。Swift 中不加修饰的 init 方法都需要在方法中保证所有非 Optional 的实例变量被赋值初始化,而在子类中也强制 (显式或者隐式地) 调用 super 版本的 designated 初始化,所以无论如何走何种路径,被初始化的对象总是可以完成完整的初始化的。

class ClassA {
let numA: Int
init(num: Int) {
numA = num
}
}
class ClassB: ClassA {
let numB: Int
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}

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

class ClassA {
let numA: Int
init(num: Int) {
numA = num
}
convenience init(bigNum: Bool) {
self.init(num: bigNum ? 10000 : 1)
}
}
class ClassB: ClassA {
let numB: Int
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}

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

let anObj = ClassB(bigNum: true)
// anObj.numA = 10000, anObj.numB = 10001

单例模式

import UIKit
class DataCenter: NSObject {
let dataCenterObj:DataCenter = DataCenter()
func getDataCenter() ->DataCenter {
return dataCenterObj
}
}

运算符重载

类似C++的运算符重载

class Vector2D {
var x : Float = 0.0
var y : Float = 0.0
init (x : Float, y: Float) {
self.x = x
self.y = y
}
}
func +(left : Vector2D, right: Vector2D) -> Vector2D {
let result = Vector2D(x: left.x + right.x, y: left.y + right.y)
return result
}
let first = Vector2D(x: 2, y: 2)
let second = Vector2D(x: 4, y: 1)
let result = first + second

继承

要重写一个函数,要在子类中重新声明它,并添加 override 关键字

class Car: Vehicle {
// 继承类可以重写函数
override func description() -> String {
var description = super.description()
return description + ", which is a car"
} }

在一个被重写的函数中,可以通过 super 回调该函数在父类中的版本

override func description() -> String {
var description = super.description()
return description + ", which is a car"
}

协议

使用协议的好处是,可以利用 Swift 的类型体系来引用任何遵守某一给定协议的对象,个人现在理解为是Interface概念。

protocol Blinking{
var isBlinking:Bool{get}
var blinkSpeed: Double { get set }
func startBlinking(blinkSpeed: Double) -> Void
}
class Light:Blinking{
var isBlinking = false
var blinkSpeed = 1.2
func startBlinking(blinkSpeed: Double) {
print("now my speed is \(self.blinkSpeed)")
}
}

扩展

extension Int {
var doubled : Int {
return self * 2
}
func multiplyWith(anotherNumber: Int) -> Int {
return self * anotherNumber
} }
2.doubled // = 4
4.multiplyWith(32) // = 128

还可以利用扩展使一个类型遵守一个协议

extension Int : Blinking {
var isBlinking : Bool {
return false;
}
var blinkSpeed : Double {
get {
return 0.0; }
set {
// 不做任何事情
} }
func startBlinking(blinkSpeed : Double) {
print("I am the integer \(self). I do not blink.")
} }
2.isBlinking // = false
2.startBlinking(2.0) // 输出"I am the integer 2. I do not blink."

Struct(结构体)

Comparison

According to the very popular WWDC 2015 talk Protocol Oriented Programming in Swift (video,transcript), Swift provides a number of features that make structs better than classes in many circumstances.

Structs are preferable if they are relatively small and copiable because copying is way safer than having multiple reference to the same instance as happens with classes. This is especially important when passing around a variable to many classes and/or in a multithreaded environment. If you can always send a copy of your variable to other places, you never have to worry about that other place changing the value of your variable underneath you.

With Structs there is no need to worry about memory leaks or multiple threads racing to access/modify a single instance of a variable.

Classes can also become bloated because a class can only inherit from a single superclass. That encourages us to created huge superclasses that encompass many different abilities that are only loosely related. Using protocols, especially with protocol extensions where you can provide implementations to protocols, allows you to eliminate the need for classes to achieve this sort of behavior.

The talk lays out these scenarios where classes are preferred:

Copying or comparing instances doesn't make sense (e.g., Window)

Instance lifetime is tide to external effects (e.g., TemporaryFile)

Instances are just "sinks"--write-only conduits to external state (e.g.CGContext)

It implies that structs should be the default and classes should be a fallback.

On the other hand, The Swift Programming Language documentation is somewhat contradictory:

Structure instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks. As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure.

As a general guideline, consider creating a structure when one or more of these conditions apply:

  • The structure’s primary purpose is to encapsulate a few relatively simple data values.

  • It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.

  • Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.

  • The structure does not need to inherit properties or behavior from another existing type.

Examples of good candidates for structures include:

The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.

A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.

A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.

In all other cases, define a class, and create instances of that class to be managed and passed by reference. In practice, this means that most custom data constructs should be classes, not structures.

Here it is claiming that we should default to using classes and use structures only in specific circumstances. Ultimately, you need to understand the real world implication of value types v.s. reference types and then you can make an informed decision about when to use structs or classes. Also keep in mind that these concepts are always evolving and The Swift Programming Language documentation was written before the Protocol Oriented Programming talk was given.

My personal advice, is to always default to using a struct because they greatly reduce complexity and fallback to classes if the Struct becomes very large or requires some feature that structs and protocols cannot provide, most notably the ability to have multiple variables reference the same data。

Performance

One big advantage is performance. Since struct instances are allocated on stack, and class instances are allocated on heap, structs can be drastically faster.

Consider the following example, which demonstrates 2 strategies of wrapping Int data type (e.g. as part of a mathematics library)

class IntClass {
var value: Int
init(_ val: Int) { self.value = val }
}
struct IntStruct {
var value: Int
init(_ val: Int) { self.value = val }
}
func + (x: IntClass, y: IntClass) -> IntClass {
return IntClass(x.value + y.value)
}
func + (x: IntStruct, y: IntStruct) -> IntStruct {
return IntStruct(x.value + y.value)
}

and measure the performance using

// Test 1: IntClass
var x = IntClass(0)
for i in 1...10000000 {
x = x + IntClass(1)
}
// Test 2: IntStruct
var y = IntStruct(0)
for i in 1...10000000 {
y = y + IntStruct(1)
}

UPDATE (1 June 2014):

As of Swift 1.2, XCode 6.3.2, running Release build on iPhone 5S, iOS 8.3, averaged over 5 runs

  • The class version took 9.788332333s

  • The struct version took 0.010532942s

That's 900 times faster.

Differences

Inheritance

structures can't inherit in swift. If you want

class Vehicle{

}

class Car : Vehicle{

}

Go for an class.

Pass By

Swift structures pass by value and class instances pass by reference.

Contextual Differences

Struct constant and variables

Example (Used at WWDC 2014)

struct Point{
var x = 0.0;
var y = 0.0;
}

Defines a struct called Point.

var point = Point(x:0.0,y:2.0)

Now if I try to change the x. Its a valid expression.

point.x = 5

But if I defined a point as constant.

let point = Point(x:0.0,y:2.0)
point.x = 5 //This will give compile time error.

In this case entire point is immutable constant.

If I used a class Point instead this is a valid expression. Because in a class immutable constant is the reference to the class itself not its instance variables (Unless those variables defined as constants)

Struct Vs Inheritance

// #!Swift-1.1
import Foundation
// MARK: - (1) classes
// Solution 1:
// - Use classes instead of struct
// Issue: Violate the concept of moving model to the value layer
// http://realm.io/news/andy-matuschak-controlling-complexity/
typealias JSONDict = [NSObject:AnyObject]
class Vehicle1 {
let model: String
let color: String
init(jsonDict: JSONDict) {
model = jsonDict["model"] as String
color = jsonDict["color"] as String
}
}
class Car1 : Vehicle1 {
let horsepower: Double
let license_plate: String
override init(jsonDict: JSONDict) {
super.init(jsonDict: jsonDict)
horsepower = jsonDict["horsepower"] as Double
license_plate = jsonDict["license_plate"] as String
}
}
class Bicycle1 : Vehicle1 {
let chainrings: Int
let sprockets: Int
override init(jsonDict: JSONDict) {
super.init(jsonDict: jsonDict)
chainrings = jsonDict["chainrings"] as Int
sprockets = jsonDict["sprockets"] as Int
}
}
// MARK: - (2) struct + composition
// Solution 2:
// - keep value types
// - use composition.
// Issue: We violate the encapsulation principle, exposing the internal composition to the outside world
struct Vehicle2 {
let model: String
let color: String
init(jsonDict: JSONDict) {
model = jsonDict["model"] as String
color = jsonDict["color"] as String
}
}
struct Car2 {
let vehicle: Vehicle2
let horsepower: Double
let license_plate: String
init(jsonDict: JSONDict) {
vehicle = Vehicle2(jsonDict: jsonDict)
horsepower = jsonDict["horsepower"] as Double
license_plate = jsonDict["license_plate"] as String
}
}
struct Bicycle2 {
let vehicle: Vehicle2
let chainrings: Int
let sprockets: Int
init(jsonDict: JSONDict) {
vehicle = Vehicle2(jsonDict: jsonDict)
chainrings = jsonDict["chainrings"] as Int
sprockets = jsonDict["sprockets"] as Int
}
}
// MARK: - (3) struct, protocol + composition for parsing
// Solution 3:
// - keep value types, use a protocol
// - use intermediate struct only for parsing to keep encapsulation
// Issue: None… except code verbosity
protocol Vehicle3 {
var model: String { get }
var color: String { get }
}
private struct VehicleFields3 : Vehicle3 {
let model: String
let color: String
init(jsonDict: JSONDict) {
model = jsonDict["model"] as String
color = jsonDict["color"] as String
}
}
struct Car3 : Vehicle3 {
let model: String
let color: String
let horsepower: Double
let license_plate: String
init(jsonDict: JSONDict) {
let vehicle = VehicleFields3(jsonDict: jsonDict)
model = vehicle.model
color = vehicle.color
horsepower = jsonDict["horsepower"] as Double
license_plate = jsonDict["license_plate"] as String
}
}
struct Bicycle3 : Vehicle3 {
let model: String
let color: String
let chainrings: Int
let sprockets: Int
init(jsonDict: JSONDict) {
let vehicle = VehicleFields3(jsonDict: jsonDict)
model = vehicle.model
color = vehicle.color
chainrings = jsonDict["chainrings"] as Int
sprockets = jsonDict["sprockets"] as Int
}
}
// MARK: - (4) struct, protocols + global function for parsing
// Solution 4: [Does not compile]
// - keep value types, use a protocol
// - use a global function to fill the objects's fields conforming to the protocol
// Issue: does not work (it seems we can't pass 'self' as inout in the init() method)
// exposes the setter in the protocol and the structs anyway (so bad access protection)
protocol Vehicle4 {
var model: String { get set }
var color: String { get set }
}
private func parseVehicle4Fields(inout obj: Vehicle4, jsonDict: JSONDict) {
obj.model = jsonDict["model"] as String
obj.color = jsonDict["color"] as String
}
struct Car4 : Vehicle4 {
var model: String
var color: String
let horsepower: Double
let license_plate: String
init(jsonDict: JSONDict) {
parseVehicle4Fields(&self, jsonDict) // Error: Car4 is not identical to Vehicle4
horsepower = jsonDict["horsepower"] as Double
license_plate = jsonDict["license_plate"] as String
}
}
struct Bicycle4 : Vehicle4 {
var model: String
var color: String
let chainrings: Int
let sprockets: Int
init(jsonDict: JSONDict) {
parseVehicle4Fields(&self, jsonDict) // Error: Bicycle4 is not identical to Vehicle4
chainrings = jsonDict["chainrings"] as Int
sprockets = jsonDict["sprockets"] as Int
}
}