Swift是蘋果2014年推出的全新的編程語言,它繼承了C語言、ObjC的特性,且克服了C語言的兼容性問題。Swift發展過程當中不只保留了ObjC不少語法特性,它也借鑑了多種現代化語言的特色,在其中你能夠看到C#、Java、Javascript、Python等多種語言的影子。同時在2015年的WWDC上蘋果還宣佈Swift的新版本Swift2.0,並宣佈稍後Swift即將開源,除了支持iOS、OS X以外還將支持linux。linux
本文將繼續iOS開發系列教程,假設讀者已經有了其餘語言基礎(強烈建議初學者從本系列第一章開始閱讀,若是您但願從Swift學起,那麼推薦你首先閱讀蘋果官方電子書《the swift programming language》),不會從零基礎一點點剖析這門語言的語法,旨在幫助你們快速從ObjC快速過分到Swift開發中。即使如此,要儘量全面的介紹Swift的語法特色也不是一件容易的事情,所以本文將採用較長的篇幅進行介紹。spring
建立一個命令行程序以下:編程
import Foundation /** * Swift沒有main函數,默認從top level code的上方開始自上而下執行(所以不能有多個top level代碼) */ println("Hello, World!")
從上面的代碼能夠看出:swift
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提供了三種集合類型:數組Array、集合Set、字典Dictionary。和ObjC不一樣的是,因爲Swift的強類型,集合中的元素必須是同一類型,而不能像ObjC同樣能夠存儲任何對象類型,而且注意Swift中的集合是值類型而非引用類型(事實上包括String、結構體struct、枚舉enum都是值類型)。閉包
首先看一下Swift中的數組:app
//聲明數組的時候必須肯定其類型,下面使用[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。ide
//經過字面量進行字典初始化,注意等價於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同樣由單獨的數據類型來控制的,而是經過變量和常量來控制,這一點和其餘高級語言比較相似。
在開發過程當中有時候會但願臨時組織一個數據類型,此時可使用一個結構體或者類,可是因爲這個類型並無那麼複雜,若是定義起來又比較麻煩,此時能夠考慮使用元組。
/** * 元組的基本用法 */ 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類型)
既然可選類型有可能有值,也可能沒有值那麼每每有時候就須要判斷。可使用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): //注意這裏有可能和第1、2、三個條件重合,可是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函數並無傳遞任何參數名,由於num一、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中的屬性分爲兩種:存儲屬性(用於類、結構體)和計算屬性(用於類、結構體、枚舉),而且在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()
須要提醒你們的是:
從上面的例子中不難區分存儲屬性和計算屬性,計算屬性一般會有一個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) }
方法就是與某個特定類關聯的函數,其用法和前面介紹的函數並沒有二致,可是和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")
下標腳本是一種訪問集合的快捷方式,例如: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中的擴展就相似於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
類的實例一般稱之爲「對象」,而在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