目錄
繼承 構造過程 析構過程 可選鏈 類型轉換 協議 泛型 訪問控制 相等與相同 內存管理
繼承
繼承我們可以理解為一個類獲取了另外一個類的方法和屬性。
當一個類繼承其它類時,繼承類叫子類,被繼承類叫超類(或父類)
在 Swift 中,類可以調用和訪問超類的方法,屬性和下標腳本,并且可以重寫它們。
也可以為類中繼承來的屬性添加屬性觀察器。
1.基類
沒有繼承其它類的類,稱之為基類(Base Class)。
以下實例中我們定義了基類 StudDetails ,描述了學生(stname)及其各科成績的分數(mark1、mark2、mark3):
class StudDetails {
var stname: String!
var mark1: Int!
var mark2: Int!
var mark3: Int!
init(stname: String, mark1: Int, mark2: Int, mark3: Int) {
self.stname = stname
self.mark1 = mark1
self.mark2 = mark2
self.mark3 = mark3
}
}
let stname = "swift"
let mark1 = 98
let mark2 = 89
let mark3 = 76
let sds = StudDetails(stname:stname, mark1:mark1, mark2:mark2, mark3:mark3);
print(sds.stname);print(sds.mark1);print(sds.mark2);print(sds.mark3)
2.子類
子類指的是在一個已有類的基礎上創建一個新的類。
為了指明某個類的超類,將超類名寫在子類名的后面,用冒號(:)分隔,語法格式如下
class SomeClass: SomeSuperclass {
// 類的定義
}
實例
以下實例中我們定義了超類 StudDetails,然后使用子類 Tom 繼承它:
class StudDetails
{
var mark1: Int;
var mark2: Int;
init(stm1:Int, results stm2:Int)
{
mark1 = stm1;
mark2 = stm2;
}
func show()
{
print("Mark1:\(self.mark1), Mark2:\(self.mark2)")
}
}
class Tom : StudDetails
{
init()
{
super.init(stm1: 93, results: 89)
}
}
let tom = Tom()
tom.show()
以上程序執行輸出結果為:
Mark1:93, Mark2:89
3.重寫(Overriding)
子類可以通過繼承來的實例方法,類方法,實例屬性,或下標腳本來實現自己的定制功能,我們把這種行為叫重寫(overriding)。
我們可以使用 override 關鍵字來實現重寫。
重寫 訪問方法,屬性,下標腳本
方法 super.somemethod()
屬性 super.someProperty()
下標腳本 super[someIndex]
- 重寫方法
以下實例中我們重寫了 show() 方法:
class SuperClass {
func show() {
print("這是超類 SuperClass")
}
}
class SubClass: SuperClass {
override func show() {
print("這是子類 SubClass")
}
}
let superClass = SuperClass()
superClass.show()
let subClass = SubClass()
subClass.show()
以上程序執行輸出結果為:
這是超類 SuperClass
這是子類 SubClass
- 重寫屬性
你可以提供定制的 getter(或 setter)來重寫任意繼承來的屬性,無論繼承來的屬性是存儲型的還是計算型的屬性。
子類并不知道繼承來的屬性是存儲型的還是計算型的,它只知道繼承來的屬性會有一個名字和類型。所以你在重寫一個屬性時,必需將它的名字和類型都寫出來。
注意點:
如果你在重寫屬性中提供了 setter,那么你也一定要提供 getter。
如果你不想在重寫版本中的 getter 里修改繼承來的屬性值,你可以直接通過super.someProperty來返回繼承來的值,其中someProperty是你要重寫的屬性的名字。
以下實例我們定義了超類 Circle 及子類 Rectangle, 在 Rectangle 類中我們重寫屬性 area:
class Circle {
var radius = 12.5
var area: String {
return "矩形半徑 \(radius) "
}
}
// 繼承超類 Circle
class Rectangle: Circle {
var print = 7
override var area: String {
return super.area + " ,但現在被重寫為 \(print)"
}
}
let rect = Rectangle()
rect.radius = 25.0
rect.print = 3
print("Radius \(rect.area)")
// 重寫屬性觀察器
class Square: Rectangle {
override var radius: Double {
didSet {
print = Int(radius/5.0)+1
}
}
}
let sq = Square()
sq.radius = 100.0
print("半徑: \(sq.area)")
以上程序執行輸出結果為:
Radius 矩形半徑 25.0 ,但現在被重寫為 3
半徑: 矩形半徑為 100.0 ,但現在被重寫為 21
- 重寫屬性觀察器
你可以在屬性重寫中為一個繼承來的屬性添加屬性觀察器。這樣一來,當繼承來的屬性值發生改變時,你就會監測到。
注意:你不可以為繼承來的常量存儲型屬性或繼承來的只讀計算型屬性添加屬性觀察器。
4.防止重寫
我們可以使用 final 關鍵字防止它們被重寫。
如果你重寫了final方法,屬性或下標腳本,在編譯時會報錯。
你可以通過在關鍵字class前添加final特性(final class)來將整個類標記為 final 的,這樣的類是不可被繼承的,否則會報編譯錯誤。
final class Circle {
final var radius = 12.5
var area: String {
return "矩形半徑為 \(radius) "
}
}
構造過程
構造過程是為了使用某個類、結構體或枚舉類型的實例而進行的準備過程。這個過程包含了為實例中的每個屬性設置初始值和為其執行必要的準備和初始化任務。
Swift 構造函數使用 init() 方法。
與 Objective-C 中的構造器不同,Swift 的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化。
類實例也可以通過定義析構器(deinitializer)在類實例釋放之前執行清理內存的工作。
1.存儲型屬性的初始賦值
類和結構體在實例創建時,必須為所有存儲型屬性設置合適的初始值。
存儲屬性在構造器中賦值時,它們的值是被直接設置的,不會觸發任何屬性觀測器。
存儲屬性在構造器中賦值流程:
- 創建初始值。
- 在屬性定義中指定默認屬性值。
- 初始化實例,并調用 init() 方法。
2.構造器
構造器在創建某特定類型的新實例時調用。它的最簡形式類似于一個不帶任何參數的實例方法,以關鍵字init命名。
- 語法
init()
{
// 實例化后執行的代碼
}
實例
以下結構體定義了一個不帶參數的構造器 init,并在里面將存儲型屬性 length 和 breadth 的值初始化為 6 和 12:
struct rectangle {
var length: Double
var breadth: Double
init() {
length = 6
breadth = 12
}
}
var area = rectangle()
print("矩形面積為 \(area.length*area.breadth)")
以上程序執行輸出結果為:
矩形面積為 72.0
- 默認屬性值
我們可以在構造器中為存儲型屬性設置初始值;同樣,也可以在屬性聲明時為其設置默認值。
使用默認值能讓你的構造器更簡潔、更清晰,且能通過默認值自動推導出屬性的類型。
以下實例我們在屬性聲明時為其設置默認值:
struct rectangle {
// 設置默認值
var length = 6
var breadth = 12
}
var area = rectangle()
print("矩形的面積為 \(area.length*area.breadth)")
- 構造參數
你可以在定義構造器 init() 時提供構造參數,如下所示:
struct Rectangle {
var length: Double
var breadth: Double
var area: Double
init(fromLength length: Double, fromBreadth breadth: Double) {
self.length = length
self.breadth = breadth
area = length * breadth
}
init(fromLeng leng: Double, fromBread bread: Double) {
self.length = leng
self.breadth = bread
area = leng * bread
}
}
let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("面積為: \(ar.area)")
let are = Rectangle(fromLeng: 36, fromBread: 12)
print("面積為: \(are.area)")
以上程序執行輸出結果為:
面積為: 72.0
面積為: 432.0
內部和外部參數名
跟函數和方法參數相同,構造參數也存在一個在構造器內部使用的參數名字和一個在調用構造器時使用的外部參數名字。
然而,構造器并不像函數和方法那樣在括號前有一個可辨別的名字。所以在調用構造器時,主要通過構造器中的參數名和類型來確定需要調用的構造器。
如果你在定義構造器時沒有提供參數的外部名字,Swift 會為每個構造器的參數自動生成一個跟內部名字相同的外部名。沒有外部名稱參數
如果你不希望為構造器的某個參數提供外部名字,你可以使用下劃線_來顯示描述它的外部名。
struct Rectangle {
var length: Double
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
//不提供外部名字
init(_ area: Double) {
length = area
}
}
// 調用不提供外部名字
let rectarea = Rectangle(180.0)
print("面積為: \(rectarea.length)")
以上程序執行輸出結果為:
面積為: 180.0
- 可選屬性類型
如果你定制的類型包含一個邏輯上允許取值為空的存儲型屬性,你都需要將它定義為可選類型optional type(可選屬性類型)。
當存儲屬性聲明為可選時,將自動初始化為空 nil。
struct Rectangle {
var length: Double?
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
init(_ area: Double) {
length = area
}
}
let rectarea = Rectangle(180.0)
print("面積為:\(rectarea.length)")
以上程序執行輸出結果為:
面積為:Optional(180.0)
- 構造過程中修改常量屬性
只要在構造過程結束前常量的值能確定,你可以在構造過程中的任意時間點修改常量屬性的值。
對某個類實例來說,它的常量屬性只能在定義它的類的構造過程中修改;不能在子類中修改。
盡管 length 屬性現在是常量,我們仍然可以在其類的構造器中設置它的值:
struct Rectangle {
let length: Double?
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
init(_ area: Double) {
length = area
}
}
let rectarea = Rectangle(180.0)
print("面積為:\(rectarea.length)")
以上程序執行輸出結果為:
面積為:Optional(180.0)
默認構造器
默認構造器將簡單的創建一個所有屬性值都設置為默認值的實例;
類中的所有屬性都有默認值,且它是沒有父類的基類,它將自動獲得一個可以為所有屬性設置默認值的默認構造器值類型的構造器代理
構造器可以通過調用其它構造器來完成實例的部分構造過程。這一過程稱為構造器代理,它能減少多個構造器間的代碼重復。
以下實例中,Rect 結構體調用了 Size 和 Point 的構造過程:
struct Size {
var width = 0.0, height = 0.0
}
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)
}
}
// origin和size屬性都使用定義時的默認值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):
let basicRect = Rect()
print("Size 結構體初始值: \(basicRect.size.width, basicRect.size.height) ")
print("Rect 結構體初始值: \(basicRect.origin.x, basicRect.origin.y) ")
// 將origin和size的參數值賦給對應的存儲型屬性
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
print("Size 結構體初始值: \(originRect.size.width, originRect.size.height) ")
print("Rect 結構體初始值: \(originRect.origin.x, originRect.origin.y) ")
//先通過center和size的值計算出origin的坐標。
//然后再調用(或代理給)init(origin:size:)構造器來將新的origin和size值賦值到對應的屬性中
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
print("Size 結構體初始值: \(centerRect.size.width, centerRect.size.height) ")
print("Rect 結構體初始值: \(centerRect.origin.x, centerRect.origin.y) ")
以上程序執行輸出結果為:
Size 結構體初始值: (0.0, 0.0)
Rect 結構體初始值: (0.0, 0.0)
Size 結構體初始值: (5.0, 5.0)
Rect 結構體初始值: (2.0, 2.0)
Size 結構體初始值: (3.0, 3.0)
Rect 結構體初始值: (2.5, 2.5)
-
構造器代理規則
- 值類型
不支持繼承,所以構造器代理的過程相對簡單,因為它們只能代理給本身提供的其它構造器。 你可以使用self.init在自定義的構造器中引用其它的屬于相同值類型的構造器。 - 類類型
它可以繼承自其它類,這意味著類有責任保證其所有繼承的存儲型屬性在構造時也能正確的初始化。
- 值類型
-
類的繼承和構造過程
Swift 提供了兩種類型的類構造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是指定構造器和便利構造器。- 指定構造器
類中最主要的構造器
初始化類中提供的所有屬性,并根據父類鏈往上調用父類的構造器來實現父類的初始化。
每一個類都必須擁有至少一個指定構造器
Init(parameters) {
statements
} - 便利構造器
類中比較次要的、輔助型的構造器
可以定義便利構造器來調用同一個類中的指定構造器,并為其參數提供默認值。你也可以定義便利構造器來創建一個特殊用途或特定輸入的實例。
只在必要的時候為類提供便利構造器
convenience init(parameters) {
statements
}
- 指定構造器
構造器的繼承和重載
Swift 中的子類不會默認繼承父類的構造器。
父類的構造器僅在確定和安全的情況下被繼承。
當你重寫一個父類指定構造器時,你需要寫override修飾符。
class SuperClass {
var corners = 4
var description: String {
return "\(corners) 邊"
}
}
let rectangle = SuperClass()
print("矩形: \(rectangle.description)")
class SubClass: SuperClass {
override init() { //重載構造器
super.init()
corners = 5
}
}
let subClass = SubClass()
print("五角型: \(subClass.description)")
以上程序執行輸出結果為:
矩形: 4 邊
五角型: 5 邊
- 類的可失敗構造器
如果一個類,結構體或枚舉類型的對象,在構造自身的過程中有可能失敗,則為其定義一個可失敗構造器。
變量初始化失敗可能的原因有:
* 傳入無效的參數值。
* 缺少某種所需的外部資源。
* 沒有滿足特定條件。
為了妥善處理這種構造過程中可能會失敗的情況。
你可以在一個類,結構體或是枚舉類型的定義中,添加一個或多個可失敗構造器。其語法為在init關鍵字后面加添問號(init?)。
下例中,定義了一個名為Animal的結構體,其中有一個名為species的,String類型的常量屬性。
同時該結構體還定義了一個,帶一個String類型參數species的,可失敗構造器。這個可失敗構造器,被用來檢查傳入的參數是否為一個空字符串,如果為空字符串,則該可失敗構造器,構建對象失敗,否則成功。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
//通過該可失敗構造器來構建一個Animal的對象,并檢查其構建過程是否成功
// someCreature 的類型是 Animal? 而不是 Animal
let someCreature = Animal(species: "長頸鹿")
// 打印 "動物初始化為長頸鹿"
if let giraffe = someCreature {
print("動物初始化為\(giraffe.species)")
}
以上程序執行輸出結果為:
動物初始化為長頸鹿
類的可失敗構造器
值類型(如結構體或枚舉類型)的可失敗構造器,對何時何地觸發構造失敗這個行為沒有任何的限制。
但是,類的可失敗構造器只能在所有的類屬性被初始化后和所有類之間的構造器之間的代理調用發生完后觸發失敗行為。
覆蓋一個可失敗構造器
就如同其它構造器一樣,你也可以用子類的可失敗構造器覆蓋基類的可失敗構造器。
者你也可以用子類的非可失敗構造器覆蓋一個基類的可失敗構造器。
你可以用一個非可失敗構造器覆蓋一個可失敗構造器,但反過來卻行不通。
一個非可失敗的構造器永遠也不能代理調用一個可失敗構造器。
可失敗構造器 init!
通常來說我們通過在init關鍵字后添加問號的方式(init?)來定義一個可失敗構造器,但你也可以使用通過在init后面添加驚嘆號的方式來定義一個可失敗構造器(init!)。
析構過程
在一個類的實例被釋放之前,析構函數被立即調用。用關鍵字deinit來標示析構函數,類似于初始化函數用init來標示。析構函數只適用于類類型。
- 析構過程原理
Swift 會自動釋放不再需要的實例以釋放資源。
Swift 通過自動引用計數(ARC)處理實例的內存管理。
通常當你的實例被釋放時不需要手動地去清理。但是,當使用自己的資源時,你可能需要進行一些額外的清理。
例如,如果創建了一個自定義的類來打開一個文件,并寫入一些數據,你可能需要在類實例被釋放之前關閉該文件。 - 語法
在類的定義中,每個類最多只能有一個析構函數。析構函數不帶任何參數,在寫法上不帶括號:
deinit {
// 執行析構過程
}
實例
var counter = 0; // 引用計數器
class BaseClass {
init() {
counter += 1;
}
deinit {
counter -= 1;
}
}
var show: BaseClass? = BaseClass()
print(counter)
show = nil
print(counter)
以上程序執行輸出結果為:1 0
可選鏈
可選鏈(Optional Chaining)是一種可以請求和調用屬性、方法和子腳本的過程,用于請求或調用的目標可能為nil。
可選鏈返回兩個值:
如果目標有值,調用就會成功,返回該值
如果目標為nil,調用將返回nil
多次請求或調用可以被鏈接成一個鏈,如果任意一個節點為nil將導致整條鏈失效。
- 可選鏈可替代強制解析
通過在屬性、方法、或下標腳本的可選值后面放一個問號(?),即可定義一個可選鏈。- 可選鏈 '?'
? 放置于可選值后來調用方法,屬性,下標腳本
當可選為 nil 輸出比較友好的錯誤信息 當 - 感嘆號(!)強制展開方法,屬性,下標腳本可選鏈
! 放置于可選值后來調用方法,屬性,下標腳本來強制展開值
可選為 nil 時強制展開執行錯誤
- 可選鏈 '?'
使用感嘆號(!)可選鏈實例
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
//將導致運行時錯誤
let roomCount = john.residence!.numberOfRooms
以上程序執行輸出結果為:
fatal error: unexpectedly found nil while unwrapping an Optional value
想使用感嘆號(!)強制解析獲得這個人residence屬性numberOfRooms屬性值,將會引發運行時錯誤,因為這時沒有可以供解析的residence值。
使用問號(?)可選鏈實例
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
// 鏈接可選residence?屬性,如果residence存在則取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
print("John 的房間號為 \(roomCount)。")
} else {
print("不能查看房間號")
}
以上程序執行輸出結果為:
不能查看房間號
因為這種嘗試獲得numberOfRooms的操作有可能失敗,可選鏈會返回Int?類型值,或者稱作"可選Int"。當residence是空的時候(上例),選擇Int將會為空,因此會出現無法訪問numberOfRooms的情況。
要注意的是,即使numberOfRooms是非可選Int(Int?)時這一點也成立。只要是通過可選鏈的請求就意味著最后numberOfRooms總是返回一個Int?而不是Int。
為可選鏈定義模型類
你可以使用可選鏈來多層調用屬性,方法,和下標腳本。這讓你可以利用它們之間的復雜模型來獲取更底層的屬性,并檢查是否可以成功獲取此類底層屬性。通過可選鏈調用方法
你可以使用可選鏈的來調用可選值的方法并檢查方法調用是否成功。即使這個方法沒有返回值,你依然可以使用可選鏈來達成這一目的。
if ((john.residence?.printNumberOfRooms()) != nil)
- 使用可選鏈調用下標腳本
你可以使用可選鏈來嘗試從下標腳本獲取值并檢查下標腳本的調用是否成功,然而,你不能通過可選鏈來設置下標腳本。
if let firstRoomName = john.residence?[0].name
- 通過可選鏈接調用來訪問下標
通過可選鏈接調用,我們可以用下標來對可選值進行讀取或寫入,并且判斷下標調用是否成功。
if let firstRoomName = john.residence?[0].name
- 連接多層鏈接
你可以將多層可選鏈連接在一起,可以掘取模型內更下層的屬性方法和下標腳本。然而多層可選鏈不能再添加比已經返回的可選值更多的層。
如果你試圖通過可選鏈獲得Int值,不論使用了多少層鏈接返回的總是Int?。 相似的,如果你試圖通過可選鏈獲得Int?值,不論使用了多少層鏈接返回的總是Int?。
if let johnsStreet = john.residence?.address?.street
- 對返回可選值的函數進行鏈接
我們還可以通過可選鏈接來調用返回可空值的方法,并且可以繼續對可選值進行鏈接。
類型轉換
類型轉換
Swift 語言類型轉換可以判斷實例的類型。也可以用于檢測實例類型是否屬于其父類或者子類的實例。
Swift 中類型轉換使用 is 和 as 操作符實現,is 用于檢測值的類型,as 用于轉換類型。
類型轉換也可以用來檢查一個類是否實現了某個協議。
檢查類型
類型轉換用于檢測實例類型是否屬于特定的實例類型。
你可以將它用在類和子類的層次結構上,檢查特定類實例的類型并且轉換這個類實例的類型成為這個層次結構中的其他類型。
類型檢查使用 is 關鍵字。
操作符 is 來檢查一個實例是否屬于特定子類型。若實例屬于那個子類型,類型檢查操作符返回 true,否則返回 false。向下轉型
向下轉型,用類型轉換操作符(as? 或 as!)
當你不確定向下轉型可以成功時,用類型轉換的條件形式(as?)。條件形式的類型轉換總是返回一個可選值(optional value),并且若下轉是不可能的,可選值將是 nil。
只有你可以確定向下轉型一定會成功時,才使用強制形式(as!)。當你試圖向下轉型為一個不正確的類型時,強制形式的類型轉換會觸發一個運行時錯誤。Any和AnyObject的類型轉換
Swift為不確定類型提供了兩種特殊類型別名:
AnyObject可以代表任何class類型的實例。
Any可以表示任何類型,包括方法類型(function types)。
注意:
只有當你明確的需要它的行為和功能時才使用Any和AnyObject。在你的代碼里使用你期望的明確的類型總是更好的。
協議
指定了類和結構需要實現的方法和變量,optional關鍵字可以定義協議中選擇實現的方法和變量,一個結構體或者類可以實現多個協議,值得注意的是協議之間是可以繼承的。
- 語法
協議的語法格式如下:
protocol SomeProtocol {
// 協議內容
}
要使類遵循某個協議,需要在類型名稱后加上協議名稱,中間以冒號:分隔,作為類型定義的一部分。遵循多個協議時,各協議之間用逗號,分隔。
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 結構體內容
}
如果類在遵循協議的同時擁有父類,應該將父類名放在協議名之前,以逗號分隔。
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 類的內容
}
對屬性的規定
協議用于指定特定的實例屬性或類屬性,而不用指定是存儲型屬性或計算型屬性。此外還必須指明是只讀的還是可讀可寫的。
協議中的通常用var來聲明變量屬性,在類型聲明后加上{ set get }來表示屬性是可讀可寫的,只讀屬性則用{ get }來表示。對 Mutating 方法的規定
有時需要在方法中改變它的實例。
例如,值類型(結構體,枚舉)的實例方法中,將mutating關鍵字作為函數的前綴,寫在func之前,表示可以在該方法中修改它所屬的實例及其實例屬性的值。對構造器的規定
協議可以要求它的遵循者實現指定的構造器。
你可以像書寫普通的構造器那樣,在協議的定義里寫下構造器的聲明,但不需要寫花括號和構造器的實體,語法如下:
protocol SomeProtocol {
init(someParameter: Int)
}
實例
protocol tcpprotocol {
init(aprot: Int)
}
- 在擴展中添加協議成員
我們可以可以通過擴展來擴充已存在類型( 類,結構體,枚舉等)。
擴展可以為已存在的類型添加屬性,方法,下標腳本,協議等成員。
protocol AgeClasificationProtocol {
var age: Int { get }
func agetype() -> String
}
class Person {
let firstname: String
let lastname: String
var age: Int
init(firstname: String, lastname: String) {
self.firstname = firstname
self.lastname = lastname
self.age = 10
}
}
extension Person : AgeClasificationProtocol {
func fullname() -> String {
var c: String
c = firstname + " " + lastname
return c
}
func agetype() -> String {
switch age {
case 0...2:
return "Baby"
case 2...12:
return "Child"
case 13...19:
return "Teenager"
case let x where x > 65:
return "Elderly"
default:
return "Normal"
}
}
}
- 協議的繼承
協議能夠繼承一個或多個其他協議,可以在繼承的協議基礎上增加新的內容要求。
協議的繼承語法與類的繼承相似,多個被繼承的協議間用逗號分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 協議定義
}
- 類專屬協議
你可以在協議的繼承列表中,通過添加class關鍵字,限制協議只能適配到類(class)類型。
該class關鍵字必須是第一個出現在協議的繼承列表中,其后,才是其他繼承協議。格式如下:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 協議定義
}
實例
protocol TcpProtocol {
init(no1: Int)
}
class MainClass {
var no1: Int // 局部變量
init(no1: Int) {
self.no1 = no1 // 初始化
}
}
class SubClass: MainClass, TcpProtocol {
var no2: Int
init(no1: Int, no2 : Int) {
self.no2 = no2
super.init(no1:no1)
}
// 因為遵循協議,需要加上"required"; 因為繼承自父類,需要加上"override"
required override convenience init(no1: Int) {
self.init(no1:no1, no2:0)
}
}
let res = MainClass(no1: 20)
let show = SubClass(no1: 30, no2: 50)
print("res is: \(res.no1)")
print("res is: \(show.no1)")
print("res is: \(show.no2)")
- 協議合成
Swift 支持合成多個協議,這在我們需要同時遵循多個協議時非常有用。
語法格式如下:
protocol Stname {
var name: String { get }
}
protocol Stage {
var age: Int { get }
}
struct Person: Stname, Stage {
var name: String
var age: Int
}
func show(celebrator: Stname & Stage) {
print("\(celebrator.name) is \(celebrator.age) years old")
}
let studname = Person(name: "Priya", age: 21)
print(studname)
let stud = Person(name: "Rehan", age: 29)
print(stud)
let student = Person(name: "Roshan", age: 19)
print(student)
以上程序執行輸出結果為:
Person(name: "Priya", age: 21)
Person(name: "Rehan", age: 29)
Person(name: "Roshan", age: 19)
- 檢驗協議的一致性
你可以使用is和as操作符來檢查是否遵循某一協議或強制轉化為某一類型。
is操作符用來檢查實例是否遵循了某個協議。
as?返回一個可選值,當實例遵循協議時,返回該協議類型;否則返回nil。
as用以強制向下轉型,如果強轉失敗,會引起運行時錯誤。
實例
protocol HasArea {
var area: Double { get }
}
for object in objects {
// 對迭代出的每一個元素進行檢查,看它是否遵循了HasArea協議
if let objectWithArea = object as? HasArea {
print("面積為 \(objectWithArea.area)")
} else {
print("沒有面積")
}
}
- 多協議
protocol AreaComputetionProtocol {
func computeArea() -> Double
}
protocol PerimeterComputetionProtocol {
func computePerimeter() -> Double
}
struct RectAngle: AreaComputetionProtocol, PerimeterComputetionProtocol {
var width, height: Double
internal func computeArea() -> Double {
return width*height
}
internal func computePerimeter() -> Double {
return 2*(width + height)
}
}
let rect: RectAngle = RectAngle(width: 3.0, height: 4.0)
print(rect.computeArea())
print(rect.computePerimeter())
//協議繼承
protocol TriangeleProtocol: AreaComputetionProtocol, PerimeterComputetionProtocol {
var a: Double {get set}
var b: Double {get set}
var c: Double {get set}
var base: Double {get set}
var height: Double {get set}
}
struct Triangle: TriangeleProtocol {
var a: Double = 0.0
var b: Double = 0.0
var c: Double = 0.0
var base: Double = 0.0
var height: Double = 0.0
func computeArea() -> Double {
return base*height/2.0
}
func computePerimeter() -> Double {
return a + b + c
}
}
let triangle: Triangle = Triangle(a: 3, b: 4, c: 5, base: 3, height: 4)
print(triangle.computeArea())
print(triangle.computePerimeter())
說到協議就不得不說委托,委托是為了讓一個類或結構體能夠將工作和決策交給另一個類或結構去完成。
//委托:讓一個類或結構能夠將工作和決策交給另一個類或結構去完成
/// 售貨機協議
protocol VendingMathineProtocol {
/// 是否投幣
var coinInserted: Bool {get set}
/// 能否售貨
func shouldVend() -> Bool
}
/// 自動售貨機類,遵守售貨機協議
class Vendor: VendingMathineProtocol {
var coinInserted: Bool = false
/// 如果投幣則售貨否則不售貨
///
/// - Returns: 是否售貨
func shouldVend() -> Bool {
if coinInserted {
coinInserted = false
return true
}
else
{
return false
}
}
}
/// 可樂機類
class ColaMethine {
// 自動售貨機類,遵守售貨機協議
var vendor: VendingMathineProtocol
init(vendor: VendingMathineProtocol) {
self.vendor = vendor
}
/// 投幣
func insertCoin() {
vendor.coinInserted = true
}
/// 銷售可樂按鈕事件
func pressColaButton() -> String {
if vendor.shouldVend() {
return "Here's a cola!"
}
else
{
return "You must insert coin!"
}
}
/// 銷售啤酒按鈕事件
func pressRootBeerButton() -> String {
if vendor.shouldVend() {
return "Here's a Root Beer!"
}
else
{
return "You must insert coin!"
}
}
}
let methine: ColaMethine = ColaMethine.init(vendor: Vendor.init())
print(methine.pressColaButton())
methine.insertCoin()
print(methine.pressColaButton())
methine.insertCoin()
print(methine.pressRootBeerButton())
print(methine.pressColaButton())
泛型
Swift 提供了泛型讓你寫出靈活且可重用的函數和類型。
Swift 標準庫是通過泛型代碼構建出來的。
Swift 的數組和字典類型都是泛型集。
不指定數據的類型,只是指定一個占位符。編譯器負責對數據類型進行檢查。使用方式如下:
func valueEqual<T: Equatable>(value1: T, value2: T) -> Bool {
return value1 == value2
}
代碼來看,它們功能代碼是相同的,只是類型上不一樣,這時我們可以使用泛型,從而避免重復編寫代碼。
泛型使用了占位類型名(在這里用字母 T 來表示)來代替實際類型名(例如 Int、String 或 Double)。
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
swapTwoValues 后面跟著占位類型名(T),并用尖括號括起來(<T>)。這個尖括號告訴 Swift 那個 T 是 swapTwoValues(::) 函數定義內的一個占位類型名,因此 Swift 不會去查找名為 T 的實際類型。
以下實例是一個泛型函數 exchange 用來交換兩個 Int 和 String 值:
// 定義一個交換兩個變量的函數
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
var numb1 = 100
var numb2 = 200
print("交換前數據: \(numb1) 和 \(numb2)")
swapTwoValues(&numb1, &numb2)
print("交換后數據: \(numb1) 和 \(numb2)")
var str1 = "A"
var str2 = "B"
print("交換前數據: \(str1) 和 \(str2)")
swapTwoValues(&str1, &str2)
print("交換后數據: \(str1) 和 \(str2)")
以上程序執行輸出結果為:
交換前數據: 100 和 200
交換后數據: 200 和 100
交換前數據: A 和 B
交換后數據: B 和 A
泛型類型
Swift 允許你定義你自己的泛型類型。
自定義類、結構體和枚舉作用于任何類型,如同 Array 和 Dictionary 的用法。
接下來我們來編寫一個名為 Stack (棧)的泛型集合類型,棧只允許在集合的末端添加新的元素(稱之為入棧),且也只能從末端移除元素(稱之為出棧)。類型約束
類型約束指定了一個必須繼承自指定類的類型參數,或者遵循一個特定的協議或協議構成。
類型約束語法
你可以寫一個在一個類型參數名后面的類型約束,通過冒號分割,來作為類型參數鏈的一部分。這種作用于泛型函數的類型約束的基礎語法如下所示(和泛型類型的語法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 這里是泛型函數的函數體部分
}
上面這個函數有兩個類型參數。第一個類型參數 T,有一個要求 T 必須是 SomeClass 子類的類型約束;第二個類型參數 U,有一個要求 U 必須符合 SomeProtocol 協議的類型約束。
實例
// 非泛型函數,查找指定字符串在數組中的索引
func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
for (index, value) in array.enumerated() {
if value == valueToFind {
// 找到返回索引值
return index
}
}
return nil
}
let strings = ["google", "weibo", "taobao", "runoob", "facebook"]
if let foundIndex = findIndex(ofString: "runoob", in: strings) {
print("runoob 的索引為 \(foundIndex)")
}
索引下標從 0 開始。
以上程序執行輸出結果為:
runoob 的索引為 3
- 關聯類
Swift 中使用 associatedtype 關鍵字來設置關聯類型實例。
下面例子定義了一個 Container 協議,該協議定義了一個關聯類型 ItemType。
Container 協議只指定了三個任何遵從 Container 協議的類型必須提供的功能。遵從協議的類型在滿足這三個條件的情況下也可以提供其他額外的功能。
// Container 協議
protocol Container {
associatedtype ItemType
// 添加一個新元素到容器里
mutating func append(_ item: ItemType)
// 獲取容器中元素的數
var count: Int { get }
// 通過索引值類型為 Int 的下標檢索到容器中的每一個元素
subscript(i: Int) -> ItemType { get }
}
// Stack 結構體遵從 Container 協議
struct Stack<Element>: Container {
// Stack<Element> 的原始實現部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 協議的實現部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
var tos = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素個數
print( tos.count)
以上程序執行輸出結果為:
["google", "runoob", "taobao"]
3
- Where 語句
類型約束能夠確保類型符合泛型函數或類的定義約束。
你可以在參數列表中通過where語句定義參數的約束。
你可以寫一個where語句,緊跟在在類型參數列表后面,where語句后跟一個或者多個針對關聯類型的約束,以及(或)一個或多個類型和關聯類型間的等價(equality)關系。
實例
下面的例子定義了一個名為allItemsMatch的泛型函數,用來檢查兩個Container實例是否包含相同順序的相同元素。
如果所有的元素能夠匹配,那么返回 true,反之則返回 false。
// Container 協議
protocol Container {
associatedtype ItemType
// 添加一個新元素到容器里
mutating func append(_ item: ItemType)
// 獲取容器中元素的數
var count: Int { get }
// 通過索引值類型為 Int 的下標檢索到容器中的每一個元素
subscript(i: Int) -> ItemType { get }
}
// // 遵循Container協議的泛型TOS類型
struct Stack<Element>: Container {
// Stack<Element> 的原始實現部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 協議的實現部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
// 擴展,將 Array 當作 Container 來使用
extension Array: Container {}
func allItemsMatch<C1: Container, C2: Container>
(_ someContainer: C1, _ anotherContainer: C2) -> Bool
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable {
// 檢查兩個容器含有相同數量的元素
if someContainer.count != anotherContainer.count {
return false
}
// 檢查每一對元素是否相等
for i in 0..<someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}
// 所有元素都匹配,返回 true
return true
}
var tos = Stack<String>()
tos.push("google")
tos.push("runoob")
tos.push("taobao")
var aos = ["google", "runoob", "taobao"]
if allItemsMatch(tos, aos) {
print("匹配所有元素")
} else {
print("元素不匹配")
}
以上程序執行輸出結果為:
匹配所有元素
- 運算符重載:只需定義一個運算符命名的函數就可以對運算符進行重載。
訪問控制
- public 可以訪問自己模塊中源文件里的任何實體,別人也可以通過引入該模塊來訪問源文件里的所有實體。
- internal 可以訪問自己模塊中源文件里的任何實體,但是別人不能訪問該模塊中源文件里的實體。
- fileprivate 文件內私有,只能在當前源文件中使用。
- private 只能在類中訪問,離開了這個類或者結構體的作用域外面就無法訪問。
1.public 為最高級訪問級別,private 為最低級訪問級別。
2.元組的訪問級別與元組中訪問級別最低的類型一致
3.枚舉中成員的訪問級別繼承自該枚舉,你不能為枚舉中的成員單獨申明不同的訪問級別。
4.函數的訪問級別需要根據該函數的參數類型和返回類型的訪問級別得出。
5.子類的訪問級別不得高于父類的訪問級別。比如說,父類的訪問級別是internal,子類的訪問級別就不能申明為public。
6.常量、變量、屬性不能擁有比它們的類型更高的訪問級別。
比如說,你定義一個public級別的屬性,但是它的類型是private級別的,這是編譯器所不允許的。
同樣,下標也不能擁有比索引類型或返回類型更高的訪問級別。
如果常量、變量、屬性、下標索引的定義類型是private級別的,那么它們必須要明確的申明訪問級別為private:
7.如果想為一個協議明確的申明訪問級別,那么需要注意一點,就是你要確保該協議只在你申明的訪問級別作用域中使用。
如果你定義了一個public訪問級別的協議,那么實現該協議提供的必要函數也會是public的訪問級別。這一點不同于其他類型,比如,public訪問級別的其他類型,他們成員的訪問級別為internal。
8.任何你定義的類型別名都會被當作不同的類型,以便于進行訪問控制。一個類型別名的訪問級別不可高于原類型的訪問級別。
9.常量、變量、屬性、下標索引的Getters和Setters的訪問級別繼承自它們所屬成員的訪問級別。
擴展
不繼承而擴展類的功能,擴展能夠以非侵入式的方式,增加類、結構、甚至基本類型的行為和功能,類似于Objective_C的類別,但是強大得多。基本使用如下:
擴展就是向一個已有的類、結構體或枚舉類型添加新功能。
擴展可以對一個類型添加新的功能,但是不能重寫已有的功能。
// MARK: - 可樂機的擴展
extension ColaMethine {
/// 健怡可樂按鈕事件
func pressDietColaButton() -> String {
if vendor.shouldVend() {
return "Here's a Diet Cola!"
}
else
{
return "You must insert coin!"
}
}
}
methine.insertCoin()
print(methine.pressDietColaButton())
1.擴展基本數據類型:不能在擴展中添加常規存儲屬性,但可以添加計算屬性:值是通過計算獲得的屬性
let limit: Double = 1024.0
// MARK: - 為Int64擴展屬性,獲取存儲單位的換算結果
extension Int64 {
var K: String {return String.init(format: "%fK", Double(self)/limit)}
var M: String {return String.init(format: "%fM", Double(self)/limit/limit)}
var G: String {return String.init(format: "%fG", Double(self)/limit/limit/limit)}
var T: String {return String.init(format: "%fT", Double(self)/limit/limit/limit/limit)}
}
let bytes: Int64 = 2345346457
print(bytes.K)
print(bytes.M)
print(bytes.G)
print(bytes.T)
2.mutating關鍵字:如果要修改自身的值而不是返回計算的結果,那么就得使用mutating關鍵字
// MARK: - 為Double數據類型擴展方法
extension Double {
/// 平方值
mutating func squra() {
self = self*self
}
/// 立方值
mutating func cube() {
self = self*self*self
}
}
var num: Double = 1.5
num.squra()
print(num)
num.cube()
print(num)
相等與相同
相等是指值相等,相同是指指向的對象為同一個對象。使用==來判斷值的相等,用===來判斷是否為同一個對象。
let a: Int = 1
let b: Int = 1
class IntClass {
var value: Int = 0
init(value: Int) {
self.value = value
}
}
let intA: IntClass = IntClass.init(value: 1)
let intB: IntClass = IntClass.init(value: 1)
print(a == b)
print(a != b)
print(intA === intB)
print(intA !== intB)
Swift內存管理
Swift以ARC方式為引用類型數據管理內存,這種方式是由編譯器提供支持的。每一個引用類型的數據都有一個引用計數的的屬性,當這個對象被創建的時候它的引用計數就為1,當這個對象在應用程序運行過程中被傳遞,就可能被多個“所有者”持有并使用。當“所有者”獲得所有權時引用計算就相應加1,而在放棄所有權時引用計數就相應減1,直到引用計數為0時(所有的“所有者”都放棄了所有權),對象就會被銷毀,它所占用的內存歸還給內存池,供其他對象使用。
1.循環引用:循環引用就是指不同對象之間相互持有對方,這樣造成了對象永遠被“所有者”所占有,而無法釋放,從而形成內存泄漏。
- 普通對象之間的循環引用:這種循環引用可以使用weak關鍵字來打破。
- 閉包內的循環引用:閉包是引用類型的數據,類的實例變量強引用了閉包,而如果閉包中再引用了類對象的話,就會造成循環引用。在閉包定義中添加"[unowned self] in"可以解決閉包內的循環引用。
Swift 提供了兩種辦法用來解決你在使用類的屬性時所遇到的循環強引用問題:
弱引用(weak)
無主引用(unowned)
弱引用和無主引用允許循環引用中的一個實例引用另外一個實例而不保持強引用。這樣實例能夠互相引用而不產生循環強引用。
對于生命周期中會變為nil的實例使用弱引用。相反的,對于初始化賦值后再也不會被賦值為nil的實例,使用無主引用。
說明:一個對象在銷毀的時候會調用deinit,而一個變量賦值為nil時就會放棄對原有對象的持有。