此篇文章整理自我以前學習Swift時的一些練習代碼,其存在的意義多是可以通過看示例代碼更快地回憶Swift的主要語法。
如果你想系統學習Swift或者是Swift的初學者請繞路,感謝Github上The Swift Programming Language
開源翻譯的中文版,感謝極客學院wiki提供的PDF版本。
代碼和PDF版本上傳至Github,有興趣的可以下載下來試試。
SwiftEnum
Swift枚舉相比C語言更加靈活,除了成員值,還可以對成員賦原始值和相關值。Swift的枚舉采用了很多只被類支持的特征,如計算屬性,構造函數,可以在原始的實現基礎上擴展它們的功能,可依遵守協議來提供標準的功能。
import Foundation
// 使用enum關鍵詞來創建枚舉
enum CompassPoint {
case North
case South
case East
case West
}
// 多個成員值可以出現在同一行,用逗號隔開
enum Planet {
case Mercury, Venus, Earth, Jupiter, Saturn, Uranus, Neptune
}
// directionToHead的類型由右側類型推斷,一旦類型確定,賦值可以省略枚舉名字
var directionToHead = CompassPoint.West
directionToHead = .South
print(directionToHead)
// 使用switch語句匹配單個枚舉值
switch directionToHead {
case .North :
print("Lots of planets have a north")
case .South :
print("Watch out for penguins")
case .East :
print("Where the sun rise")
case .West :
print("Where the skies blue")
}
// Swift的枚舉允許對每個成員定義更多的信息
// 允許存儲人意類型的相關值,如果需要的話每個成員的相關值類型可以各不相同
// 相關值在枚舉定義時只聲明類型,具體值在使用時初始化
enum Barcode {
case UPCA(Int, Int, Int, Int)
case QRCode(String)
}
var productBarcode = Barcode.UPCA(8, 85909, 51226, 3)
productBarcode = .QRCode("AFFJFLJSFJKSLJFSF")
// 在swith的cese分句中提取相關值
// 如果所有相關值都被提取成let或var,可以只放置一個let或var
switch productBarcode {
case .UPCA(let numberSystem, let manufacture, let product, let check) :
print("UPC-A:\(numberSystem),\(manufacture),\(product),\(check)")
case let .QRCode(productCode):
print("QR code:\(productCode)")
}
// 另外一種增加信息的方式是原始值(RawValue)
// 原始值的類型和值都需要在定義時確定,不同case的原始值具有相同的類型
// 原始值可以是字符串,字符,或者任何整數型或浮點數值
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
// 使用整數作為原始值時,隱式賦值會依次遞增1,如果第一個值沒有被賦初值,將自動被賦值為0
// 使用字符串作為原始值時,隱式賦值為枚舉成員名稱
enum AnotherPlanet: Int {
case Mercury = 1, Venus, Earth, Jupiter, Saturn, Uranus, Neptune
}
enum AnotherCompassPoint: String {
case North, South, East, West
}
// 使用枚舉成員的rawValue屬性可以訪問成員的原始值
print(AnotherPlanet.Earth.rawValue)
// 使用原始值來初始化枚舉變量,因為有可能失敗,所以返回可選類型
let positionToFind = 9
if let possiblePlanet = AnotherPlanet(rawValue: positionToFind) {
switch possiblePlanet {
case .Earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
// 遞歸枚舉,一個或多個枚舉成員使用該枚舉類型的實例作為關聯值
// 在枚舉成員前加上indirect來表示該成員可遞歸
// 你也可以在枚舉類型開頭加上indirect關鍵字來表明它的所有成員都是可遞歸的
indirect enum ArithmeticExpression {
case Number(Int)
case Addition(ArithmeticExpression, ArithmeticExpression)
case Multiplication(ArithmeticExpression, ArithmeticExpression)
}
func evaluate(expression: ArithmeticExpression)-\>Int {
switch expression {
case.Number(let value) :
return value
case.Addition(let left, let right) :
return evaluate(left) + evaluate(right)
case.Multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
let five = ArithmeticExpression.Number(5)
let four = ArithmeticExpression.Number(4)
let sum = ArithmeticExpression.Addition(five, four)
let product = ArithmeticExpression.Multiplication(sum, ArithmeticExpression.Number(2))
print(evaluate(product))
SwiftClass
與其它編程語言所不同的是,Swift并不要求你為類和結構創建獨立的借口和實現文件,系統會自動生成面向其它代碼的外部接口。
類和結構體的共同點:
- 定義屬性用于存儲值
- 定義方法用于提供功能
- 定義附屬腳本用于訪問值
- 定義構造器用于初始化值
- 通過擴展以增加默認實現的功能
- 通過協議以提供某種標準功能
與結構體相比,類還有如下的附加功能:
- 繼承允許一個類繼承另一個類的特征
- 類型轉換允許在運行時檢查和解釋一個類實例的類型
- 解構器允許一個類實例釋放任何其所被分配的資源
- 引用計數允許對一個類的多次引用
import Foundation
// 類和結構體由著類似的定義方法,通過class和struct關鍵字來定義
// 每定義一個新類和結構體,實際上定義了一個新的Swift類型,因此請使用首字母大寫為類或結構體命名
struct Resolution {
var width = 0
var height = 0
}
// name屬性會自動被賦予一個默認值nil,意為“沒有name值”,因為它是一個可選類型
class VideoMode {
var resolution = Resolution()
var interlaced = false
var frameRate = 0.0
var name: String?
}
// 使用默認構造函數創建實例,其屬性值會被初始化為默認值
let someResolution = Resolution()
let someVideoMode = VideoMode()
// 訪問實例中的屬性,為屬性賦值
print("The width of someResolution is \(someResolution.width)")
someVideoMode.resolution.width = 1280
print("The width of someVideoMode is \(someVideoMode.resolution.width)")
// 所有結構體都會自動生成成員逐一構造器,類實例沒有默認的成員逐一構造器
let vga = Resolution(width: 640, height: 480)
// 結構體和枚舉是值類型,值類型被賦予一個常量、變量或者本身被傳遞給一個函數的時候,實際上的操作是拷貝
let hd = Resolution(width: 1920, height: 1080)
var cinema = hd
cinema.width = 2048
print(cinema, hd)
// 類是引用類型,在被賦予一個常量、變量或者本身被傳遞給一個函數的時候,實際上的操作是引用
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print(alsoTenEighty.frameRate, tenEighty.frameRate)
// 恒等運算符,判斷兩個常量或變臉是否引用同一個類實例
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the sanme instance")
}
// Swift中String Array Dictionary均以結構體的形式實現
SwiftProperties
Swift廣義的類包含Class、Struct、Enum。Swift的類內值分為存儲屬性和計算屬性兩大類,存儲屬性又分普通存儲屬性和延遲存儲屬性。又可以根據屬性屬于實例還是類分為類型屬性和實例屬性。除了延遲屬性,均可對屬性添加屬性觀察者。
import Foundation
// 屬性將值與特定的類、結構體和枚舉關聯。
// 存儲屬性存儲常量或變量作為實例的一部分,只能用于類和結構體
// 計算屬性計算一個值,計算屬性可以用于類、結構體和枚舉
// 存儲屬性和計算屬性通常與特定類型的實例關聯,但屬性也可以直接作用于類型本身,稱為類型屬性
// 還可以定義屬性觀察者來監控屬性值的變化,以觸發一個自定義的操作
// 存儲屬性
struct FixedLengthRange {
var firstVaule: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstVaule: 0, length: 3)
rangeOfThreeItems.firstVaule = 6
// 常量結構體的屬性無法修改,即使屬性是變量。
// 結構體是值類型,值類型的實例被聲明為常量時,它的所有屬性也成了常量
// 屬于引用的類則不一樣,把一個引用類型的實例賦給一個常量后,仍然可以修改實例的變量屬性
let rangeOfFourItems = FixedLengthRange(firstVaule: 0, length: 4)
// Error: rangeOfFourItems.firstVaule = 6
class SomeClass {
var firstVaule = 0
let secondValue = 0
}
let aSomeClass = SomeClass()
aSomeClass.firstVaule = 8
// 延遲存儲屬性,用關鍵字lazy聲明,必須將延遲存儲屬性聲明為變量
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")
// importer屬性還沒有被創建,直到第一次被訪問才被創建
print(manager.importer.fileName)
// 類、結構體和枚舉都可以定義計算屬性
// 計算屬性并不直接存儲值,而是提供一個getter和一個可選的setter方法來間接獲取和設置其它屬性的值
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
// 計算屬性應該定義成變量,因為它的值不固定,即便是只讀計算屬性
var center: Point {
get {
let centerX = origin.x + (size.width) / 2
let centerY = origin.y + (size.height) / 2
return Point(x: centerX, y: centerY)
}
// 如果計算屬性只有getter沒有setter,該計算屬性就是只讀計算屬性
// 只讀計算屬性在聲明中可以去掉get關鍵詞和花括號
set(newCenter) {
origin.x = newCenter.x - (size.width) / 2
origin.y = newCenter.y - (size.height) / 2
// 更便捷的寫法是,省略定義Setter參數,用默認的newValue代替
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("Square.origin is now at (\(square.origin.x),\(square.origin.y))")
// 屬性觀察者監控和響應屬性值的變化。每次屬性被設置的時候都會調用屬性觀察者,即便新值和舊值有時不同
// 除了延遲存儲屬性外的其它屬性均可設置屬性觀察者
// 可以為通過屬性重載方式繼承來的屬性添加屬性觀察者
// willSet觀察者會將新的屬性值以常量參數傳入,默認為newValue
// didSet會將舊的屬性值作為參數傳入,默認為oldValue
class StepCounter {
var totalSteps: Int = 0 {
willSet {
print("about to set totalSteps to \(newValue)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
stepCounter.totalSteps = 360
// 屬性觀察者的方法也可以用于全局變量或局部變量
// 全局的常量或變量都是延遲計算的,與延遲存儲屬性相似。局部常量或變量不會延遲計算
// 存儲型類型屬性必須指定默認值
struct AudioChannel {
static let thresholdLevel = 10
static var maxInputLevelForAllChannels = 0
var currentLevel: Int = 0 {
didSet {
if currentLevel > AudioChannel.thresholdLevel {
currentLevel = AudioChannel.thresholdLevel
}
if currentLevel > AudioChannel.maxInputLevelForAllChannels {
AudioChannel.maxInputLevelForAllChannels = currentLevel
}
}
}
}
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
print(AudioChannel.maxInputLevelForAllChannels)
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
print(AudioChannel.maxInputLevelForAllChannels)
SwiftMethods
Swift允許在類、結構體和枚舉上創建方法,方法分為實例方法和類型方法兩大類,這部分內容相對簡單,方法的語法與函數完全相同。
import Foundation
// 實例方法,語法與函數完全相同,能隱式地訪問它所屬類型的其它實例方法和屬性
class Counter1 {
var count = 0
func increment() {
++count
}
func incrementBy(amount:Int) {
count += amount
}
func reset() {
count = 0
}
}
let counter1 = Counter1()
counter1.increment()
counter1.incrementBy(5)
print(counter1.count)
counter1.reset()
print(counter1.count)
// 方法的局部參數名稱和外部參數名稱,語法類似函數
class Counter2 {
var count = 0
func incrementBy(amount: Int, numberOfTimes: Int) {
count += amount * numberOfTimes
}
}
let counter2 = Counter2()
// Swift只把amount作為局部名稱,但是把numberOfTimes即看作局部名稱又看作外部名稱
// 可以定義時修改外部參數名或者用\_隱藏外部參數名
counter2.incrementBy(5, numberOfTimes: 3)
// 類型的每一個實例都有一個隱含的屬性叫做self,你可以在一個實例方法中使用self屬性來引用當前實例
// 主要用于實例方法的某個參數名稱與實例的某個屬性名稱相同的情況。
struct Point1 {
var x = 0.0, y = 0.0
func isToTheRightOfX(x: Double)->Bool {
return self.x > x
}
}
let somePoint1 = Point1(x: 4.0, y: 5.0)
if somePoint1.isToTheRightOfX(1.0) {
print("The point is to the right of the line ")
}
// 一般情況下,結構體和枚舉的屬性不能在它的實例方法中被修改
// 使用關鍵字mutating聲明方法可改變屬性
// mutating方法甚至可以更改self屬性的值
// 結構體常量不能調用mutating方法
struct Point2 {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint2 = Point2(x: 1.0, y: 1.0)
somePoint2.moveByX(2.0, y: 3.0)
struct Point3 {
var x = 0.0, y = 0.0
mutating func moveByX(deltaX: Double, y deltaY: Double) {
self = Point3(x: x + deltaX, y: y + deltaY)
}
}
var somePoint3 = Point3(x: 1.0, y: 1.0)
somePoint3.moveByX(2.0, y: 3.0)
enum TriStateSwitch {
case Off, Low, High
mutating func next(){
switch self {
case .Off :
self = .Low
case .Low :
self = .High
default :
self = .Off
}
}
}
var ovenLight = TriStateSwitch.Low
ovenLight.next()
print(ovenLight)
// 在方法前加上static關鍵字聲明結構體和枚舉的類型方法
// 類可能會用關鍵字class來允許子類重寫父類的實現方法
struct LevelTracker {
static var highestUnlockedLevel = 1
static func unLockLevel(level: Int) {
if level > highestUnlockedLevel {
highestUnlockedLevel = level
}
}
static func levelIsUnlocked(level: Int) ->Bool {
return level <= highestUnlockedLevel
}
var currentLevel = 1
mutating func advanceToLevel(level: Int)->Bool {
if LevelTracker.levelIsUnlocked(level) {
currentLevel = level
return true
} else {
return false
}
}
}
SwiftSubscripts
Swift下標腳本相當于其它語言中的[]運算符重載,語法類似于計算屬性,是一種對訪問集合列表和序列的一種常見的快捷操作,注意不要忘了索引值合理性的判定。
import Foundation
// 下標腳本(subscript)可以定義在類、結構體和枚舉中,作為訪問集合、列表或序列的快捷方式
// 允許通過在實例后面的方括號傳入一個或者多個索引值來對實例進行訪問和賦值
struct TimesTable {
let multiplier: Int
subscript(index: Int) ->Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print(threeTimesTable[6])
// 下標腳本的參數個數沒有限制,可以使用可變參數,但參數默認值和in-out參數不被允許
// 可以定義重載的下標腳本
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(count: rows * columns, repeatedValue: 0.0)
}
func indexIsValidForRow(row: Int, column: Int)->Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
subscript(row: Int, column: Int)->Double {
get {
assert(indexIsValidForRow(row, column: column), "Index out of range")
return grid[row * columns + column]
}
set {
assert(indexIsValidForRow(row, column: column), "Index out of range")
grid[row * columns + column] = newValue
}
}
}
var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
print(matrix.grid)
// matrix[0, 3] = 1.5
SwiftInheritance
一個類可以繼承(inherit)另一個類的方法(methods),屬性(properties)和其它特性。在 Swift 中,繼承是區分「類」與其它類型的一個基本特征。
在 Swift 中,類可以調用和訪問超類的方法,屬性和下標腳本(subscripts),并且可以重寫(override)這些方法,屬性和下標腳本來優化或修改它們的行為。Swift 會檢查你的重寫定義在超類中是否有匹配的定義,以此確保你的重寫行為是正確的。
可以為類中繼承來的屬性添加屬性觀察器(property observers),這樣一來,當屬性值改變時,類就會被通知到。可以為任何屬性添加屬性觀察器,無論它原本被定義為存儲型屬性(stored property)還是計算型屬性(computed property)。
import Foundation
// 不集成自其它的類,我們稱之為基類
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
print("Don't Know")
}
}
let someVehicle = Vehicle()
print(someVehicle.description)
// 定義一個子類,冒號:后面表示繼承的類(父類)
class Bicycle: Vehicle {
var hasBasket = false // 增加一個屬性
}
let bicycle = Bicycle()
bicycle.hasBasket = true
// 可以訪問繼承自父類的屬性
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// 重寫方法
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
let train = Train()
train.makeNoise()
// 重寫屬性, 采用super.訪問父類方法或屬性
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
let car = Car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// 重寫屬性觀察者
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
automatic.makeNoise()
// 防止重寫
// 你可以通過把方法,屬性或下標腳本標記為final來防止它們被重寫
// 你可以通過在關鍵字class前添加final修飾符來將整個類聲明為final,不允許繼承
SwiftInitialization
SwiftInitialization是對Swift構造函數的介紹,Swift的構造函數大部分語法特性與其它語言類似,需要注意的是可失敗構造器、便利構造器和必要構造器這些特殊的地方,內容相對較多,但都容易理解。
import Foundation
// 類和結構體在創建實例時,必須為所有存儲型屬性設置合適的初始值。存儲型屬性的值不能處于一個未知的狀態。
// 使用關鍵詞init創建一個構造器
struct Fahrenheit {
var temperature: Double // 也可以為存儲屬性設置類內默認值
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature) Fahrenheit")
// 構造參數
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromkelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}
let boilingpointOfWater = Celsius(fromFahrenheit: 212.0)
let freezingPointOfWater = Celsius(fromkelvin: 273.15)
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) { // 可用_隱藏外部參數名
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
// 類型包含一個邏輯上允許取值為空的存儲類型,需要定義為可選類型。
// 可選類型自動初始化為nil
// 構造過程中可以改變常量屬性的值,一旦常量屬性被賦值,它將永遠不可更改
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese")
cheeseQuestion.ask()
cheeseQuestion.response = "Yes, I do like cheese"
// 默認構造器
// 結構體或類的所有屬性值都有默認值且沒有自定義構造器,則Swift會提供一個默認構造器為所有的屬性值設置為默認值
// 如果你自定義了構造器,默認構造器將會失效。可以通過擴展寫自定義構造器來防止默認構造器失效。
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
// 結構體提供特殊的逐一成員構造器
struct Size {
var width = 0.0
var height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
// 構造器可以通過調用其它構造器來完成實例的部分構造過程。這一過程稱為構造器代理
// 構造器代理對于值類型和類類型有區別,值類型只能代理自身別的構造器
// 使用self.init在自定義的構造器中引用類型中的其它構造器
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
let basicRect = Rect()
let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
// Swift 提供了兩種類型的類構造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是指定構造器和便 利構造器。
// 一個指定構造器將初始化類中提供的所有屬性,并根據父類鏈往上調用父類的構造器來實現父類的初始化
// 每一個類都必須擁有至少一個指定構造器.你可以定義便利構造器來調用同一個類中的指定構造器,并為其參數提供默認值
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
} }
let namedMeat = Food(name: "Bacon")
let mysteryMeat = Food()
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
} }
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
class ShoppingListItems: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ?" : " ?"
return output
} }
// 由于它為自己引入的所有屬性都提供了默認值,并且自己沒有定義任何構造器, ShoppingListItem 將自動繼承所 有父類中的指定構造器和便利構造器。
var breakfastList = [
ShoppingListItems(),
ShoppingListItems(name: "Bacon"),
ShoppingListItems(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 可失敗構造器。其語法為在 init 關鍵字后面加添問號 (init?)
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
let someCreature = Animal(species: "Giraffe")
print(someCreature)
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
let anonymousCreature = Animal(species: "")
if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}
// 枚舉類型的可失敗構造器
enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil }
} }
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
// 帶原始值的枚舉類型會自帶一個可失敗構造器 init?(rawValue:)
enum TemperatureUnit1: Character {
case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}
let fahrenheitUnit1 = TemperatureUnit1(rawValue: "F")
if fahrenheitUnit1 != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
let unknownUnit1 = TemperatureUnit1(rawValue: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
// 類的可失敗構造器只能在所有的類屬性被初始化后和所有類之間的構造器之間的代理調用發生完后觸發失敗行為。
// Product 類的所有可失敗構造器必須在自己 失敗前給 name 屬性一個初始值
class Product {
let name: String!
init?(name: String) {
self.name = name
if name.isEmpty { return nil }
}
}
class CartItem: Product {
let quantity: Int!
init?(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
if quantity < 1 { return nil }
}
}
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// 重寫一個可失敗構造器
class Document {
var name: String!
// 該構造器構建了一個name屬性值為nil的document對象
init() {}
// 該構造器構建了一個name屬性值為非空字符串的document對象
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}
class UntitledDocument: Document {
override init() {
// 你可以在構造器中調用父類的可失敗構造器強制解包,以實現子類的非可失敗構造器
super.init(name: "[Untitled]")!
}
}
struct Checkerboard {
let boardColors: [Bool] = {
var temporaryBoard = [Bool]()
var isBlack = false
for i in 1...10 {
for j in 1...10 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squareIsBlackAtRow(row: Int, column: Int) -> Bool {
return boardColors[(row * 10) + column]
}
}
let board = Checkerboard()
print(board.squareIsBlackAtRow(0, column: 1))
print(board.squareIsBlackAtRow(9, column: 9))
SwiftDeinitialization
SwiftDeinitialization是對Swift析構函數的介紹,Swift析構器只適用于類類型,通常當你的實例被釋放時不需要手動地去清理,但是,當使用自己的資源時,你可能 需要進行一些額外的清理。析構器會自動調用父類析構器。
import Foundation
// 簡單的析構器例子。
struct Bank {
static var coinsInBank = 10_000
static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receiveCoins(coins: Int) {
coinsInBank += coins
}
}
class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.vendCoins(coins)
}
func winCoins(coins: Int) {
coinsInPurse += Bank.vendCoins(coins)
}
deinit {
Bank.receiveCoins(coinsInPurse)
}
}
var playerOne: Player? = Player(coins: 100)
print("A new player has joined the game with \(playerOne!.coinsInPurse) coins")
print("There are now \(Bank.coinsInBank) coins left in the bank")
playerOne!.winCoins(2\_000)
print("PlayerOne won 2000 coins & now has \(playerOne!.coinsInPurse) coins")
print("The bank now only has \(Bank.coinsInBank) coins left")
SwiftARC
Swift使用自動引用計數(ARC)機制來跟蹤和管理你的應用程序的內存。通常情況下,Swift 內存管理機制會一
直起作用,你無須自己來考慮內存的管理。ARC 會在類的實例不再被使用時,自動釋放其占用的內存。然而,在少數情況下,ARC 為了能幫助你管理內存,需要更多的關于你的代碼之間關系的信息。
import Foundation
// Swift使用自動引用計數(ARC)機制來跟蹤和管理你的應用程序的內存。
// 引用計數僅僅應用于類的實例。結構體和枚舉類型是值類型,不是引用類型,也不是通過引用的方式存儲和傳遞。
// ----------------- Part 1 -------------------------
// 下面的例子展示了自動引用計數的工作機制。
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) is being initialized")
}
deinit {
print("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
reference1 = Person(name: "John Appleseed")
// reference1到Person類的新實例之間建立了一個強引用
// 正是因為這一個強引用,ARC 會保證 Person 實例被保持在內存中不被銷毀
reference2 = reference1
reference3 = reference2
// 現在這一個Person實例已經有三個強引用了
reference1 = nil
reference3 = nil
// 給其中兩個變量賦值 nil 的方式斷開兩個強引用(包括最先的那個強引用),只留下一個強引用, Person實例不會被銷毀
reference2 = nil
// 最后一個強引用被斷開,Person實例被銷毀
// ----------------- Part 2 -----------------
// 循環強引用問題,一個類實例的強引用數永遠不能變成0
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
// 變量john現在有一個指向Person實例的強引用,而變量unit4A有一個指向Apartment實例的強引用
john!.apartment = unit4A
unit4A!.tenant = john
// 這兩個實例關聯后會產生一個循環強引用
john = nil
unit4A = nil
// 當你斷開john和unit4A引用時,引用計數并不會減為0,實例也不會被ARC銷毀
// ----------------- Part 3 -----------------
// Swift提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環強引用問題:弱引用(weak reference)和無主引用(unowned reference)。
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person?
deinit { print("Apartment \(unit) is being deinitialized") }
}
// 跟之前一樣,建立兩個變量( john 和 unit4A )之間的強引用,并關聯兩個實例
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
// Person實例依然保持對Apartment實例的強引用,但是Apartment實例只是對Person實例的弱引用。
// 這意味著當你斷開john變量所保持的強引用時,再也沒有指向Person實例的強引用了
john = nil
// 唯一剩下的指向Apartment實例的強引用來自于變量unit4A。如果你斷開這個強引用,再也沒有指向Apartment實例的強引用了
unit4A = nil
// 首先斷開unit4A的對Apartment實例的強引用,并不會使得Apartment實例銷毀,因為此時Person實例依舊有對Apartment實例的強引用
// ----------------- Part 4 -----------------
// 下面的例子定義了兩個類, Customer 和 CreditCard ,模擬了銀行客戶和客戶的信用卡。
// 這兩個類中,每一個都將另外一個類的實例作為自身的屬性。這種關系可能會造成循環強引用。
class Customer {
let name: String
var card: CreditCard?
init(name: String) {
self.name = name
}
deinit { print("\(name) is being deinitialized") }
}
class CreditCard {
let number: UInt64
unowned let customer: Customer
init(number: UInt64, customer: Customer) {
self.number = number
self.customer = customer
}
deinit { print("Card #\(number) is being deinitialized") }
}
var john: Customer?
john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234\_5678\_9012\_3456, customer: john!)
// Customer 實例持有對 CreditCard 實例的強引用,而 CreditCard 實例持有對 Customer 實例的無主引用。
john = nil
// ----------------- Part 4 -----------------
//Person和Apartment的例子展示了兩個屬性的值都允許為nil,并會潛在的產生循環強引用。這種場景最適合用弱引用來解決。
//Customer和CreditCard的例子展示了一個屬性的值允許為nil,而另一個屬性的值不允許為nil,這也可能會產生循環強引用。這種場景最適合通過無主引用來解決。
//存在著第三種場景,在這種場景中,兩個屬性都必須有值,并且初始化完成后永遠不會為nil。在這種場景中,需要一個類使用無主屬性,而另外一個類使用隱式解析可選屬性。
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
deinit { print("Country \(name) is being deinitialized") }
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
deinit { print("City \(name) is being deinitialized") }
}
var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// 以上的意義在于你可以通過一條語句同時創建Country和City 的實例,而不產生循環強引用,并且capitalCity的屬性能被直接訪問,而不需要通過感嘆號來展開它的可選值
country = Country(name: "China", capitalName: "Beijing")
// ----------------- Part 5 -----------------
// 循環強引用還會發生在當你將一個閉包賦值給類實例的某個屬性,并且這個閉包體中又使用了這個類實例。
// 這個閉包體中可能訪問了實例的某個屬性,例如self.someProperty,或者閉包中調用了實例的某個方法,例如 self.someMethod 。
// 這兩種情況都導致了閉包 “捕獲" self ,從而產生了循環強引用。
// 循環強引用的產生,是因為閉包和類相似,都是引用類型
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
heading = HTMLElement(name: "head")
// HTMLElement類產生了類實例和asHTML默認值的閉包之間的循環強引用。
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil
// ----------------- Part 6 -----------------
// Swift提供了一種優雅的方法來解決這個問題,稱之為閉包捕獲列表(closuer capture list)
// 在定義閉包時同時定義捕獲列表作為閉包的一部分,捕獲列表定義了閉包體內捕獲一個或者多個引用類型的規則
// Swift有如下要求:只要在閉包內使用self的成員,就要用self.someProperty(而非someProperty)。這提醒你可能會一不小心就捕獲了self。
// 捕獲列表中的每一項都由一對元素組成,一個元素是unowned或weak關鍵字。
// 另一個元素是類實例的引用(如self)或初始化過的變量(如self.someProperty)
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: Void -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)</\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
paragraph = nil