类与对象
类与对象
定义与实例化
类的基本的定义方式如下所示:
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: 延迟存储属性
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用
注意
必须将延迟存储属性声明成变量
( 使用var 关键字) ,因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
最简单的
//直接赋值变量
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
}
适合延迟加载的场景,一般而言有两种:
就如上面代码中提及的DataImporter
而言,完成初始化需要消耗不少时间:因为它的实例在初始化时可能要打开文件,还要读取文件内容到内存。由于使用了
print(manager.importer.fileName)
// DataImporter 实例的 importer 属性现在被创建了
// 输出 "data.txt”
举例来说,你有一个
class Person {
var name: String
@lazy var personalizedGreeting: String = {
[unowned self] in
return "Hello, \(self.name)!"
}()
init(name: String) {
self.name = name
}
}
注意,你必须使用
当你实例化一个
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( 属性监控)
利用属性监视器可以在属性设置之前
@IBInspectable var progress: Float = 0.75 {
didSet {
setNeedsDisplay()
}
}
方法(Methods)
访问控制
在将一个方法或属性声明为
// 可供所有人访问
public var publicProperty = 123
//如果将一个方法或属性声明为 private,那只能在声明它的源文件内部看到它:
// 只能在这个源文件中访问
private var privateProperty = 123
// 仅能供本模块访问
// 这里的'internal'是默认的,可以省略
internal var internalProperty = 123
静态属性/ 方法
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
,即在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"
}
}
对象
实例构造
在
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"
创建一个可以返回
convenience init? (value: Int) {
self.init()
if value > 5 {
// 不能初始化这个对象;返回nil,表示初始化失败 return nil
} }
在使用一个可以失败的初始化器时
let failableExample = InitAndDeinitExample(value: 6)
// = nil
required: 父类指定子类必须实现的初始化函数
如果子类需要添加异于父类的初始化方法时,必须先要实现父类中使用
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)
如果子类中不需要添加任何初始化方法,我们则可以忽略父类的
class MyClass {
var str:String
required init(str:String) {
self.str = str
}
}
class MySubClass:MyClass
{
}
MySubClass(str: "hello swift")
在这种情况下,编译器不会报错,因为如果子类没有任何初始化方法时,
You do not have to provide an explicit implementation of a required initializer if you can satisfy the requirement with an inherited initialiser.
required 修饰符的使用规则
convenience: 调用其他方法保证非Optional 对象初始化
在
所以
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)
}
}
与
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)
}
}
只要在子类中实现重写了父类
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
}
}
运算符重载
类似
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
继承
要重写一个函数
class Car: Vehicle {
// 继承类可以重写函数
override func description() -> String {
var description = super.description()
return description + ", which is a car"
} }
在一个被重写的函数中
override func description() -> String {
var description = super.description()
return description + ", which is a car"
}
协议
使用协议的好处是
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
}
}