概述
Swift是蘋果2014年推出的全新的編程語言,它繼承了C語言、ObjC的特性,且克服了C語言的兼容性問題。Swift發展過程中不僅保留了ObjC很多語法特性,它也借鑒了多種現代化語言的特點,在其中你可以看到C#、Java、Javascript、Python等多種語言的影子。同時在2015年的WWDC上蘋果還宣布Swift的新版本Swift2.0,並宣布稍后Swift即將開源,除了支持iOS、OS X之外還將支持linux。
本文將繼續iOS開發系列教程,假設讀者已經有了其他語言基礎(強烈建議初學者從本系列第一章開始閱讀,如果您希望從Swift學起,那么推薦你首先閱讀蘋果官方電子書《the swift programming language》),不會從零基礎一點點剖析這門語言的語法,旨在幫助大家快速從ObjC快速過度到Swift開發中。即便如此,要盡可能全面的介紹Swift的語法特點也不是一件容易的事情,因此本文將采用較長的篇幅進行介紹。
基礎部分
第一個Swift程序
創建一個命令行程序如下:
import Foundation /** * Swift沒有main函數,默認從top level code的上方開始自上而下執行(因此不能有多個top level代碼) */ println("Hello, World!")
從上面的代碼可以看出:
- Swift沒有main函數,從top level code的上方開始往下執行(就是第一個非聲明語句開始執行[表達式或者控制結構,類、結構體、枚舉和方法等屬於聲明語句]),不能存在多個top level code文件(否則編譯器無法確定執行入口,事實上swift隱含一個main函數,這個main函數會設置並調用全局 “C_ARGC C_ARGV”並調用由top level code構成的top_level_code()函數);
- Swift通過import引入其他類庫(和Java比較像);
- Swift語句不需要雙引號結尾(盡管加上也不報錯),除非一行包含多條語句(和Python有點類似);
數據類型
Swift包含了C和ObjC語言中的所有基礎類型,Int整形,Float和Double浮點型,Bool布爾型,Character字符型,String字符串類型;當然還包括enum枚舉、struct結構體構造類型;Array數組、Set集合、Dictionary字典集合類型;不僅如此還增加了高階數據類型元組(Tuple),可選類型(Optinal)。
基礎類型
Xcode 從6.0開始加入了Playground代碼測試,可以實時查看代碼執行結果,下面使用Playground簡單演示一下Swift的基礎內容,對Swift有個簡單的認識:
import Foundation var a:Int=1 //通過var定義一個變量 //下面變量b雖然沒有聲明類型,但是會自動進行類型推斷,這里b推斷為Int類型 var b=2 var c:UInt=3 let d=a+b //通過let定義一個變量 //下面通過"\()"實現了字符串和變量相加(字符串插值),等價於println("d="+String(d)) println("d=\(d)") //結果:d=3 //注意由於Swift是強類型語言,a是Int類型而c是UInt類型,二者不能運算,下面的語句報錯;但是注意如果是類似於:let a=1+2.0是不會報錯的,因為兩個都是字面量,Swift會首先計算出結果再推斷a的類型 //let e=a+c //Int.max是Int類型的最大值,類似還有Int.min、Int32.max、Int32.min等 let e=Int.max //結果:9223372036854775807 var f:Float=1.0 var g=2.0 //浮點型自動推斷為Double類型 var h:String="hello " //emoj表情也可以作為變量或者常量,事實上所有Unicode字符都是可以的 var 💖🍎="love and apple" //兩個字符串相加,但是注意不同類型不能相加 var i=h+💖🍎 //結果:hello love and apple //布爾類型只有兩個值true、false,類似於if語句中的條件只能是布爾類型不能像ObjC一樣非0即真 var j:Bool=true //字符類型,同樣使用雙引號,但是只能是一個字符,如果不指定類型則"c"默認會推斷為字符串(var k:Character="c"是字符類型,但是var k="c"是字符串類型) var k:Character="c" var l=00100 //等於100,可以在前面添加額外的0 var m=10_000_000 //等於10000000,可以使用增加額外的下划線方便閱讀而不改變值的大小
- Swift通過var進行變量定義,通過let進行常量定義(這和其他高級語言比較類似,例如F#);
- Swift添加了類型推斷,對於賦值的常量或者變量會自動推斷其具體類型;
- Swift是強類型語言(應該說它比C#、Java等強類型語言控制還要嚴格),不同的數據類型之間不能隱式轉化,如果需要轉化只能強制轉化;
- 在Swift中類型轉換直接通過其類型構造函數即可,降低了API的學習成本;
集合類型
Swift提供了三種集合類型:數組Array、集合Set、字典Dictionary。和ObjC不同的是,由於Swift的強類型,集合中的元素必須是同一類型,而不能像ObjC一樣可以存儲任何對象類型,並且注意Swift中的集合是值類型而非引用類型(事實上包括String、結構體struct、枚舉enum都是值類型)。
首先看一下Swift中的數組:
//聲明數組的時候必須確定其類型,下面使用[String]聲明一個字符串數組([String]是Array<String>簡單表達形式) //var a:Array<String>=["hello","world"] var a:[String]=["hello","world"] a[0] //訪問數組元素 //下面創建一個Double類型的數組,這里沒有使用字面量,當前是一個空數組,當然也可以寫成var b:[Double]=[] var b=[Double]() for i in a{ println("i=\(i)") } //添加元素,Swift中可變類型不再由單獨的一個類型來表示,統統使用Array,如果想聲明為不可變數組只要使用let定義即可 a.append("!") a+=["I" ,"am" ,"Kenshin"] //追加元素 println("a.count=\(a.count)") //結果:a.count=6 a[3...5]=["I","Love","Swift"] //修改元素,但是注意無法用這種方式添加元素 //a[6]=["."]//這種方式是錯誤的 a.insert("New", atIndex: 5) //插入元素:hello world! I Love New Swift a.removeAtIndex(5) //刪除指定元素 //使用全局enumerate函數遍歷數據索引和元素 for (index,element) in enumerate(a){ println("index=\(index),element=\(element)") } //使用構造函數限制數組元素個數並且指定默認值,等價於var c=Array(count: 3, repeatedValue: 1),自動推斷類型 var c=[Int](count: 3, repeatedValue: 1)
Set表示沒有順序的集合:
//注意集合沒有類似於數組的簡化形式,例如不能寫成var a:[String]=["hello","world"] var a:Set<String>=["hello","world"] var b:Set=[1,2] //類型推斷:Set<Int> a.insert("!") //注意這個插入不保證順序 if !a.isEmpty { //判斷是否為空 a.remove("!") } if !a.contains("!"){ a.insert("!") }
Dictionary字典同樣是沒有順序的,並且在Swift中字典同樣要在使用時明確具體的類型。和ObjC中一樣,字典必須保證key是唯一的,而這一點就要求在Swift中key必須是可哈希的,不過幸運的是Swift中的基本類型(如Int、Float、Double、Bool、String)都是可哈希的,都可以作為key。
//通過字面量進行字典初始化,注意等價於var a:Dictionary<Int,String>=[200:"success",404:"not found"] var a:[Int:String]=[200:"success",404:"not found"] var b=[200:"success",404:"not found"] //不聲明類型,根據值自動推斷類型 a[200] //讀取字典 a[404]="can not found" //修改 a[500]="internal server error" //添加 //a=[:] //設置為空字典,等價於:a=[Int:String]() for code in a.keys{ println("code=\(code)") } for description in a.values{ println("description=\(description)") } for (code,description) in a{ println("code=\(code),description=\(description)") }
注意:在Swift中集合的可變性不是像ObjC一樣由單獨的數據類型來控制的,而是通過變量和常量來控制,這一點和其他高級語言比較類似。
元組(Tuple)
在開發過程中有時候會希望臨時組織一個數據類型,此時可以使用一個結構體或者類,但是由於這個類型並沒有那么復雜,如果定義起來又比較麻煩,此時可以考慮使用元組。
/** * 元組的基本用法 */ var point=(x:50,y:100) //自動推斷其類型:(Int,Int) point.x //可以用類似於結構體的方式直接訪問元素,結果:50 point.y //結果:100 point.0 //也可以采用類似數組的方式使用下標訪問,結果:50 point.1 //結果:100 //元組也可以不指定元素名稱,訪問的時候只能使用下標 let frame:(Int,Int,Int,Float)=(0,0,100,100.0) println(frame) //結果:(0, 0, 100, 100.0) //注意下面的語句是錯誤的,如果指定了元組的類型則無法指定元素名稱 //let frame:(Int,Int,Int,Int)=(x:0,y:0,width:100,height:100) var size=(width:100,25) //僅僅給其中一個元素命名 size.width //結果:100 size.1 //結果:25 var httpStatus:(Int,String)=(200,"success") //元組的元素類型並不一定相同 var (status,description)=httpStatus //一次性賦值給多個變量,此時status=200,description="success" //接收元組的其中一個值忽略另一個值使用"_"(注意在Swift中很多情況下使用_忽略某個值或變量) var (sta,_)=httpStatus println("sta=\(sta)") //結果:sta=200 /** * 元組作為函數的參數或返回值,借助元組實現了函數的多個返回值 */ func request()->(code:Int,description:String){ return (404,"not found") } var result=request() result.0 //結果:404 result.1 //結果:not found result.code //結果:404 result.description //結果:not found
可選類型
所謂可選類型就是一個變量或常量可能有值也可能沒有值則設置為可選類型。在ObjC中如果一個對象類型沒有賦值,則默認為nil,同時nil類型也只能作為對象類型的默認值,對於類似於Int等基本類型則對應0這樣的默認值。由於Swift是強類型語言,如果在聲明變量或常量時沒有進行賦值,Swift並不會默認設置初值(這一點和其他高級語言不太一樣,例如C#雖然也有可選類型,但是要求並沒有那么嚴格)。
/** * 可選類型基礎 */ var x:Float? //使用?聲明成一個可選類型,如果不賦值默認為nil x=172.0 var y:Float=60.0 //var z=x+y //注意此句報錯,因為Int和Int?根本就是兩種不同的類型,在Swift中兩種不同的類型不能運算(因為不會自動進行類型轉化) var z=x!+y //使用!進行強制解包 var age="29" var ageInt=age.toInt() //注意ageInt是Int可選類型而不是Int類型(因為String的toInt()方法並不能保證其一定能轉化為Int類型)
- Swift中類似於Int和Int?並不是同一種類型,不能進行相關運算,如果要運算只能解包;
- 可選類型其本質就是此類型內部存儲分為“Some”和“None”兩個部分,如果有值則存儲到“Some”中,沒有值則為“None”(早期Playground中可以看到兩個部分,如今已經取消顯示Some等描述了),使用感嘆號強制解包的過程就是取出“Some”部分;
既然可選類型有可能有值,也可能沒有值那么往往有時候就需要判斷。可以使用if直接判斷一個可選類型是否為nil,這樣一來就可以根據情況進行強制解包(從Some部分取出值的過程);另一個選擇就是在判斷的同時如果有值則將值賦值給一個臨時變量或常量,否則不進入此條件語句,這個過程稱之為“可選綁定”。
/** * 可選類型判斷 */ var age="29" var ageInt=age.toInt() //注意ageInt是Int可選類型而不是Int類型(因為String的toInt()方法並不能保證其一定能轉化為Int類型) if ageInt==nil { println("ageInt=nil") }else{ println("ageInt=\(ageInt!)") //注意這里使用感嘆號!強制解析 } /** * 可選類型綁定 * 如果可選類型有值則將值賦值給一個臨時變量或者常量(此時此變量或者常量接受的值已經不是可選類型),如果沒有值則不執行此條件 */ if let newAge=ageInt{ //此時newAge可以定義成常量也可以定義成變量 println("newAge=\(newAge)") //注意這里並不需要對newAge強制解包 }else{ println("ageInt=nil") }
通過前面的演示可以看出Swift中的可選綁定如果實際計算不得不進行強制解包,如果一個可選類型從第一次賦值之后就能保證有值那么使用時就不必進行強制解包了,這種情況下可以使用隱式可選解析類型(通過感嘆號聲明而不是問號)
/** * 隱式解析可選類型 */ var age:Int!=0 //通過感嘆號聲明隱式解析可選類型,此后使用時雖然是可選類型但是不用強制解包 age=29 var newAge:Int=age //不用強制解包直接賦值給Int類型(程序會自動解包) if var tempAge=age { println("tempAge=\(tempAge)") }else{ println("age=nil") }
運算符
Swift中支持絕大多數C語言的運算符並改進以減少不必要的錯誤(例如等號賦值后不返回值),算術運算會檢查溢出情況,必要時還能使用新增的溢出運算符。另外Swift中還可以對浮點數使用取余運算符,新增了區間運算符。對於基本的運算符這里不再一一介紹,簡單看一下Swift中的區間運算符和溢出運算符。
/** * 區間運算符,通常用於整形或者字符范圍(例如"a"..."z") */ for i in 1...5 { //閉區間運算符...(從1到5,包含5) println("i=\(i)") } for i in 1..<5{ //半開區間運算符..<(從1到4) println("i=\(i)") } var str = "hello world." var range="a"..."z" for t in str { if range.contains(String(t)) { print(t) //結果:helloworld } } /** * 溢出運算符 */ var a=UInt8.max //a=255 //var b:UInt8=a+1 //注意b會出現溢出,此句報錯 //下面使用溢出運算符,結果為:0,類似的還有&-、&*、&/ //使用溢出運算符可以在最大值和最小值之前循環而不會報錯 var b:UInt8=a &+ 1
溢出運算符的原理其實很簡單,例如對於UInt8,如果8位均為1則十進制表示是255,但是當加1之后則變成了9位“100000000”,出現了溢出但是UInt8本身值只能存儲8位,所以取后面8位就變成了“00000000”,十進制表示就是0。
控制流
Swift中的多數控制流和其他語言差別並不大,例如for、while、do while、if、switch等,而且有些前面已經使用過(例如for in循環),這里將着重介紹一些不同點。
var a=["a","b","c","d","e","f","g"] let b=a[1] /** * switch支持一個case多個模式匹配,同時case后不用寫break也會在匹配到種情況后自動跳出匹配,不存在隱式貫穿,如果想要貫穿在case之后添加"fallthrough"關鍵字 */ switch b{ case "a","b": println("b=a or b=b") case "c","d","e","f": println("b in (c,d,e,f)") default: println("b=g") } /** * 匹配區間,同時注意switch必須匹配所有情況,否則必須加上default */ let c:Int=88 switch c{ case 1...60: println("1-60") case 61...90: println("61-90") case 91...100: println("91-100") default: println("1>c>100") } /** * 元組匹配、值綁定、where條件匹配 * 注意下面的匹配沒有default,因為它包含了所有情況 */ var d=(x:900,y:0) switch d{ case (0,0): println("d in (0,0)") case (_,0): //忽略x值匹配 println("d in y") case (0,let y)://值綁定 println("d in x,y=\(y)") case (-100...100,-100...100): //注意這里有可能和第一、二、三個條件重合,但是Swift允許多個case匹配同一個條件,但是只會執行第一個匹配 println("x in(0-100),y in (0-100)") case let (x,y) where x==y: //where條件匹配,注意這里的寫法等同於:(let x,let y) where x==y println("x=y=\(x)") case let (x, y): println("x=\(x),y=\(y)") }
在其他語言中通常可以使用break、continue、return(Swift中添加了fallthrough)等來終止或者跳出某個執行語句,但是對於其行為往往是具有固定性的,例如break只能終止其所在的內層循環,而return只能跳出它所在的函數。在Swift中這種控制轉移功能得到了加強,那就是使用標簽。利用標簽你可以隨意指定轉移的位置,例如下面的代碼演示了如何直接通過標簽跳出最外層循環:
var a=5 whileLoop: while --a>0 { for var i=0;i<a;++i{ println("a=\(a),i=\(i)") break whileLoop //如果此處直接使用break將跳出for循環,而由於這里使用標簽直接跳出了while,結果只會打印一次,其結果為:a=4,i=0 } }
函數和閉包
函數
函數是一個完成獨立任務的代碼塊,Swift中的函數不僅可以像C語言中的函數一樣作為函數的參數和返回值,而且還支持嵌套,並且有C#一樣的函數參數默認值、可變參數等。
//定義一個函數,注意參數和返回值,如果沒有返回值可以不寫返回值或者寫成Void、空元組()(注意Void的本質就是空元組) func sum(num1:Int,num2:Int)->Int{ return num1 + num2 } sum(1, 2)
可以看到Swift中的函數僅僅表達形式有所區別(定義形式類似於Javascript,但是js不用書寫返回值),但是本質並沒有太大的區別。不過Swift中對函數參數強調兩個概念就是局部參數名(又叫“形式參數”)和外部參數名,這極大的照顧到了ObjC開發者的開發體驗。在上面的例子中調用sum函數並沒有傳遞任何參數名,因為num1、num2僅僅作為局部參數名在函數內部使用,但是如果給函數指定一個外部參數名在調用時就必須指定參數名。另外前面也提到關於Swift中的默認參數、可變長度的參數,包括一些高級語言中的輸入輸出參數,通過下面的例子大家會有一個全面的了解。
/** /** * 函數參數名分為局部參數名和外部參數名 */ func split(string a:String,seperator b:Character)->[String]{ return split(a, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0==b}) } //由於給split函數設置了外部參數名string和seperator,所以執行的時候必須帶上外部參數名,此處可以看到一個有意義的外部參數名大大節省開發者使用成本 split(string: "hello,world,!", seperator: ",") //結果:["hello", "world", "!"] //下面通過在局部參數名前加上#來簡寫外部參數名(此時局部參數名和外部參數名相同) func split2(#string:String,#seperator:Character)->[String]{ return split(string, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0==seperator}) } split2(string: "hello,world,!", seperator: ",") //上面的split函數的最后一個參數默認設置為",",注意如果使用默認參數那么此參數名將默認作為外部參數名(此時局部參數名和外部參數名相同) func split3(#string:String,seperator:Character=",")->[String]{ return split(string, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0==seperator}) } split3(string: "hello,world,!", seperator: ",") //結果:["hello", "world", "!"] split3(string: "hello world !", seperator: " ") //結果:["hello", "world", "!"] //但是如果有默認值,又不想指定局部參數名可以使用“_”取消外部參數名 func split4(string:String,_ seperator:Character=",")->[String]{ return split(string, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0==seperator}) } split4("hello,world,!", ",") //結果:["hello", "world", "!"] /** * 可變參數,一個函數最多有一個可變參數並且作為最后一個參數 * 下面strings參數在內部是一個[String],對於外部是不定個數的String參數 */ func joinStr(seperator:Character=",",strings:String...)->String{ var result:String="" for var i=0;i<strings.count;++i{ if i != 0{ result.append(seperator) } result+=strings[i] } return result } joinStr(seperator:" ", "hello","world","!") //結果:"hello world !" /** * 函數參數默認是常量,不能直接修改,通過聲明var可以將其轉化為變量(但是注意C語言參數默認是變量) * 但是注意這個變量對於外部是無效的,函數執行完就消失了 */ func sum2(var num1:Int,num2:Int)->Int{ num1 = num1 + num2 return num1 } sum2(1, 2) //結果:3 /** * 輸入輸出參數 * 通過輸入輸出參數可以在函數內部修改函數外部的變量(注意調用時不能是常量或字面量) * 注意:下面的swap僅僅為了演示,實際使用時請用Swift的全局函數swap */ func swap(inout a:Int ,inout b:Int){ a=a+b b=a-b a=a-b } var a=1,b=2 swap(&a, &b) //調用時參數加上“&”符號 println("a=\(a),b=\(b)") //結果:"a=2,b=1"
和很多語言一樣,Swift中的函數本身也可以看做一種類型,既可以作為參數又可以作為返回值。
/** * 函數類型 */ var sum3=sum //自動推斷sum3的類型:(Int,Int)->Int,注意不同的函數類型之間不能直接賦值 sum3(1,2) //結果:3 //函數作為返回值 func fn()->(Int,Int)->Int{ //下面的函數是一個嵌套函數,作用於是在fn函數內部 func minus(a:Int,b:Int)->Int{ return a-b } return minus; } var minus=fn() //函數作為參數 func caculate(num1:Int,num2:Int,fn:(Int,Int)->Int)->Int{ return fn(num1,num2) } caculate(1, 2, sum) //結果:3 caculate(1,2, minus) //結果:-1
閉包
Swift中的閉包其實就是一個函數代碼塊,它和ObjC中的Block及C#、Java中的lambda是類似的。閉包的特點就是可以捕獲和存儲上下文中的常量或者變量的引用,即使這些常量或者變量在原作用域已經被銷毀了在代碼塊中仍然可以使用。事實上前面的全局函數和嵌套函數也是一種閉包,對於全局函數它不會捕獲任何常量或者變量,而對於嵌套函數則可以捕獲其所在函數的常量或者變量。通常我們說的閉包更多的指的是閉包表達式,也就是沒有函數名稱的代碼塊,因此也稱為匿名閉包。
在Swift中閉包表達式的定義形式如下:
{ ( parameters ) -> returnType in
statements
}
下面通過一個例子看一下如何通過閉包表達式來簡化一個函數類型的參數,在下面的例子中閉包的形式也是一而再再而三的被簡化,充分說明了Swift語法的簡潔性:
func sum(num1:Int,num2:Int)->Int{ return num1 + num2 } func minus(num1:Int,num2:Int)->Int{ return num1 - num2 } func caculate(num1:Int,num2:Int,fn:(Int,Int)->Int)->Int{ return fn(num1,num2) } var (a,b)=(1,2) caculate(a, b, sum) //結果:3 caculate(a, b, minus) //結果:-1 //利用閉包表達式簡化閉包函數 caculate(a, b, {(num1:Int,num2:Int)->Int in return num1 - num2 }) //結果:-1 //簡化形式,根據上下文推斷類型並且對於單表達式閉包(只有一個語句)可以隱藏return關鍵字 caculate(a, b, { num1,num2 in num1 - num2 }) //結果:-1 //再次簡化,使用參數名縮寫,使用$0...$n代表第n個參數,並且此in關鍵字也省略了 caculate(a, b, { $0 - $1 }) //結果:-1
考慮到閉包表達式的可讀取性,Swift中如果一個函數的最后一個參數是一個函數類型的參數(或者說是閉包表達式),則可以將此參數寫在函數括號之后,這種閉包稱之為“尾隨閉包”。
func sum(num1:Int,num2:Int)->Int{ return num1 + num2 } func minus(num1:Int,num2:Int)->Int{ return num1-num2 } func caculate(num1:Int,num2:Int,fn:(Int,Int)->Int)->Int{ return fn(num1,num2) } var (a,b)=(1,2) //尾隨閉包,最后一個參數是閉包表達式時可以卸載括號之后,同時注意如果這個函數只有一個閉包表達式參數時可以連通括號一塊省略 //請注意和函數定義進行區分 caculate(a, b){ $0 - $1 } //結果:-1
前面說過閉包之所以稱之為“閉包”就是因為其可以捕獲一定作用域內的常量或者變量進而閉合並包裹着。
func add()->()->Int{ var total=0 var step=1 func fn()->Int{ total+=step return total } return fn } //fn捕獲了total和step,盡管下面的add()執行完后total和step被釋放,但是由於fn捕獲了二者的副本,所以fn會隨着兩個變量的副本一起被存儲 var a=add() a() //結果:1 a() //結果:2,說明a中保存了total的副本(否則結果會是1) var b=add() b() //結果:1 ,說明a和b單獨保存了total的副本(否則結果會是3) var c=b c() //結果:2,說明閉包是引用類型,換句話說函數是引用類型(否則結果會是1)
Swift會自動決定捕獲變量或者常量副本的拷貝類型(值拷貝或者引用拷貝)而不需要開發者關心,另外被捕獲的變量或者常量的內存管理同樣是由Swift來管理,例如當上面的函數a不再使用了那么fn捕獲的兩個變量也就釋放了。
類
作為一門面向對象語言,類當然是Swift中的一等類型。首先通過下面的例子讓大家對Swift的class有一個簡單的印象,在下面的例子中可以看到Swift中的屬性、方法(包括構造方法和析構方法):
//Swift中一個類可以不繼承於任何其他基類,那么此類本身就是一個基類 class Person { //定義屬性 var name:String var height=0.0 //構造器方法,注意如果不編寫構造方法默認會自動創建一個無參構造方法 init(name:String){ self.name=name } //定義方法 func showMessage(){ println("name=\(name),height=\(height)") } //析構方法,在對象被釋放時調用,類似於ObjC的dealloc,注意此函數沒有括號,沒有參數,無法直接調用 deinit{ println("deinit...") } } var p=Person(name: "Kenhin") p.height=172.0 p.showMessage() //結果:name=Kenhin,height=172.0 //類是引用類型 var p2 = p p2.name = "Kaoru" println(p.name) //結果:Kaoru if p === p2 { //“===”表示等價於,這里不能使用等於“==”(等於用於比較值相等,p和p2是不同的值,只是指向的對象相同) println("p===p2") //p等價於p2,二者指向同一個對象 }
從上面的例子不難看出:
- Swift中的類不必須繼承一個基類(但是ObjC通常必須繼承於NSObject),如果一個類沒有繼承於任何其他類則這個類也稱為“基類”;
- Swift中的屬性定義形式類似於其他語句中的成員變量(或稱之為“實例變量”),盡管它有着成員變量沒有的特性;
- Swift中如果開發者沒有自己編寫構造方法那么默認會提供一個無參數構造方法(否則不會自動生成構造方法);
- Swift中的析構方法沒有括號和參數,並且不支持自行調用;
屬性
Swift中的屬性分為兩種:存儲屬性(用於類、結構體)和計算屬性(用於類、結構體、枚舉),並且在Swift中並不強調成員變量的概念。 無論從概念上還是定義方式上來看存儲屬性更像其他語言中的成員變量,但是不同的是可以控制讀寫操作、通過屬性監視器來屬性的變化以及快速實現懶加載功能。
class Account { var balance:Double=0.0 } class Person { //firstName、lastName、age是存儲屬性 var firstName:String var lastName:String let age:Int //fullName是一個計算屬性,並且由於只定義了get方法,所以是一個只讀屬性 var fullName:String{ get{ return firstName + "." + lastName } set{ let array=split(newValue, maxSplit: Int.max, allowEmptySlices: false, isSeparator: {$0=="."}) if array.count == 2 { firstName=array[0] lastName=array[1] } } //set方法中的newValue表示即將賦的新值,可以自己設置set中的newValue變量,如下: // set(myValue){ // } } //如果fullName只有get則是一個只讀屬性,只讀屬性可以簡寫如下: // var fullName:String{ // return firstName + "." + lastName // } //屬性的懶加載,第一次訪問才會計算初始值,在Swift中懶加載的屬性不一定就是對象類型,也可以是基本類型 lazy var account = Account() //構造器方法,注意如果不編寫構造方法默認會自動創建一個無參構造方法 init(firstName:String,lastName:String,age:Int){ self.firstName=firstName self.lastName=lastName self.age=age } //定義方法 func showMessage(){ println("name=\(self.fullName)") } } var p=Person(firstName: "Kenshin", lastName: "Cui",age:29) p.fullName="Kaoru.Sun" p.account.balance=10 p.showMessage()
需要提醒大家的是:
- 計算屬性並不直接存儲一個值,而是提供getter來獲取一個值,或者利用setter來間接設置其他屬性;
- lazy屬性必須有初始值,必須是變量不能是常量(因為常量在構造完成之前就已經確定了值);
- 在構造方法之前存儲屬性必須有值,無論是變量屬性(var修飾)還是常量屬性(let修飾)這個值既可以在屬性創建時指定也可以在構造方法內指定;
從上面的例子中不難區分存儲屬性和計算屬性,計算屬性通常會有一個setter、getter方法,如果要監視一個計算屬性的變化在setter方法中即可辦到(因為在setter方法中可以newValue或者自定義參數名),但是如果是存儲屬性就無法通過監視屬性的變化過程了,因為在存儲屬性中是無法定義setter方法的。不過Swift為我們提供了另外兩個方法來監視屬性的變化那就是willSet和didSet,通常稱之為“屬性監視器”或“屬性觀察器”。
class Account { //注意設置默認值0.0時監視器不會被調用 var balance:Double=0.0{ willSet{ self.balance=2.0 //注意newValue可以使用自定義值,並且在屬性監視器內部調用屬性不會引起監視器循環調用,注意此時修改balance的值沒有用 println("Account.balance willSet,newValue=\(newValue),value=\(self.balance)") } didSet{ self.balance=3.0 //注意oldValue可以使用自定義值,並且在屬性監視器內部調用屬性不會引起監視器循環調用,注意此時修改balance的值將作為最終結果 println("Account.balance didSet,oldValue=\(oldValue),value=\(self.balance)") } } } class Person { var firstName:String var lastName:String let age:Int var fullName:String{ get{ return firstName + "." + lastName } set{ //對於計算屬性可以直接在set方法中進行屬性監視 let array=split(newValue, maxSplit: Int.max, allowEmptySlices: false, isSeparator: { $0 == "." }) if array.count == 2 { firstName=array[0] lastName=array[1] } } } lazy var account = Account() init(firstName:String,lastName:String,age:Int){ self.firstName=firstName self.lastName=lastName self.age=age } //類型屬性 static var skin:Array<String>{ return ["yellow","white","black"]; } } var p=Person(firstName: "Kenshin", lastName: "Cui",age:29) p.account.balance=1.0 println("p.account.balance=\(p.account.balance)") //結果:p.account.balance=3.0 for color in Person.skin { println(color) }
- 和setter方法中的newValue一樣,默認情況下載willSet和didSet中會有一個newValue和oldValue參數表示要設置的新值和已經被修改過的舊值(當然參數名同樣可以自定義);
- 存儲屬性的默認值設置不會引起屬性監視器的調用(另外在構造方法中賦值也不會引起屬性監視器調用),只有在外部設置存儲屬性才會引起屬性監視器調用;
- 存儲屬性的屬性監視器willSet、didSet內可以直接訪問屬性,但是在計算屬性的get、set方法中不能直接訪問計算屬性,否則會引起循環調用;
- 在didSet中可以修改屬性的值,這個值將作為最終值(在willSet中無法修改);
方法
方法就是與某個特定類關聯的函數,其用法和前面介紹的函數並無二致,但是和ObjC相比,ObjC中的函數必須是C語言,而方法則必須是ObjC。此外其他語言中方法通常存在於類中,但是Swift中的方法除了在類中使用還可以在結構體、枚舉中使用。關於普通的方法這里不做過多贅述,用法和前面的函數區別也不大,這里主要看一下構造方法。
class Person { //定義屬性 var name:String var height:Double var age=0 //指定構造器方法,注意如果不編寫構造方法默認會自動創建一個無參構造方法 init(name:String,height:Double,age:Int){ self.name=name self.height=height self.age=age } //便利構造方法,通過調用指定構造方法、提供默認值來簡化構造方法實現 convenience init(name:String){ self.init(name:name,height:0.0,age:0) } //實例方法 func modifyInfoWithAge(age:Int,height:Double){ self.age=age self.height=height } //類型方法 class func showClassName(){ println("Class name is \"Person\"") } //析構方法,在對象被釋放時調用,類似於ObjC的dealloc,注意此函數沒有括號,沒有參數,無法直接調用 deinit{ println("deinit...") } } //通過便利構造方法創建對象 var p=Person(name: "kenshin")
- 除構造方法、析構方法外的其他方法的參數默認除了第一個參數是局部參數,從第二個參數開始既是局部參數又是外部參數(這種方式和ObjC的調用方式很類似,當然,可以使用“#”將第一個參數同時聲明為外部參數名,也可以使用“_”將其他參數設置為非外部參數名)。但是,對於函數,默認情況下只有默認參數既是局部參數又是外部參數,其他參數都是局部參數。
- 構造方法的所有參數默認情況下既是外部參數又是局部參數;
- Swift中的構造方法分為“指定構造方法”和“便利構造方法(convenience)”,指定構造方法是主要的構造方法,負責初始化所有存儲屬性,而便利構造方法是輔助構造方法,它通過調用指定構造方法並指定默認值的方式來簡化多個構造方法的定義,但是在一個類中至少有一個指定構造方法。
下標腳本
下標腳本是一種訪問集合的快捷方式,例如:var a:[string],我們經常使用a[0]、a[1]這種方式訪問a中的元素,0和1在這里就是一個索引,通過這種方式訪問或者設置集合中的元素在Swift中稱之為“下標腳本”(類似於C#中的索引器)。從定義形式上通過“subscript”關鍵字來定義一個下標腳本,很像方法的定義,但是在實現上通過getter、setter實現讀寫又類似於屬性。假設用Record表示一條記錄,其中有多列,下面示例中演示了如何使用下標腳本訪問並設置某一列的值。
class Record { //定義屬性,假設store是Record內部的存儲結構 var store:[String:String] init(data:[String:String]){ self.store=data } //下標腳本(注意也可以實現只有getter的只讀下標腳本) subscript(index:Int)->String{ get{ var key=sorted(Array(self.store.keys))[index] return self.store[key]! } set{ var key=sorted(Array(self.store.keys))[index] self.store[key]=newValue //newValue參數名可以像屬性一樣重新自定義 } } //下標腳本(重載) subscript(key:String)->String{ get{ return store[key]! } set{ store[key]=newValue } } } var r=Record(data:["name":"kenshin","sex":"male"]) println("r[0]=\(r[0])") //結果:r[0]=kenshin r["sex"]="female" println(r[1]) //結果:female
繼承
和ObjC一樣,Swift也是單繼承的(可以實現多個協議,此時協議放在后面),子類可以調用父類的屬性、方法,重寫父類的方法,添加屬性監視器,甚至可以將只讀屬性重寫成讀寫屬性。
class Person { var firstName:String,lastName:String var age:Int=0 var fullName:String{ get{ return firstName+" "+lastName } } init(firstName:String,lastName:String){ self.firstName=firstName self.lastName=lastName } func showMessage(){ println("name=\(fullName),age=\(age)") } //通過final聲明,子類無法重寫 final func sayHello(){ println("hello world.") } } class Student: Person { //重寫屬性監視器 override var firstName:String{ willSet{ println("oldValue=\(firstName)") } didSet{ println("newValue=\(firstName)") } } var score:Double //子類指定構造方法一定要調用父類構造方法 //並且必須在子類存儲屬性初始化之后調用父類構造方法 init(firstName:String,lastName:String, score:Double){ self.score=score super.init(firstName: firstName, lastName: lastName) } convenience init(){ self.init(firstName:"",lastName:"",score:0) } //將只讀屬性重寫成了可寫屬性 override var fullName:String{ get{ return super.fullName; } set{ let array=split(newValue, maxSplit: Int.max, allowEmptySlices: false, isSeparator: { $0 == "." }) if array.count == 2 { firstName=array[0] lastName=array[1] } } } //重寫方法 override func showMessage() { println("name=\(fullName),age=\(age),score=\(score)") } } var p=Student() p.firstName="kenshin"
在使用ObjC開發時init構造方法並不安全,首先無法保證init方法只調用一次,其次在init中不能訪問屬性。但是這些完全依靠文檔約定,編譯時並不能發現問題,出錯檢測是被動的。在Swift中構造方法(init)有了更為嚴格的規定:構造方法執行完之前必須保證所有存儲屬性都有值。這一點不僅在當前類中必須遵循,在整個繼承關系中也必須保證,因此就有了如下的規定:
- 子類的指定構造方法必須調用父類構造方法,並確保調用發生在子類存儲屬性初始化之后。而且指定構造方法不能調用同一個類中的其他指定構造方法;
- 便利構造方法必須調用同一個類中的其他指定構造方法(可以是指定構造方法或者便利構造方法),不能直接調用父類構造方法(用以保證最終以指定構造方法結束);
- 如果父類僅有一個無參構造方法(不管是否包含便利構造方法),子類的構造方法默認就會自動調用父類的無參構造方法(這種情況下可以不用手動調用);
- 常量屬性必須默認指定初始值或者在當前類的構造方法中初始化,不能在子類構造方法中初始化;
協議
協議是對實例行為的一種約束,和ObjC類似,在Swift中可以定義屬性和方法(ObjC中之所以能定義屬性是因為@property的本質就是setter、getter方法)。和其他語言不同的是Swift中的協議不僅限於類的實現,它同樣可以應用於枚舉、結構體(如果只想將一個協議應用於類,可以在定義協議時在后面添加class關鍵字來限制其應用范圍)。
protocol Named{ //定義一個實例屬性 var name:String { get set } //定義一個類型屬性 static var className:String { get } //定義構造方法 init(name:String) //定義一個實例方法 func showName() //定義一個類型方法 static func showClassName() } protocol Scored{ var score:Double { get set } } //Person遵循了Named協議 class Person:Named { //注意從Named協議中並不知道name是存儲屬性還是計算屬性,這里將其作為存儲屬性實現 var name:String var age:Int = 0 static var className:String{ return "Person" } //協議中規定的構造方法,必須使用required關鍵字聲明,除非類使用final修飾 required init(name:String){ self.name=name } //遵循showName方法 func showName() { println("name=\(name)") } //遵循showClassName方法 static func showClassName() { println("Class name is \"Person\"") } } //Student繼承於Person並且實現了Scored協議 class Student: Person,Scored { var score:Double=0.0 init(name:String, score:Double){ self.score = score super.init(name: name) } //由於上面自定義了構造方法則必須實現協議中規定的構造方法 required init(name: String) { super.init(name: name) } func test(){ println("\(self.name) is testing.") } } var p=Person(name: "Kenshin Cui") p.showName() //結果:name=Kenshin Cui println("className=\(Person.className)") //結果:className=Person Person.showClassName() //結果:Class name is "Person" p.age=28 var s:Named=Student(name: "Kaoru",score:100.0) //盡管這里將s聲明為Named類型,但是運行時仍然可以正確的解析(多態),但是注意此時編譯器並不知道s有test方法,所以此時調用test()會報錯 s.showName() //在下面的函數中要求參數stu必須實現兩個協議 func showMessage(stu:protocol<Named,Scored>){ println("name=\(stu.name),score=\(stu.score)") } var s2=Student(name: "Tom",score:99.0) showMessage(s2) //結果:name=Tom,age=99.0 //檢測協議 let b1 = s is Scored //判斷p是否遵循了Scored協議 if b1 { println("s has score property.") } //類型轉化 if let s3 = s as? Scored { //如果s轉化成了Scored類型則返回實例,否則為nil println("s3' score is \(s3.score)") //結果:s3' score is 100.0 } let s4 = s as! Scored //強制轉換,如果轉化失敗則報錯 println("s4' score is \(s4.score)") //結果:s4' score is 100.0
- 協議中雖然可以指定屬性的讀寫,但即使協議中規定屬性是只讀的但在使用時也可以將其實現成可讀寫的;
- Swift的協議中可以約定屬性是實例屬性還是類型屬性、是讀寫屬性還是只讀屬性,但是不能約束其是存儲屬性還是計算屬性;
- 協議中的類型屬性和類型方法使用static修飾而不是class(盡管對於類的實現中類型屬性、類型方法使用class修飾);
- 協議中約定的方法支持可變參數,但是不支持默認參數;
- 協議中約定的構造方法,在實現時如果不是final類則必須使用require修飾(以保證子類如果需要自定義構造方法則必須覆蓋父類實現的協議構造方法,如果子類不需要自定義構造方法則不必);
- 一個協議可以繼承於另外一個或多個協議,一個類只能繼承於一個類但可以實現多個協議;
- 協議本身就是一種類型,這也體現除了面向對象的多態特征,可以使用多個協議的合成來約束一個實例參數必須實現某幾個協議;
擴展
Swift中的擴展就類似於ObjC中的分類(事實上在其他高級語言中更多的稱之為擴展而非分類),但是它要比分類強大的多,它不僅可以擴展類還可以擴展協議、枚舉、結構體,另外擴展也不局限於擴展方法(實例方法或者類型方法),還可以擴展便利構造方法、計算屬性、下標腳本、
class Person { var firstName:String,lastName:String var age:Int=0 var fullName:String{ get{ return firstName+" "+lastName } } init(firstName:String,lastName:String){ self.firstName=firstName self.lastName=lastName } func showMessage(){ println("name=\(fullName),age=\(age)") } } extension Person{ //只能擴展便利構造方法,不能擴展指定構造方法 convenience init(){ self.init(firstName:"",lastName:"") } //只能擴展計算屬性,無法擴展存儲屬性 var personInfo:String{ return "firstName=\(firstName),lastName=\(lastName),age=\(age)"; } //擴展實例方法 func sayHello(){ println("hello world.") } //嵌套類型 enum SkinColor{ case Yellow,White,Black } //擴展類型方法 static func skin()->[SkinColor]{ return [.Yellow,.White,.Black] } } var p=Person() p.firstName="Kenshin" p.lastName="Cui" p.age=28 println(p.personInfo) //結果:firstName=Kenshin,lastName=Cui,age=28 p.sayHello() //結果:hello world. Person.skin()
枚舉和結構體
結構體
結構體和類是構造復雜數據類型時常用的構造體,在其他高級語言中結構體相比於類要簡單的多(在結構體內部僅僅能定義一些簡單成員),但是在Swift中結構體和類的關系要緊密的多,這也是為什么將結構體放到后面來說的原因。Swift中的結構體可以定義屬性、方法、下標腳本、構造方法,支持擴展,可以實現協議等等,很多類可以實現的功能結構體都能實現,但是結構體和類有着本質區別:類是引用類型,結構體是值類型。
struct Person { var firstName:String var lastName:String var fullName:String{ return firstName + " " + lastName } var age:Int=0 //構造函數,如果定義了構造方法則不會再自動生成默認構造函數 // init(firstName:String,lastName:String){ // self.firstName=firstName // self.lastName=lastName // } func showMessage(){ println("firstName=\(firstName),lastName=\(lastName),age=\(age)") } //注意對於類中聲明類型方法使用關鍵字class修飾,但結構體里使用static修飾 static func showStructName(){ println("Struct name is \"Person\"") } } //注意所有結構體默認生成一個全員逐一構造函數,一旦自定義構造方法,這個默認構造方法將不會自動生成 var p=Person(firstName: "Kenshin", lastName: "Cui", age: 28) println(p.fullName) //結果:Kenshin Cui p.showMessage() //結果:firstName "Kenshin", lastName "Cui", age 28 Person.showStructName() //結果:Struct name is "Person" //由於結構體(包括枚舉)是值類型所以賦值、參數傳遞時值會被拷貝(所以下面的實例中p2修改后p並未修改,但是如果是類則情況不同) var p2 = p p2.firstName = "Tom" println(p2.fullName) //結果:Tom Cui println(p.fullName) //結果:Kenshin Cui
- 默認情況下如果不自定義構造函數那么將自動生成一個無參構造函數和一個全員的逐一構造函數;
- 由於結構體是值類型,所以它雖然有構造函數但是沒有析構函數,內存釋放系統自動管理不需要開發人員過多關注;
- 類的類型方法使用class修飾(以便子類可以重寫),而結構體、枚舉的類型方法使用static修飾(補充:類方法也可以使用static修飾,但是不是類型方法而是靜態方法;另外類的存儲屬性如果是類型屬性使用static修飾,而類中的計算屬性如果是類型屬性使用class修飾以便可以被子類重寫;換句話說class作為“類型范圍作用域”來理解時只有在類中定義類型方法或者類型計算屬性時使用,其他情況使用static修飾[包括結構體、枚舉、協議和類型存儲屬性]);
類的實例通常稱之為“對象”,而在Swift中結構體也可以有實例,因此對於很多二者都可以實現的功能,在文中稱之為實例而沒有使用對象的概念。
枚舉
在其他語言中枚舉本質就是一個整形,只是將這組相關的值組織起來並指定一個有意義的名稱。但是在Swift中枚舉不強調一個枚舉成員必須對應一個整形值(當然如果有必要仍然可以指定),並且枚舉類型的可以是整形、浮點型、字符、字符串。首先看一下枚舉的基本使用:
//注意Swift中的枚舉默認並沒有對應的整形值,case用來定義一行新的成員,也可以將多個值定義到同一行使用逗號分隔,例如:case Spring,Summer,Autumn,Winter enum Season{ case Spring case Summer case Autumn case Winter } var s=Season.Spring //一旦確定了枚舉類型,賦值時可以去掉類型實現簡寫 s = .Summer switch s { case .Spring: //由於Swift的自動推斷,這里仍然可以不指明類型 println("spring") case .Summer: println("summer") case .Autumn: println("autumn") default: println("winter") }
事實上Swift中也可以指定一個值和枚舉成員對應,就像其他語言一樣(通常其他語言的枚舉默認就是整形),但是Swift又不局限於整形,它可以是整形、浮點型、字符串、字符,但是原始值必須是一種固定類型而不能存儲多個不同的類型,同時如果原始值為整形則會像其他語言一樣默認會自動遞增。
//指定原始值(這里定義成了整形) enum Season:Int{ case Spring=10 //其他值會默認遞增,例如Summer默認為11,如果此處也不指定值會從0開始依次遞增 case Summer case Autumn case Winter } var summer=Season.Summer //使用rawValue訪問原始值 println("summer=\(summer),rawValue=\(summer.rawValue)") //通過原始值創建枚舉類型,但是注意它是一個可選類型 var autumn=Season(rawValue: 12) //可選類型綁定 if let newAutumn=autumn{ println("summer=\(newAutumn),rawValue=\(newAutumn.rawValue)") }
如果一個枚舉類型能夠和一些其他類型的數據一起存儲起來往往會很有用,因為這可以讓你存儲枚舉類型之外的信息(類似於其他語言中對象的tag屬性,但是又多了靈活性),這在其他語言幾乎是不可能實現的,但是在Swift中卻可以做到,這在Swift中稱為枚舉類型相關值。要注意的是相關值並不是原始值,原始值需要事先存儲並且只能是同一種類型,但是相關值只有創建一個基於枚舉的變量或者常量時才會指定,並且類型可以不同(原始值更像其他語言的枚舉類型)。
//相關值 enum Color{ case RGB(String) //注意為了方便演示這里沒有定義成三個Int類型(例如: RGB(Int,Int,Int))而使用16進制字符串形式 case CMYK(Float,Float,Float,Float) case HSB(Int,Int,Int) } var red=Color.RGB("#FF0000") var green=Color.CMYK(0.61, 0.0, 1.0, 0.0) var blue=Color.HSB(240, 100, 100) switch red { case .RGB(let colorStr): println("colorStr=\(colorStr)") case let .CMYK(c,m,y,k): println("c=\(c),m=\(m),y=\(y),k=\(k)") case let .HSB(h,s,b): println("h=\(h),s=\(s),b=\(b)") }
上面提到其實枚舉也有一些類型和結構體的特性,例如計算屬性(包括類型屬性,枚舉只能定義計算屬性不能定義存儲屬性,存儲屬性只能應用於類和結構體)、構造方法(其實上面使用原始值創建枚舉的例子就是一個構造方法)、方法(實例方法、類型方法)、下標腳本 。
enum Season:Int{ case Spring=0 ,Summer,Autumn,Winter //定義計算屬性 var tag:Int{ return self.rawValue } //類型屬性 static var enumName:String{ return "Season" } // //定義構造方法,注意在枚舉的構造函數中則必須保證self有值(正如類的構造方法必須保證其存儲屬性有值一樣) // init(prefix:String){ // switch prefix.lowercaseString { // case "sp": // self = .Spring // case "su": // self = .Summer // case "au": // self = .Autumn // default: // self = .Winter // } // } //其實上面的構造器有些不合理,那就是default就是Winter,事實上這類構造器可能傳任何參數,此時可以使用可失敗構造函數來解決 //可失敗構造函數返回nil(盡管Swift中構造函數是不返回值的,但是此時約定返回nil代表構造失敗) init?(prefix:String){ switch prefix.lowercaseString { case "sp": self = .Spring case "su": self = .Summer case "au": self = .Autumn case "wi": self = .Winter default: return nil } } //定義實例方法 func showMessage(){ println("rowValue=\(self.rawValue)") } //定義類型方法 static func showEnumName(){ println("Enum name is \"Season\"") } } var summer=Season.Summer println(summer.tag) //結果:1 println(Season.enumName) //結果:Season Season.showEnumName() //結果:Enum name is "Season" summer.showMessage() //結果:rowValue=1 if let spring = Season(prefix: "au") { //可選綁定,構造函數返回值可能為nil println(spring.tag) //結果:2 }
之所以沒有將枚舉、結構體放到上面的數據類型部分介紹一方面Swift中的枚舉、結構體和其他語言中有較大差別,另一方面是因為這個部分的介紹要用到前面的知識。
泛型
泛型可以讓你根據需求使用一種抽象類型來完成代碼定義,在使用時才真正知道其具體類型。這樣一來就好像在定義時使用一個占位符做一個模板,實際調用時再進行模板套用,所以在C++中也稱為“模板”。泛型在Swift中被廣泛應用,上面介紹的Array<>、Dictionary<>事實上都是泛型的應用。通過下面的例子簡單看一下泛型參數和泛型類型的使用。
/*泛型參數*/ //添加了約束條件的泛型(此時T必須實現Equatable協議) func isEqual<T:Equatable>(a:T,b:T)->Bool{ return a == b } var a:Int=1,b:Int=2 println(isEqual(a,b)) //結果:false var c:String="abc",d:String="abc" println(isEqual(c,d)) //結果:true /*泛型類型*/ struct Stack<T> { var store:[T]=[] //在結構體、枚舉中修改其變量需要使用mutating修飾(注意類不需要) mutating func push(item:T){ store.append(item) } mutating func pop()->T{ return store.removeLast() } } var s = Stack<Int>() s.push(1) let t = s.pop() println("t=\(t)") //結果:t=1 //擴展泛型類型 extension Stack{ var top:T?{ return store.last } } s.push(2) println(s.top!) //結果:2
上面演示了泛型結構體用法,其實類同樣是類似的,這里就不在贅述了,但是如果遇到泛型協議怎么辦呢?假設Stack必須遵循一個Stackable協議,此時就必須在協議中引入一個關聯類型來解決。
protocol Stackable{ //聲明一個關聯類型 typealias ItemType mutating func push(item:ItemType) mutating func pop()->ItemType; } struct Stack:Stackable{ var store:[T]=[] mutating func push(item:T){ store.append(item) } mutating func pop()->T{ return store.removeLast() } } var s = Stack() s.push("hello") s.push("world") let t = s.pop() println("t=\(t)") //結果:t=world