




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)
文檔簡介
IOS編程基礎(chǔ)swiftxcode和cocoa入門指南目錄\h第一部分語言\h第1章Swift架構(gòu)縱覽\h1.1基礎(chǔ)\h1.2萬物皆對象\h1.3對象類型的3種風(fēng)格\h1.4變量\h1.5函數(shù)\h1.6Swift文件的結(jié)構(gòu)\h1.7作用域與生命周期\h1.8對象成員\h1.9命名空間\h1.10模塊\h1.11實例\h1.12為何使用實例\h1.13self\h1.14隱私\h1.15設(shè)計\h第2章函數(shù)\h2.1函數(shù)參數(shù)與返回值\h2.2外部參數(shù)名\h2.3重載\h2.4默認(rèn)參數(shù)值\h2.5可變參數(shù)\h2.6可忽略參數(shù)\h2.7可修改參數(shù)\h2.8函數(shù)中的函數(shù)\h2.9遞歸\h2.10將函數(shù)作為值\h2.11匿名函數(shù)\h2.12定義與調(diào)用\h2.13閉包\h2.14柯里化函數(shù)\h第3章變量與簡單類型\h3.1變量作用域與生命周期\h3.2變量聲明\h3.3計算初始化器\h3.4計算變量\h3.5setter觀察者\h3.6延遲初始化\h3.7內(nèi)建簡單類型\h第4章對象類型\h4.1對象類型聲明與特性\h4.2枚舉\h4.3結(jié)構(gòu)體\h4.4類\h4.5多態(tài)\h4.6類型轉(zhuǎn)換\h4.7類型引用\h4.8協(xié)議\h4.9泛型\h4.10擴展\h4.11保護類型\h4.12集合類型\h第5章流程控制與其他\h5.1流程控制\h5.2運算符\h5.3隱私性\h5.4內(nèi)省\h5.5內(nèi)存管理\h第二部分IDE\h第6章Xcode項目剖析\h6.1新建項目\h6.2項目窗口\h6.3項目文件及其依賴\h6.4目標(biāo)\h6.5從項目到運行應(yīng)用\h6.6對項目內(nèi)容進行重命名\h第7章nib管理\h7.1nib編輯器界面概覽\h7.2nib加載\h7.3連接\h7.4nib實例的其他配置\h第8章文檔\h8.1文檔窗口\h8.2類文檔頁面\h8.3示例代碼\h8.4快速幫助\h8.5符號\h8.6頭文件\h8.7互聯(lián)網(wǎng)資源\h第9章項目的生命周期\h9.1設(shè)備架構(gòu)與條件代碼\h9.2版本控制\h9.3編輯與代碼導(dǎo)航\h9.4在模擬器中運行\(zhòng)h9.5調(diào)試\h9.6測試\h9.7清理\h9.8在設(shè)備中運行\(zhòng)h9.9分析\h9.10本地化\h9.11歸檔與發(fā)布\h9.12AdHoc發(fā)布\h9.13最后的準(zhǔn)備\h9.14向AppStore提交應(yīng)用\h第三部分Cocoa\h第10章Cocoa類\h10.1子類化\h10.2類別與擴展\h10.3協(xié)議\h10.4Foundation類精講\h10.5訪問器、屬性與鍵值編碼\h10.6NSObject揭秘\h第11章Cocoa事件\h11.1為何使用事件\h11.2子類化\h11.3通知\h11.4委托\(zhòng)h11.5數(shù)據(jù)源\h11.6動作\h11.7響應(yīng)器鏈\h11.8鍵值觀測\h11.9事件泥潭\h11.10延遲執(zhí)行\(zhòng)h第12章內(nèi)存管理\h12.1Cocoa內(nèi)存管理的原理\h12.2Cocoa內(nèi)存管理的原則\h12.3ARC及其作用\h12.4Cocoa對象管理內(nèi)存的方式\h12.5自動釋放池\h12.6實例屬性的內(nèi)存管理\h12.7保持循環(huán)與弱引用\h12.8值得注意的內(nèi)存管理情況\h12.9nib加載與內(nèi)存管理\h12.10CFTypeRefs的內(nèi)存管理\h12.11屬性的內(nèi)存管理策略\h12.12調(diào)試內(nèi)存管理的錯誤\h第13章對象間通信\h13.1實例化可見性\h13.2關(guān)系可見性\h13.3全局可見性\h13.4通知與KVO\h13.5模型-視圖-控制器\h附錄AC、Objective-C與Swift注:原文檔電子版,非掃描,需要的請下載本文檔后留言謝謝。第一部分語言本部分將會從頭開始介紹Swift這門語言,整個介紹是非常嚴(yán)密且有序的。通過本部分的介紹,你將熟悉并適應(yīng)Swift,從而能夠進行實際的編程工作?!さ?章從概念與實踐上介紹Swift程序的結(jié)構(gòu)。你將學(xué)習(xí)到Swift代碼文件的組織方式,以及面向?qū)ο蟮腟wift語言最重要的底層概念:變量與函數(shù)、作用域與命名空間,以及對象類型與實例?!さ?章將會介紹Swift函數(shù)。我們首先會從函數(shù)的聲明與調(diào)用方式基礎(chǔ)開始;接下來介紹參數(shù)——外部參數(shù)名、默認(rèn)參數(shù)與可變參數(shù)。然后將會深入介紹Swift函數(shù)的功能,同時還會介紹函數(shù)中的函數(shù)、作為一等值的函數(shù)、匿名函數(shù)、作為閉包的函數(shù),以及柯里化函數(shù)。·第3章首先會介紹Swift變量——變量的作用域與生命周期、如何聲明與初始化變量,以及一些重要的Swift特性,如計算變量與setter觀察者等。然后會介紹一些重要的內(nèi)建Swift類型,包括布爾、數(shù)字、字符串、范圍、元組與Optional。·第4章將會介紹Swift對象類型——類、結(jié)構(gòu)體與枚舉。本章將會介紹這3種對象類型的工作方式,如何聲明、實例化與使用它們。接下來會介紹多態(tài)與類型轉(zhuǎn)換、協(xié)議、泛型及擴展。本章最后將會介紹Swift的保護類型(如AnyObject)與集合類型(Array、Dictionary與Set,還包括Swift2.0新引入的用于表示位掩碼的選項集合)?!さ?章內(nèi)容比較龐雜。我們首先會介紹用于分支、循環(huán)與跳轉(zhuǎn)的Swift流程控制結(jié)構(gòu),包括Swift2.0的一個新特性——錯誤處理。接下來將會介紹如何創(chuàng)建自己的Swift運算符。本章最后將會介紹Swift訪問控制(私有性)、內(nèi)省機制(反射)與內(nèi)存管理。第1章Swift架構(gòu)縱覽首先對Swift語言的構(gòu)建方式有個總體認(rèn)識并了解基于Swift的iOS程序的樣子是很有用的。本章將會介紹Swift語言的整體架構(gòu)與本質(zhì)特性,后續(xù)章節(jié)將會對細(xì)節(jié)進行詳盡的介紹。1.1基礎(chǔ)一個完整的Swift命令是一條語句。一個Swift文本文件包含了多行文本。換行符是有意義的。一個程序的典型布局就是一行一條語句:print("hello")
print("world")
(print命令會在Xcode控制臺提供即時反饋。)可以將多條語句放到一行,不過這就需要在語句間加上分號:print("hello");print("world")
可以將分號放到語句的末尾,也可以在一行上單獨放置一個分號,不過沒人這么做(除了習(xí)慣原因之外,因為C和Objective-C要求使用分號):print("hello");
print("world");
與之相反,單條語句可以放到多行,這樣做可以防止一行中出現(xiàn)過長的語句。不過在這樣做的時候要注意語句的位置,以免對Swift造成困擾。比如,左圓括號后面就是個不錯的位置:print(
"world")
一行中雙斜線后面的內(nèi)容會被當(dāng)作注釋(即所謂的C++風(fēng)格的注釋):print("world")//thisisacomment,soSwiftignoresit
還可以將注釋放到/*...*/中,就像C一樣。與C不同,Swift風(fēng)格的注釋是可以嵌套的。Swift中的很多構(gòu)建塊都會將花括號用作分隔符:classDog{
funcbark(){
print("woof")
}
}
根據(jù)約定,花括號中的內(nèi)容由換行符開始,并且通過縮進增強可讀性,如上述代碼所示。Xcode會幫助你應(yīng)用該約定,不過實際情況卻是Swift并不在意這些,像下面這樣的布局也是合法的(有時也更加便捷):classDog{funcbark(){print("woof")}}
Swift是一門編譯型語言。這意味著代碼必須要先構(gòu)建(通過編譯器,由文本轉(zhuǎn)換為計算機可以理解的某種底層形式),然后再執(zhí)行并根據(jù)指令完成任務(wù)。Swift編譯器非常嚴(yán)格;在編寫程序時,你經(jīng)常會構(gòu)建并運行,不過你會發(fā)現(xiàn)第一次甚至都無法構(gòu)建成功,原因就在于編譯器會識別出一些錯誤,如果想讓代碼運行,你就需要修復(fù)這些問題。有時候,編譯器會給出一些警告;這時代碼可以運行,不過一般情況下,你應(yīng)該有所警戒并修復(fù)編譯器報出的警告。編譯器的嚴(yán)格性是Swift最強大的優(yōu)勢之一,可以在代碼開始運行前提供最大程度的審計正確性。Swift編譯器的錯誤與警告消息涵蓋范圍非常廣,從洞察性極強到一般性提示再到完全誤導(dǎo)人。很多時候,你知道某行代碼有問題,不過Swift編譯器卻不會清晰地告訴你什么地方出錯了,甚至連是哪行都不會告訴你。對于這些情況,我的建議是將可能有問題的代碼行放到簡單的代碼塊中,直到發(fā)現(xiàn)問題所在位置。雖然提示消息有時起不到幫助作用,不過請保持與編譯器的親密接觸吧。請記住,雖然編譯器有時無法準(zhǔn)確地進行描述,但它知道的一定比你多。1.2萬物皆對象在Swift中,萬物皆對象。這與各種現(xiàn)代化面向?qū)ο笳Z言是一致的,不過這表示什么意思呢?這取決于你所理解的對象,那“萬物”又是什么意思呢?首先來定義一下對象,大概來說,對象指的是你可以向其發(fā)送消息的某個實體。一般來說,消息指的是一種命令指令。比如,你可以向一只狗發(fā)送命令:吼叫!坐下!在這種情況下,這些短語就是消息,而狗則是你向其發(fā)送消息的對象。在Swift中,消息發(fā)送語法采用的是點符號。首先是對象,然后是一個點,接下來是消息(有些消息后會跟圓括號,不過現(xiàn)在請不用管它,消息發(fā)送的完整語法是接下來將會詳細(xì)介紹的一個主題)。如下是有效的Swift語法:fido.bark()
rover.sit()
萬物皆對象的想法表明即便是“原生”的語言實體都可以接收消息。比如,1。它是個數(shù)字字面值,除此之外別無其他。如果你曾經(jīng)使用過其他編程語言,那么在Swift中像下面這樣做就不會覺得有什么奇怪的了:letsum=1+2
不過,讓人奇怪的是1后面可以跟著一個點和一條消息。這在Swift中是合法且有意義的(現(xiàn)在可以不用管其含義):letx=1.successor()
還可以更進一步?;氐街澳莻€1+2代碼示例上來。實際上這是一種語法技巧,是表示并隱藏實際情況的一種便捷方式。就好像1實際上是個對象,+是一條消息;不過這條消息使用了特殊的語法(運算符語法)。在Swift中,每個名詞都是一個對象,每個動詞都是一條消息。也許判別Swift中的某個實體是不是對象的根本標(biāo)準(zhǔn)在于你能否修改它。在Swift中,對象類型是可以擴展的,這意味著你可以定義該類型下自己的消息。比如,正常情況下你無法向數(shù)字發(fā)送sayHello消息。不過你可以修改數(shù)字類型使得希望達成:extensionInt{
funcsayHello(){
print("Hello,I'm\(self)")
}
}
1.sayHello()//outputs:"Hello,I'm1"
這樣,情況就發(fā)生了變化。在Swift中,1是個對象。在其他一些語言(如Objective-C)中顯然不是這樣的;1是個原生或標(biāo)量內(nèi)建數(shù)據(jù)類型。區(qū)別在于,當(dāng)我們說萬物皆對象時,一方面指的是對象類型,另一方面指的是標(biāo)量類型。Swift中是不存在標(biāo)量的;所有類型最終都是對象類型。這就是“萬物皆對象”的真正含義。1.3對象類型的3種風(fēng)格如果了解Objective-C或是其他一些面向?qū)ο笳Z言,你可能好奇于Swift中的對象1是個什么概念。在很多語言(如Objective-C)中,對象指的是一個類或一個類的實例。Swift擁有類與實例,你可以向其發(fā)送消息;不過在Swift中,1既不是類也不是實例:它是個結(jié)構(gòu)體(struct)。Swift還有另外一種可以接收消息的實體,叫作枚舉。因此,Swift擁有3種對象類型:類、結(jié)構(gòu)體與枚舉。我喜歡稱它們?yōu)閷ο箢愋偷?種風(fēng)格。后續(xù)內(nèi)容將會介紹它們之間的差別。不過它們都是確定的對象類型,彼此之間的相似性要遠(yuǎn)遠(yuǎn)高于差異性?,F(xiàn)在,只需知道這3種風(fēng)格的存在即可。(如果了解Objective-C,那么你會驚訝于Swift中的結(jié)構(gòu)體與枚舉竟然都是對象類型,不過它們并非對象。特別地,Swift中的結(jié)構(gòu)體要比Objective-C的結(jié)構(gòu)體更加重要,使用更為廣泛。Swift與Objective-C對待結(jié)構(gòu)體和枚舉的不同方式在Cocoa中顯得尤為重要。)1.4變量變量指的是對象的名字。從技術(shù)角度來說,它指向一個對象;它是一個對象引用。從非技術(shù)角度來看,你可以將其看作存放對象的一個盒子。對象可能會發(fā)生變化,或是盒子中的對象被其他對象所替換,但名字卻不會發(fā)生變化。在Swift中,不存在沒有名字的變量,所有變量都必須要聲明。如果需要為某個東西起個名字,那么你要說“我在創(chuàng)建一個名字”。可以通過兩個關(guān)鍵字實現(xiàn)這一點:let或是var。在Swift中,聲明通常會伴隨著初始化一起——使用等號為變量賦值,并作為聲明的一部分。下面這些都是變量聲明(與初始化):letone=1
vartwo=2
如果名字存在,那么你就可以使用它了。比如,我們可以將two中的值修改為one中的:letone=1
vartwo=2
two=one
上面最后一行代碼使用了前兩行所聲明的名字one與two:等號右側(cè)的名字one僅僅用于引用盒子中的值(即1);不過,等號左側(cè)的名字two則用于替換掉盒子中的值。這種語句(變量名位于等號左側(cè))叫作賦值,等號叫作賦值運算符。等號并不是相等性斷言,這與數(shù)學(xué)公式中的等號不同;它是一個命令,表示“獲取右側(cè)的值,然后使用它替換掉左側(cè)的值”。變量的這兩種聲明方式是不同的,通過let聲明的名字是不能替換掉其對象的。通過let聲明的變量是個常量;其值只能被賦予一次并且不再變化。如下代碼是無法編譯通過的:letone=1
vartwo=2
one=two//compileerror
可以通過var聲明一個名字來實現(xiàn)最大的靈活性,不過如果知道永遠(yuǎn)不會改變變量的初始值,那么最好使用let,這樣Swift在處理時效率會更高;事實上,如果本可以使用let,但你卻使用了var,那么Swift編譯器就會提示你,并且可以幫你修改。變量也是有類型的,其類型是在變量聲明時創(chuàng)建的,而且永遠(yuǎn)不會改變。比如,如下代碼是無法編譯通過的:vartwo=2
two="hello"http://compileerror
一旦聲明two并將其初始化為2,那么它就是一個數(shù)字了(確切地說是一個Int),而且一直都將如此。你可以將其替換為1,因為1也是個Int,但不能將其值替換為“hello”,因為"hello"是個字符串(確切地說是一個String),而String并非Int。變量有自己的生命——更準(zhǔn)確地說是有自己的生命周期。只要變量存在,那么它就會一直保存其值。這樣,變量不僅是一種便于命名的手段,還是一種保存值的方式。稍后將會對此做詳細(xì)介紹。根據(jù)約定,如String或Int(或Dog、Cat)等類型名要以大寫字母開頭;變量名則以小寫字母開頭,請不要違背該約定。如果違背了,那么你的代碼雖然還是可以編譯通過并正常運行,但其他人卻不太容易理解。1.5函數(shù)如fido.bark()或one=two這樣的可執(zhí)行代碼不能隨意放置。一般來說,這樣的代碼必須要位于函數(shù)體中。函數(shù)由一系列代碼構(gòu)成,并且可以運行。一般來說,函數(shù)有一個名字,這個名字是通過函數(shù)聲明得到的。函數(shù)聲明語法的細(xì)節(jié)將會在后面進行詳細(xì)介紹,先來看一個示例:funcgo(){
letone=1
vartwo=2
two=one
}
上述代碼描述了要做的一系列事情——聲明one、聲明two,將one值賦給two——并且給這一系列代碼賦予一個名字go;不過該代碼序列并不會執(zhí)行。只有在調(diào)用函數(shù)時,該代碼序列才會執(zhí)行。我們可以在其他地方這樣執(zhí)行:go()
這會向go函數(shù)發(fā)出一個命令,這樣go函數(shù)才會真正運行起來。重申一次,命令本身是可執(zhí)行代碼,因此它不能位于自身當(dāng)中。它可以位于不同的函數(shù)體中:funcdoGo(){
go()
}
請等一下!這么做有點奇怪。上面是一個函數(shù)聲明;要想運行該函數(shù),你需要調(diào)用doGo,它才是可執(zhí)行代碼。這看起來像是無窮無盡的循環(huán)一樣;似乎代碼永遠(yuǎn)都不會運行。如果所有代碼都必須位于一個函數(shù)中,那么誰來讓函數(shù)運行呢?初始動力一定來自于其他地方。幸好,在實際情況下,這個問題并不會出現(xiàn)。記住,你的最終目標(biāo)是編寫iOS應(yīng)用。因此,應(yīng)用會運行在iOS設(shè)備(或是模擬器)中,由運行時調(diào)用,而運行時已經(jīng)知道該調(diào)用哪些函數(shù)了。首先編寫一些特殊函數(shù),這些函數(shù)會由運行時本身來調(diào)用。這樣,應(yīng)用就可以啟動了,并且可以將函數(shù)放到運行時在某些時刻會調(diào)用的地方——比如,當(dāng)應(yīng)用啟動時,或是當(dāng)用戶輕拍應(yīng)用界面上的按鈕時。Swift還有一個特殊的規(guī)則,那就是名為main.swift的文件可以在頂層包含可執(zhí)行代碼,這些代碼位于任何函數(shù)體的外部,當(dāng)程序運行時真正執(zhí)行的其實就是這些代碼。你可以通過main.swift文件來構(gòu)建應(yīng)用,不過一般來說沒必要這么做。1.6Swift文件的結(jié)構(gòu)Swift程序可以包含一個或多個文件。在Swift中,文件是一個有意義的單元,它通過一些明確的規(guī)則來確定Swift代碼的結(jié)構(gòu)(假設(shè)不在main.swift文件中)。只有某些內(nèi)容可以位于Swift文件的頂層部分,主要有如下幾個部分模塊import語句模塊是比文件更高層的單元。一個模塊可以包含多個文件,在Swift中,模塊中的文件能夠自動看到彼此;但如果沒有import語句,那么一個模塊是看不到其他模塊的。比如,想想如何在iOS程序中使用Cocoa:文件的第1行會使用importUIKit。變量聲明聲明在文件頂層的變量叫作全局變量:只要程序還在運行,它就會一直存在。函數(shù)聲明聲明在文件頂層的函數(shù)叫作全局函數(shù):所有代碼都能看到并調(diào)用它,無需向任何對象發(fā)送消息。對象類型聲明指的是類、結(jié)構(gòu)體或是枚舉的聲明。比如,下面是一個合法的Swift文件,包含(只是為了說明)一個import語句、一個變量聲明、一個函數(shù)聲明、一個類聲明、一個結(jié)構(gòu)體聲明,以及一個枚舉聲明。importUIKit
varone=1
funcchangeOne(){
}
classManny{
}
structMoe{
}
enumJack{
}
這個示例本身并沒有什么意義,不過請記住,我們的目標(biāo)是探求語言的組成部分與文件的結(jié)構(gòu),該示例僅僅是為了演示。此外,示例中每一部分的花括號中還可以加入變量聲明、函數(shù)聲明與對象類型聲明。事實上,任何結(jié)構(gòu)化的花括號中都可以包含這些聲明。比如,關(guān)鍵字if(Swift流程控制的一部分,第5章將會介紹)后面會跟著結(jié)構(gòu)化的花括號,它們可以包含變量聲明、函數(shù)聲明與對象類型聲明。如下代碼雖然毫無意義,但卻是合法的:funcsilly(){
iftrue{
classCat{}
varone=1
one=one+1
}
}
你會發(fā)現(xiàn)我并沒有說可執(zhí)行代碼可以位于文件的頂部,因為這是不行的。只有函數(shù)體可以包含可執(zhí)行代碼。函數(shù)自身可以包含任意深度的可執(zhí)行代碼;在上述代碼中,one=one+1這一行可執(zhí)行代碼是合法的,因為它位于if結(jié)構(gòu)中,而該if結(jié)構(gòu)又位于函數(shù)體中。但one=one+1這一行不能位于文件頂層,也不能位于Cat聲明的花括號中。示例1-1是一個合法的Swift文件,其中概要地展示了其結(jié)構(gòu)的各種可能性。(請忽略枚舉聲明中關(guān)于Jack的name變量聲明;枚舉中的頂層變量有一些特殊規(guī)則,稍后將會介紹。)示例1-1:合法的Swift文件的概要結(jié)構(gòu)importUIKit
varone=1
funcchangeOne(){
lettwo=2
funcsayTwo(){
print(two)
}
classKlass{}
structStruct{}
enumEnum{}
one=two
}
classManny{
letname="manny"
funcsayName(){
print(name)
}
classKlass{}
structStruct{}
enumEnum{}
}
structMoe{
letname="moe"
funcsayName(){
print(name)
}
classKlass{}
structStruct{}
enumEnum{}
}
enumJack{
varname:String{
return"jack"
}
funcsayName(){
print(name)
}
classKlass{}
structStruct{}
enumEnum{}
}
顯然,可以一直遞歸下去:類聲明中可以包含類聲明,里面的類聲明中還可以包含類聲明,以此類推。不過這么做毫無意義。1.7作用域與生命周期在Swift程序中,一切事物都有作用域。這指的是它們會被其他事物看到的能力。一個事物可以嵌套在其他事物中,形成一個嵌套的層次結(jié)構(gòu)。規(guī)則是一個事物可以看到與自己相同層次或是更高層次的事物。層次有:·模塊是一個作用域?!の募且粋€作用域?!ο舐暶魇且粋€作用域?!せɡㄌ柺且粋€作用域。在聲明某個事物時,它實際上是在該層級的某個層次上進行的聲明。它在層級中的位置(即作用域)決定了是否能被其他事物看到。再來看看示例1-1。在Manny的聲明中是個name變量聲明和一個sayName函數(shù)聲明;sayName花括號中的代碼可以看到更高層次中花括號之外的內(nèi)容,因此它可以看到name變量。與之類似,changeOne函數(shù)體中的代碼可以看到文件頂層所聲明的one變量;實際上,該文件中的一切事物都可以看到文件頂層所聲明的one變量。作用域是共享信息的一種非常重要的手段。聲明在Manny中的兩個不同函數(shù)都會看到在Manny頂層所聲明的name變量。Jack中的代碼與Moe中的代碼都可以看到聲明在文件頂層的one。事物還有生命周期,這與其作用域是相關(guān)的。一個事物的生命周期與其外部作用域的生命周期是一致的。因此,在示例1-1中,變量one的生命周期就與文件一樣,只要程序處于運行狀態(tài),one就是有效的。它是全局且持久的。不過,聲明在Manny頂層的變量name只有在Manny存在時才存在(稍后將會對此做出說明)。聲明在更深層次中的事物的生命周期會更短;比如,看看下面這段代碼:funcsilly(){
iftrue{
classCat{}
varone=1
one=one+1
}
}
在上述代碼中,類Cat與變量one只在代碼執(zhí)行路徑通過if結(jié)構(gòu)這一短暫的時間內(nèi)才會存在。當(dāng)調(diào)用函數(shù)silly時,執(zhí)行路徑就會進入if結(jié)構(gòu)中,Cat會被聲明并進入存活狀態(tài);接下來,one被聲明并進入存活狀態(tài);然后代碼行one=one+1會被執(zhí)行;接下來作用域結(jié)束,Cat與one都會消失殆盡。1.8對象成員在3種對象類型(類、結(jié)構(gòu)體與枚舉)中,聲明在頂層的事物具有特殊的名字,這在很大程度上是出于歷史原因。下面以Manny類作為示例:classManny{
letname="manny"
funcsayName(){
print(name)
}
}
在上述代碼中:·name是聲明在對象聲明頂層中的變量,因此叫作該對象的屬性?!ayName是聲明在對象聲明頂層中的函數(shù),因此叫作對象的方法。聲明在對象聲明頂層的事物(屬性、方法以及聲明在該層次上的任何對象)共同構(gòu)成了該對象的成員。成員具有特殊的意義,因為它們定義了你可以向該對象所發(fā)送的消息!1.9命名空間命名空間指的是程序中的具名區(qū)域。命名空間具有這樣一個屬性:如果事先不穿越區(qū)域名這一屏障,那么命名空間外的事物是無法訪問到命名空間內(nèi)的事物的。這是一個好想法,因為通過命名空間,我們可以在不同地方使用相同的名字而不會出現(xiàn)沖突。顯然,命名空間與作用域是緊密關(guān)聯(lián)的兩個概念。命名空間有助于解釋清楚在一個對象頂層聲明另一個對象的意義,比如:classManny{
classKlass{}
}
通過這種方式來聲明Klass會使得Klass成為一個嵌套類型,并且很好地將其“隱藏”到Manny中。Manny就是個命名空間!Manny中的代碼可以直接看到Klass,不過Manny外的代碼則看不到。需要顯式指定命名空間才能穿過命名空間所代表的屏障。要想做到這一點,必須先使用Manny的名字,后跟一個點,然后是術(shù)語Klass。簡而言之,需要寫成Manny.Klass。命名空間本身并不會提供安全或隱私;只是提供了便捷的手段而已。因此,在示例1-1中,我給Manny一個Klass類,也給Moe一個Klass類。不過它們之間并不會出現(xiàn)沖突,因為它們位于不同的命名空間中,如果必要,我可以通過Manny.Klass與Moe.Klass來區(qū)分它們。毫無疑問,顯式使用命名空間的語法依舊是消息發(fā)送點符號語法,事實上,它們是一回事。實際上,你可以通過消息發(fā)送進入本無法進入的作用域。Moe中的代碼不能自動看到Manny中聲明的Klass,不過可以采取一個額外的步驟來實現(xiàn)這個目標(biāo),即通過Manny.Klass。之所以可以這么做是因為它能看到Manny(因為Manny聲明的層級可以被Moe中的代碼看到)。1.10模塊頂層命名空間是模塊。在默認(rèn)情況下,應(yīng)用本身就是個模塊,因此也是個命名空間;大概來說,命名空間的名字就是應(yīng)用的名字。比如,假如應(yīng)用叫作MyApp,那么如果在文件頂層聲明一個類Manny,那么該類的真實名字就是MyApp.Manny。但通常情況下是不需要這個真實名字的,因為代碼已經(jīng)在相同的命名空間中了,可以直接看到名字Manny??蚣芤彩悄K,因此它們也是命名空間。比如,Cocoa的Foundation框架(NSString位于其中)就是個模塊。在編寫iOS程序時,你會importFoundation(還有可能importUIKit,它本身又會導(dǎo)入Foundation),這樣就可以直接使用NSString而不必寫成Foundation.NSString了。不過你可以寫成Foundation.NSString,如果在自己的模塊中聲明了一個不同的NSString,那么為了區(qū)分它們,你就只能寫成Foundation.NSString了。你還可以創(chuàng)建自己的框架,當(dāng)然了,它們也都是模塊。如示例1-1所示,文件層級之外的是文件所導(dǎo)入的庫或模塊。代碼總是會隱式導(dǎo)入Swift本身。還可以顯式導(dǎo)入,方式就是以importSwift作為文件的開始;但沒必要這么做,不過這么做也沒什么弊端。這個事實是非常重要的,因為它解決了一個大謎團:如print來自于哪里,為何可以在任何對象的任何消息之外使用它們?事實上,print是在Swift.h頭文件的頂層聲明的一個函數(shù)——你的文件可以看到它,因為它導(dǎo)入了Swift。它就是個普通的頂層函數(shù),與其他函數(shù)一樣。你可以寫成Swift.print("hello"),但沒必要,因為并不會出現(xiàn)沖突。你可以查看Swift.h文件并閱讀和研究它,這么做很有幫助。要想做到這一點,請按住Command鍵并單擊代碼中的print。此外,還可以顯式importSwift并按住Command鍵,然后單擊Swift來查看其頭文件!你看不到任何可執(zhí)行的Swift代碼,不過卻可以查看到所有可用的Swift聲明,包括如print之類的頂層函數(shù)、+之類的運算符以及內(nèi)建類型的聲明,如Int和String(查找structInt、structString等)。1.11實例對象類型(類、結(jié)構(gòu)體與枚舉)都有一個共同的重要特性:它們可以實例化。事實上,在聲明一個對象類型時,你只不過是定義了一個類型而已。實例化類型則是創(chuàng)建該類型的一個實例。比如,我可以聲明一個Dog類并為其添加一個方法:classDog{
funcbark(){
print("woof")
}
}
但程序中實際上并沒有任何Dog對象。我只不過是描述了Dog類型。要想得到一個實際的Dog,我需要創(chuàng)建一個。圖1-1:創(chuàng)建實例并調(diào)用實例方法創(chuàng)建一個類型為Dog類的實際的Dog對象的過程就是實例化Dog的過程。結(jié)果就是一個全新的對象——一個Dog實例。在Swift中,實例可以通過將對象類型名作為函數(shù)名并調(diào)用該函數(shù)來創(chuàng)建。這里使用了圓括號。在將圓括號附加到對象類型名時,你實際上向該對象類型發(fā)送了一條非常特殊的消息:實例化自身!現(xiàn)在來創(chuàng)建一個Dog實例:letfido=Dog()
上述代碼蘊涵著很多事情!我做了兩件事:實例化了Dog,得到一個Dog實例;還將該Dog實例放到了名為fido的盒子中——我聲明了一個變量,然后通過將新的Dog實例賦給它來初始化該變量。現(xiàn)在fido是一個Dog實例。(此外,由于使用了let,因此fido將總是指向這個Dog實例。我可以使用var,但即便如此,將fido初始化為一個Dog實例依然表示fido將只能指向某個Dog實例)。既然有了一個Dog實例,我可以向其發(fā)送實例消息。你覺得會是什么呢?它們是Dog的屬性與方法!比如:letfido=Dog()
fido.bark()
上述代碼是合法的。不僅如此,它還是有效的:它會在控制臺中輸出"woof"。我創(chuàng)建了一個Dog,并且讓其吼叫(如圖1-1所示)!這里有一些重要的事情要說明一下。在默認(rèn)情況下,屬性與方法是實例屬性與實例方法。你不能將其作為消息發(fā)送給對象類型本身;只能將這些消息發(fā)送給實例。正如下面的代碼所示,這么做是不合法的,無法編譯通過:Dog.bark()//compileerror
可以聲明一個函數(shù)bark,使得Dog.bark()調(diào)用變成合法調(diào)用,不過這是另外一種函數(shù)(類函數(shù)或是靜態(tài)函數(shù))。如果聲明了這樣的函數(shù)就可以這么調(diào)用了。屬性也一樣。為了說明問題,我們?yōu)镈og增加一個name屬性。到目前為止,每一個Dog都有一個名字,這是通過為變量name賦值而得到的。不過該名字并不屬于Dog對象本身。name屬性如下所示:classDog{
varname=""
}
這樣就可以設(shè)置Dog的name了,不過需要是Dog的實例:letfido=Dog()
="Fido"
還可以聲明屬性name,使得D這種寫法變成合法的,不過這是另外一種屬性——類屬性或是靜態(tài)屬性——如果這么聲明了,那就可以這樣使用。1.12為何使用實例雖然沒有實例這個事物,不過一個對象類型本身就是個對象。之所以這么說是因為我們可以向?qū)ο箢愋桶l(fā)送消息:可以將對象類型看作命名空間,并顯式進入該命名空間中(如Manny.Klass)。此外,既然存在類成員與靜態(tài)成員,我們可以直接調(diào)用類、結(jié)構(gòu)體或枚舉類型的方法,還可以引用類、結(jié)構(gòu)體或枚舉類型的屬性。既然如此,實例還有存在的必要嗎?答案與實例屬性的本質(zhì)有關(guān)。實例屬性的值的定義與特定的實例有關(guān),這正是實例真正的用武之地。再來看看Dog類。我為它添加了一個name屬性和一個bark方法;請記住,它們分別是實例屬性與實例方法:classDog{
varname=""
funcbark(){
print("woof")
}
}
Dog實例剛創(chuàng)建出來時name是空的(一個空字符串)。不過其name屬性是個var,因此一旦創(chuàng)建了Dog實例,我們就可以為其name賦予一個新的String值:letdog1=Dog()
="Fido"
還可以獲取Dog實例的name:letdog1=Dog()
="Fido"
print()//"Fido"
重要之處在于我們可以創(chuàng)建多個Dog實例,兩個不同的Dog實例可以擁有不同的name屬性值(如圖1-2所示):letdog1=Dog()
="Fido"
letdog2=Dog()
="Rover"
print()//"Fido"
print()//"Rover"
注意,Dog實例的name屬性與Dog實例所賦予的變量名之間沒有任何關(guān)系。該變量只不過是一個盒子而已。你可以將一個實例從一個盒子傳遞給另一個。不過實例本身會維護自己的內(nèi)在統(tǒng)一性:letdog1=Dog()
="Fido"
vardog2=Dog()
="Rover"
print()//"Fido"
print()//"Rover"
dog2=dog1
print()//"Fido"
上述代碼并不會改變Rover的name;它改變的是dog2盒子中的狗,將Rover替換成了Fido?;趯ο缶幊痰耐ΜF(xiàn)在開始顯現(xiàn)出來了。有一個Dog對象類型,它定義了什么是Dog。我們對Dog的聲明表示任何一個Dog實例都有一個name屬性和一個bark方法。不過每個Dog實例都有自己的name屬性值。它們是不同的實例,分別維護著自己的內(nèi)部狀態(tài)。因此,相同對象類型的多個實例的行為都是類似的:Fido與Rover都可以吼叫,如果向它們發(fā)送bark消息它們就會這么做,但它們是不同的實例,可以有不同的屬性值:Fido的name是"Fido",而Rover的name是"Rover"。圖1-2:具有不同屬性值的兩只狗(對于數(shù)字1與2來說同樣如此,不過事實卻并不是那么顯而易見。Int是一個value屬性。1是一個Int,其值是1,2表示值為2的Int。不過,這一事實在實際開發(fā)中卻沒那么有趣,因為你顯然不會改變1的值!)實例是其類型的實例方法的反映,但這并非全部;它還是實例屬性的集合。對象類型負(fù)責(zé)實例所擁有的屬性,但對這些屬性的值并不是必需的。當(dāng)程序運行時,值可以發(fā)生變化,并且只會應(yīng)用到特定的實例上。實例是特定屬性值的集合。實例不僅要負(fù)責(zé)值,還要負(fù)責(zé)屬性的生命周期。假設(shè)我們創(chuàng)建了一個Dog實例,并為其name屬性賦予了值"Fido"。那么只要我們沒有使用其他值替換掉其name值并且該實例處在存活狀態(tài),那么該Dog實例就會一直持有字符串"Fido"。簡而言之,實例既包含代碼又包含數(shù)據(jù)。代碼來自于實例的類型,并且與該類型的所有其他實例共享,不過數(shù)據(jù)只屬于該實例本身。只要實例存在,其數(shù)據(jù)就一直存在。在任意時刻,實例都有一個狀態(tài)——自身屬性值的完整集合。實例是維護狀態(tài)的一個設(shè)備,它是數(shù)據(jù)的一個存儲箱。1.13self實例是一個對象,對象則是消息的接收者。因此,實例需要通過一種方式才能將消息發(fā)送給自己。這是通過神奇的單詞self實現(xiàn)的。該單詞可以用在需要恰當(dāng)類型的實例的情況下。比如,假設(shè)我想要在一個屬性中記錄下Dog吼叫時所喊出的內(nèi)容,即"woof"。接下來,在bark的實現(xiàn)中,我需要使用該屬性,可以像下面這樣做:classDog{
varname=""
varwhatADogSays="woof"
funcbark(){
print(self.whatADogSays)
}
}
與之類似,假設(shè)我想要編寫一個實例方法speak,表示bark的同義詞。該speak實現(xiàn)只需調(diào)用自己的bark方法即可。可以像下面這樣做:classDog{
varname=""
varwhatADogSays="woof"
funcbark(){
print(self.whatADogSays)
}
funcspeak(){
self.bark()
}
}
注意該示例中self只出現(xiàn)在實例方法中。當(dāng)一個實例的代碼使用self時,表示引用該實例。如果表達式出現(xiàn)在Dog的實例方法代碼中,它表示該Dog實例的名字,即此時此刻運行該代碼的實例。實際上self是完全可選的,你可以省略它,結(jié)果完全一樣:classDog{
varname=""
varwhatADogSays="woof"
funcbark(){
print(whatADogSays)
}
funcspeak(){
bark()
}
}
原因在于如果省略消息接收者,那么你所發(fā)送的消息就會發(fā)送給self,編譯器會在底層將self作為消息接收者。不過,我從來不會這么做(除非寫錯了)。作為一種風(fēng)格,我喜歡顯式在代碼中使用self。我覺得省略self的代碼的可讀性與可理解性都會變得很差。在某些情況下,self是不能省略的,因此我傾向于在可能的情況下都使用self。1.14隱私我之前曾說過,命名空間本身并非訪問內(nèi)部名字的不可逾越的屏障。不過如果你愿意,它還是可以作為屏障的。比如,并非存儲在實例中的所有數(shù)據(jù)都需要修改,甚至要對其他實例可見。并非每一個實例方法都可以被其他實例調(diào)用。任何優(yōu)秀的基于對象的編程語言都需要通過一種方式確保其對象成員的隱私性——如果不希望其他對象看到這些成員,那么可以通過這種方式做到這一點。比如:classDog{
varname=""
varwhatADogSays="woof"
funcbark(){
print(self.whatADogSays)
}
funcspeak(){
print(self.whatADogSays)
}
}
對于上述代碼來說,其他對象可以修改屬性whatADogSays。由于該屬性由bark與speak使用,因此最后可能出現(xiàn)的結(jié)果就是,我們讓一只Dog吼叫,但它卻發(fā)出“貓叫聲”。這顯然不是我們想要的:letdog1=Dog()
dog1.whatADogSays="meow"
dog1.bark()//meow
你可能會說:真夠笨的了,為何要使用var聲明whatADogSays呢?使用let聲明不就行了,讓它成為常量。現(xiàn)在就沒人能夠修改它了:classDog{
varname=""
letwhatADogSays="woof"
funcbark(){
print(self.whatADogSays)
}
funcspeak(){
print(self.whatADogSays)
}
}
這個答案還不錯,但并不是最好的答案。它有兩個問題。假設(shè)我希望Dog實例本身能夠修改self.whatADogSays,那么whatADogSays就必須得是var了;否則,即便實例本身也無法修改它。此外,假設(shè)我不希望其他對象知道這只Dog吼的是什么,除非調(diào)用bark或是speak才可以。即便使用let聲明,其他對象依然還是可以讀取到whatADogSays的值。我可不想這樣。為了解決這個問題,Swift提供了關(guān)鍵字private。稍后將會介紹這個關(guān)鍵字的全部含義,不過現(xiàn)在只要知道它可以解決問題就行了:classDog{
varname=""
privatevarwhatADogSays="woof"
funcbark(){
print(self.whatADogSays)
}
funcspeak(){
print(self.whatADogSays)
}
}
現(xiàn)在,name是個公有屬性,但whatADogSays卻是個私有屬性:其他對象是看不到它的。一個Dog實例可以使用self.whatADogSays,但引用Dog實例的不同對象(如dog1)就無法使用dog1.whatADogSays。這里要說明的重要的一點是,對象成員默認(rèn)是公有的,如果需要訪問隱私信息,那就需要請求。類聲明定義了一個命名空間;該命名空間要求其他對象通過額外的點符號來引用命名空間內(nèi)部的事物,不過其他對象依然可以引用命名空間內(nèi)部的事物;命名空間本身并不會對可見性形成屏障,而private關(guān)鍵字則形成了這道屏障。1.15設(shè)計現(xiàn)在你已經(jīng)知道了何為對象,何為實例。不過,程序需要什么對象類型呢,這些對象類型應(yīng)該包含哪些方法與屬性呢,何時以及如何實例化它們呢,該如何使用這些實例呢?遺憾的是,我無法告訴你這些,這是一門藝術(shù),基于對象編程的藝術(shù)。我能告訴你的是,在設(shè)計與實現(xiàn)基于對象的程序時,你的首要關(guān)注點應(yīng)該是什么,程序正是通過這個過程不斷發(fā)展起來的?;趯ο蟮某绦蛟O(shè)計必須要基于對對象本質(zhì)的深刻理解之上。你設(shè)計出的對象類型需要能夠封裝好正確的功能(方法)以及正確的數(shù)據(jù)(屬性)。接下來,在實例化這些對象類型時,你要確保實例擁有正確的生命周期,恰到好處地公開給其他對象,并且要能實現(xiàn)對象之間的通信。1.15.1對象類型與API程序文件只會包含極少的頂層函數(shù)與變量。對象類型的方法與屬性(特別是實例方法與實例屬性)實現(xiàn)了大多數(shù)動作。對象類型為每個具體實例賦予了專門的能力。它們還有助于更有意義地組織程序代碼,并使其更易維護??梢杂脙蓚€短語來總結(jié)對象的本質(zhì):功能封裝與狀態(tài)維護(我最早是在REALbasic:TheDefinitiveGuide一書中給出的這個總結(jié))。功能封裝每個對象都有自己的職責(zé),并且在自身與外界對象(從某種意義上說是程序員)之間架起一道不透明的墻,外界只能通過這道墻訪問方法與動作,而這是通過向其發(fā)送相應(yīng)的消息實現(xiàn)的。在背后,這些動作的實現(xiàn)細(xì)節(jié)是隱藏起來的,其他對象無須知曉。狀態(tài)維護每個實例都有自己所維護的一套數(shù)據(jù)。通常來說,這些數(shù)據(jù)是私有的,因此是被封裝的;其他對象不知道這些數(shù)據(jù)是什么,也不清楚其形式是怎樣的。對于外界來說,探求對象所維護的私有數(shù)據(jù)的唯一方式就是通過公有方法或是屬性。比如,假設(shè)有一個對象,它實現(xiàn)了棧的功能——也許是Stack類的實例。棧是一種數(shù)據(jù)結(jié)構(gòu),以LIFO(后進先出)的順序維護著一組數(shù)據(jù),它只會響應(yīng)兩個消息:push與pop。push表示將給定數(shù)據(jù)添加到集合中,pop表示從集合中刪除最近剛加進去的數(shù)據(jù)。棧像是一摞盤子:盤子會一個接著一個地放到棧上面或從棧上移除,因此只有將后面添加的盤子都移除后才能移除第一個添加的盤子(如圖1-3所示)。圖1-3:棧棧對象很好地說明了功能的封裝,因為外部對棧的實現(xiàn)方式一無所知。它可能是個數(shù)組,也可能是個鏈表,還有可能是其他實現(xiàn)。不過客戶端對象(向棧對象發(fā)送push或pop消息的對象)對此卻并不在意,只要棧對象堅持其行為要像一個棧這樣的契約即可。這對于程序員來說也非常棒,在開發(fā)程序時,它們可以將一個實現(xiàn)替換為另外的實現(xiàn),同時又不會破壞程序的機制。恰恰相反,棧對象不知道,也不關(guān)心到底是誰向其發(fā)送push或pop消息以及為何發(fā)送。它只不過以可靠的方式完成自己的工作而已。棧對象也很好地說明了狀態(tài)維護,因為它不僅僅是棧數(shù)據(jù)的網(wǎng)關(guān),它就是棧數(shù)據(jù)本身。其他對象可以訪問到棧的數(shù)據(jù),但只能通過訪問棧對象本身的方式才行,棧對象也只允許通過這種方式來訪問。棧數(shù)據(jù)位于棧對象內(nèi)部;其他對象是看不到的。其他對象能做的只是push或pop。如果某個對象位于棧對象的頂部,那么無論哪個對象向其發(fā)送pop消息,棧對象都會收到這個消息并將頂部對象返回。如果沒有對象向這個棧對象發(fā)送pop消息,那么棧頂部的對象就會一直待在那里。每個對象類型可以接收的消息總數(shù)(即API,應(yīng)用編程接口)類似于你可以讓這個對象類型所做的事項列表。對象類型將你的代碼劃分開來;其API構(gòu)成了各部分之間通信的基礎(chǔ)。在實際情況下,當(dāng)編寫iOS程序時,你所使用的大多數(shù)對象類型都不是你自己的,而是蘋果公司提供的。Swift本身自帶了一些頗具價值的對象類型,如String和Int;你可能還會使用importUIKit,它包含了為數(shù)眾多的對象類型,這些對象類型會涌入你的程序中。你無須創(chuàng)建這些對象類型,只要學(xué)習(xí)如何使用它們就可以了,你可以查閱公開的API,即文檔。蘋果公司自己的Cocoa文檔包含了大量頁面,每一頁都列出并介紹了一種對象類型的屬性與方法。比如,要想了解可以向NSString實例發(fā)送什么消息,你可以先研究一下NSString類的文檔。其頁面包含屬性與方法的長長的列表,告訴你NSString對象能做什么。文檔上并不會列出關(guān)于NSString的一切,不過大部分內(nèi)容都可以在上面找到。在編寫代碼前,蘋果公司已經(jīng)替你做了很多思考與規(guī)劃。因此,你會大量使用蘋果公司提供的對象類型。你也可以創(chuàng)建全新的對象類型,但相對于使用現(xiàn)有的對象類型來說,自己創(chuàng)建的比例不是很大。1.15.2實例創(chuàng)建、作用域與生命周期Swift中重要的實體基本上就是實例了。對象類型定義了實例的種類以及每一種實例的行為方式。不過這些類型的具體實例都是持有狀態(tài)的“實體”,這也是程序最為關(guān)注的內(nèi)容,實例方法與屬性就是可以發(fā)送給實例的消息。因此,程序能夠發(fā)揮作用是離不開實例的幫助的。不過在默認(rèn)情況下,實例是不存在的!回頭看看示例1-1,我們定義了一些對象類型,但卻并沒有創(chuàng)建實例。如果運行程序,那么對象類型從一開始就會存在,但僅僅只是存在而已。我們實現(xiàn)了一種可能性,能夠創(chuàng)建一些可能存在的對象的類型,但在這種情況下,其實什么都不會發(fā)生。實例并不會自動產(chǎn)生。你需要對類型進行實例化才能得到實例。因此,程序的很多動作都會包含實例化類型。當(dāng)然,你希望保留這些實例,因此還會將每個新創(chuàng)建的實例賦給一個變量,讓這個變量來持有它、為其命名,并保持其生命周期。實例的生命周期取決于引用它的變量的生命周期。根據(jù)引用實例的變量的作用域,一個實例可能會對其他實例可見。基于對象編程的藝術(shù)的要點就在于此,賦予實例足夠的生命周期并讓它對其他實例可見。你常常會將實例放到特定的盒子中(將其賦給某個變量、在某個作用域中聲明),這多虧了變量生命周期與作用域規(guī)則,如果需要,實例會留存足夠長的時間供程序使用,其他代碼也可以獲得它的引用并與之通信。規(guī)劃好如何創(chuàng)建實例、確定好實例的生命周期以及實例之間的通信這件事讓人望而生畏。幸好,在實際情況下,當(dāng)編寫iOS程序時,Cocoa框架本身會再一次為你提供初始框架。比如,你知道對于iOS應(yīng)用來說,你需要一個應(yīng)用委托類型和一個視圖控制器類型;實際上,在創(chuàng)建iOS應(yīng)用項目時,Xcode會幫你完成這些事情。此外,當(dāng)應(yīng)用啟動時,運行時會幫你實例化這些對象類型,并且構(gòu)建好它們之間的關(guān)系。運行時會創(chuàng)建出應(yīng)用委托實例,并且讓其在應(yīng)用的生命周期中保持存活狀態(tài);它會創(chuàng)建一個窗口實例,并將其賦給應(yīng)用委托的一個屬性;它還會創(chuàng)建一個視圖控制器實例,并將其賦給窗口的一個屬性。最后,視圖控制器實例有一個視圖,它會自動出現(xiàn)在窗口中。這樣,你無須做任何事情就擁有了在應(yīng)用生命周期中一直存在的一些對象,包括構(gòu)成可視化界面的基礎(chǔ)對象。重要的是,你已經(jīng)擁有了定義良好的全局變量,可以引用所有這些對象。這意味著,無須編寫任何代碼,你就已經(jīng)可以訪問某些重要對象了,并且有了一個初始位置用于放置生命周期較長的其他對象,以及應(yīng)用所需的其他可視化界面元素。1.15.3小結(jié)在構(gòu)建基于對象的程序來執(zhí)行特定的任務(wù)時,我們要理解對象的本質(zhì)。它們是類型與實例。類型指的是一組方法,用于說明該類型的所有實例可以做什么(功能封裝)。相同類型的實例只有屬性值是不同的(狀態(tài)維護)。我們要做好規(guī)劃。對象是一種組織好的工具,一組盒子,用于封裝完成特定任務(wù)的代碼。它們還是概念工具。程序員要能以離散對象的思維進行思考,他們要能將程序的目標(biāo)與行為分解為離散的任務(wù),每個任務(wù)都對應(yīng)一個恰當(dāng)?shù)膶ο蟆Ec此同時,對象并不是孤立的。對象之間可以合作,這叫作通信,方式則是發(fā)送消息。通信途徑是多種多樣的。對此做出妥善的安排(即架構(gòu)),從而實現(xiàn)對象之間的協(xié)作、有序的關(guān)系是基于對象編程最具挑戰(zhàn)性的一個方面。在iOS編程中,你可以得到Cocoa框架的幫助,它提供了初始的對象類型集合以及基礎(chǔ)的架構(gòu)框架。使用基于對象的編程能夠很好地讓程序?qū)崿F(xiàn)你的需求,同時又會保持程序的整潔性和可維護性;你的能力會隨著經(jīng)驗的增加而增強。最后,你可能想要進一步閱讀關(guān)于高效規(guī)劃及基于對象編程架構(gòu)方面的讀物。我推薦兩本經(jīng)典、值得收藏的好書。MartinFowler所寫的Refactoring(Addison-Wesley,1999)介紹了如何根據(jù)哪些方法應(yīng)該屬于哪些類而重新調(diào)整代碼的原則(如何戰(zhàn)勝恐懼以實現(xiàn)這一點)。ErichGamma、RichardHelm、RalphJohnson及JohnVlissides(又叫作“四人組”)所寫的DesignPatterns(本書中文版《設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》已由機械工業(yè)出版社引進出版,書號:978-7-111-07575-2。)是架構(gòu)基于對象程序的寶典,書中列舉了如何根據(jù)正確的原則以及對象之間的關(guān)系來安排對象的所有方法(Addison-Wesley,1994)。第2章函數(shù)Swift語法中最具特色的就是聲明與調(diào)用函數(shù)的方式了,沒什么比這更重要!就像第1章所介紹的那樣,所有代碼都位于函數(shù)中;而動作則是由函數(shù)觸發(fā)的。2.1函數(shù)參數(shù)與返回值函數(shù)就像是小學(xué)數(shù)學(xué)課本中所講的那種能夠處理各種東西的機器一樣。你知道我說的是什么:上面是一個漏斗,中間是一些齒輪和曲柄,下面的管子就會出來東西。函數(shù)就像這樣的機器一樣:你提供一些東西,然后由特定的機器進行處理,最后東西就生成出來了。提供的東西是輸入,出來的東西是輸出。從技術(shù)的角度來看,函數(shù)的輸入叫作參數(shù),輸出叫作結(jié)果。比如,下面這個簡單的函數(shù)有兩個Int值輸入,然后將它們相加,最后輸出相加的和:funcsum(x:Int,_y:Int)->Int{
letresult=x+y
returnresult
}
這里所用的語法是非常嚴(yán)格且定義良好的,如果理解不好你就沒法用好Swift。下面來詳細(xì)介紹;我將第1行分為幾塊來分別介紹:funcsum①
(x:Int,_y:Int)②③
->Int{④
letresult=x+y⑤
returnresult⑥
}
①聲明從關(guān)鍵字func開始,后跟函數(shù)的名字,這里就是sum。調(diào)用函數(shù)時必須使用這個名字,所謂調(diào)用就是運行函數(shù)所包含的代碼。②函數(shù)名后面跟著的是其參數(shù)列表,至少要包含一對圓括號。如果函數(shù)接收參數(shù)(輸入),那么它們就會列在圓括號中,中間用逗號隔開。每個參數(shù)都有嚴(yán)格的格式:參數(shù)名,一個冒號,然后是參數(shù)類型。這里的sum函數(shù)接收兩個參數(shù):一個是Int,其名字為x;另一個也是Int,其名字為y。值得注意的是,名字x與y是隨意起的,它們只對該函數(shù)起作用。它們與其他函數(shù)或是更高層次作用域中所使用的其他x與y是不同的。定義這些名字的目的在于讓參數(shù)值能有自己的名字,這樣就可以在函數(shù)體的代碼塊中引用它們了。實際上,參數(shù)聲明是一種變量聲明:我們聲明變量x與y以便在該函數(shù)中使用它們。③該函數(shù)聲明的參數(shù)列表中第2個參數(shù)名前還有一個下劃線(_)和一個空格?,F(xiàn)在我不打算介紹這個下劃線,只是這個示例需要用到它,因此相信我就行了。④圓括號之后是一個箭頭運算符→,后跟函數(shù)所返回的值類型。接下來是一對花括號,包圍了函數(shù)體——實際代碼。⑤在花括號中(即函數(shù)體),作為參數(shù)名定義的變量進入生命周期,其類型是在參數(shù)列表中指定的。我們知道,只有當(dāng)函數(shù)被調(diào)用并將值作為參數(shù)傳遞進去時,這些代碼才會運行。這里的參數(shù)叫作x與y,這樣我們就可以安全地使用這些值,通過其名字來引用它們,我們知道這些值都會存在,并且是Int值,這是通過參數(shù)列表來指定的。不僅是程序員,編譯器也可以確保這一點。⑥如果函數(shù)有返回值,那么它必須要使用關(guān)鍵字return,后跟要返回的值。該值的類型要與之前聲明的返回值類型(位于箭頭運算符之后)相匹配。這里返回了一個名為result的變量;它是通過將兩個Int值相加創(chuàng)建出來的,因此也是個Int,這正是該函數(shù)所生成的結(jié)果。如果嘗試返回一個String(return"howdy")或是省略掉return語句,那么編譯器就會報錯。關(guān)鍵字return實際上做了兩件事。它返回后面的值,同時又終止了函數(shù)的執(zhí)行。return語句后面可以跟著多行代碼,不過如果這些代碼行永遠(yuǎn)都不會執(zhí)行,那么編譯器就會發(fā)出警告?;ɡㄌ栔暗暮瘮?shù)聲明是一種契約,描述了什么樣的值可以作為輸入,產(chǎn)生的輸出會是什么樣子。根據(jù)該契約,函數(shù)可以接收一定量的參數(shù),每個參數(shù)都有確定的類型,并會生成確定類型的結(jié)果。一切都要遵循這個契約。花括號中的函數(shù)體可以將參數(shù)作為局部變量。返回值要與聲明的返回類型相匹配。這個契約會應(yīng)用到調(diào)用該函數(shù)的任何地方。如下代碼調(diào)用了sum函數(shù):letz=sum(4,5)
重點關(guān)注等號右側(cè)——sum(4,5),這是個函數(shù)調(diào)用。它是如何構(gòu)建的呢?它使用了函數(shù)的名字、名字后跟一對圓括號;在圓括號里面是逗號分隔的傳遞給函數(shù)參數(shù)的值。從技術(shù)角度來說,這些值叫作實參。我這里使用了Int字面值,不過還可以使用Int變量;唯一的要求就是它要有正確的類型:letx=4
lety=5
letz=sum(y,x)
在上述代碼中,我故意使用了名字x與y,這兩個變量值會作為參數(shù)傳遞給函數(shù),而且還在調(diào)用中將它們的順序有意弄反,從而強調(diào)這些名字與函數(shù)參數(shù)列表及函數(shù)體中的名字x與y沒有任何關(guān)系。對于函數(shù)來說,名字并不重要,值才重要,它們的值叫作實參。函數(shù)的返回值呢?該值會在函數(shù)調(diào)用發(fā)生時替換掉函數(shù)調(diào)用。上述代碼就是這樣的,其結(jié)果是9。因此,最后一行就像我說過的:letz=9
程序員與編譯器都知道該函數(shù)會返回什么類型的值,還知道在什么地方調(diào)用該函數(shù)是合法的,什么地方調(diào)用是不合法的。作為變量z聲明的初始化來調(diào)用該函數(shù)是沒問題的,這相當(dāng)于將9作為聲明的初始化部分:在這兩種情況下,結(jié)果都是Int,因此z也會聲明為Int。不過像下面這樣寫就不合法了:letz=sum(4,5)+"howdy"http://compileerror
由于sum返回一個Int,這相當(dāng)于將Int加到一個String上。在默認(rèn)情況下,Swift中不允許這么做。忽略函數(shù)調(diào)用的返回值也是可以的:sum(4,5)
上述代碼是合法的;它既不會導(dǎo)致編譯錯誤,也不會造成運行錯誤。這么做有點無聊,因為我們歷盡辛苦使得sum函數(shù)能夠?qū)崿F(xiàn)4與5相加的結(jié)果,但卻沒有用到這個結(jié)果,而是將其丟棄掉了。不過,很多時候我們都會忽略掉函數(shù)調(diào)用的返回值;特別是除了返回值之外,函數(shù)還會做一些其他事情(從技術(shù)上來說叫作副作用),而調(diào)用函數(shù)的目的只是讓函數(shù)能夠做一些其他事情。如果在使用Int的地方調(diào)用sum,而且sum的參數(shù)都是Int值,那是不是就表示你可以在sum調(diào)用中再調(diào)用sum呢?當(dāng)然了!這么做是完全合法的,也是合情合理的:letz=sum(4,sum(5,6))
這樣寫代碼存在著一個爭議,那就是你可能被自己搞暈,而且會導(dǎo)致調(diào)試變得困難。不過從技術(shù)上來說,這么做是正常的。2.1.1Void返回類型與參數(shù)下面回到函數(shù)聲明。關(guān)于函數(shù)參數(shù)與返回類型,存在以下兩種情況能夠讓我們更加簡潔地表示函數(shù)聲明。無返回類型的函數(shù)沒有規(guī)定說函數(shù)必須要有返回值。函數(shù)聲明可以沒有返回值。在這種情況下,有3種聲明方式:可以返回Void,可以返回(),還可以完全省略箭頭運算符與返回類型。如下聲明都是合法的:funcsay1(s:String)->Void{print(s)}
funcsay2(s:String)->(){print(s)}
funcsay3(s:String){print(s)}
如果函數(shù)沒有返回值,那么函數(shù)體就無須包含return語句。如果包含了return語句,那么其目的就純粹是在該處終止函數(shù)的執(zhí)行。return語句通常只會包含return。不過,Void(無返回值的函數(shù)的返回類型)是Swift中的一個類型。從技術(shù)上來說,無返回值的函數(shù)實際上返回的就是該類型的值,可以表示為字面值()。(第3章將會介紹字面值()的含義。)因此,函數(shù)聲明return()是合法的;無論是否聲明,()就是函數(shù)所返回的。寫成return()或return;(后面加上一個分號)有助于消除歧義,否則Swift可能認(rèn)為函數(shù)會返回下一行的內(nèi)容。如果函數(shù)無返回值,那么調(diào)用它純粹就是為了函數(shù)的副作用;其調(diào)用結(jié)果無法作為更大的表達式的一部分,這樣函數(shù)中代碼的執(zhí)行就是函數(shù)要做的唯一一件事,返回的()值會被忽略。不過,也可以將捕獲的值賦給Void類型的變量;比如:letpointless:Void=say1("howdy")
無參數(shù)的函數(shù)沒有規(guī)定說函數(shù)必須要有參數(shù)。如果沒有參數(shù),那么函數(shù)聲明中的參數(shù)列表就可以完全為空。不過,省略參數(shù)列表圓括號本身是不可以的!圓括號需要出現(xiàn)在函數(shù)聲明中,位于函數(shù)名之后:funcgreet1()->String{return"howdy"}
顯然,函數(shù)可以既沒有返回值,也沒有參數(shù);如下代碼的含義是一樣的:funcgreet1()->Void{print("howdy")}
funcgreet2()->(){print("howdy")}
funcgreet3(){print("howdy")}
就像不能省略函數(shù)聲明中的圓括號(參數(shù)列表)一樣,你也不能省略函數(shù)調(diào)用中的圓括號。如果函數(shù)沒有參數(shù),那么這些圓括號就是空的,但必須要有。比如:greet1()
注意上述代碼中的圓括號!2.1.2函數(shù)簽名如果省略函數(shù)聲明中的參數(shù)名,那就完全可以通過輸入與輸出的類型來描述一個函數(shù)了,比如,下面這個表達式:(Int,Int)->Int
事實上,這在Swift中是合法的表達式。它是函數(shù)簽名。在該示例中,它表示的是sum函數(shù)的簽名。當(dāng)然,還可能有其他函數(shù)也接收兩個Int參數(shù)并返回一個Int,這就是要點。該簽名描述了所有接收這些類型、具有這些數(shù)量的參數(shù),并返回該類型結(jié)果的函數(shù)。實際上,函數(shù)的簽名是其類型——函數(shù)的類型。稍后將會看到,函數(shù)擁有類型是非常重要的。函數(shù)的簽名必須要包含參數(shù)列表(無參數(shù)名)與返回類型,即便其中一樣或兩者都為空亦如此;因此,不接收參數(shù)且無返回值的函數(shù)簽名可以有4種等價的表示方式,包括Void->Void與()->()。2.2外部參數(shù)名函數(shù)可以外化其參數(shù)名。外部名稱要作為實參的標(biāo)簽出現(xiàn)在對函數(shù)的調(diào)用中。這么做是有意義的,原因如下:·闡明了每個實參的作用;每個實參名都能表示出自己對函數(shù)動作的作用。·將函數(shù)區(qū)分開來;兩個函數(shù)可能有相同的名字和簽名,但卻擁有不同的外化參數(shù)名?!び兄赟wift與Objective-C和Cocoa的通信,后者的方法參數(shù)幾乎總是有外化名字的。要想外化參數(shù)名,請在函數(shù)聲明中將外部名字放在內(nèi)部參數(shù)名之前,中間用空格隔開。外部名字可以與內(nèi)部名字相同,也可以不同。不過在Swift中,外化參數(shù)名已經(jīng)形成了標(biāo)準(zhǔn),因此有如下規(guī)則:在默認(rèn)情況下,除了第一個參數(shù),所有參數(shù)名都會自動外化。這樣,如果需要外化一個參數(shù)名,并且它不是第一個參數(shù),并且希望外化名與內(nèi)部名相同,那么你什么都不需要做,Swift會自動幫你實現(xiàn)。如下是一個函數(shù)聲明,該函數(shù)會將一個字符串拼接到自身times次:funcrepeatString(s:String,#times:Int)->String{
varresult=""
for_in1...times{result+=s}
returnresult
}
該函數(shù)的第1個參數(shù)只有內(nèi)部名字,不過第2個參數(shù)有一個外部名字,它與內(nèi)部名字相同,都叫作times。下面是調(diào)用該函數(shù)的代碼:lets=repeatString("hi",times:3)
如你所見,在調(diào)用中,外部名作為一個標(biāo)簽位于實參之前,中間用冒號隔開。我之前曾經(jīng)說過,參數(shù)的外部名可以與內(nèi)部名不同。比如,在repeatString函數(shù)中,我們將times作為外部名,將n作為內(nèi)部名。那么,函數(shù)聲明將會如下所示:funcrepeatString(s:String,timesn:Int)->String{
varresult=""
for_in1...n{result+=s}
returnresult
}
函數(shù)體中現(xiàn)在已經(jīng)看不到times了;times只用作外部名,在調(diào)用時使用。內(nèi)部名是n,也是代碼所引用的名字。外部名的存在并不意味著調(diào)用可以使用與聲明不同的參數(shù)順序。比如,repeatString接受一個String參數(shù)和一個Int參數(shù),順序就是這樣的。雖然標(biāo)簽可以消除實參對應(yīng)于哪個參數(shù)的歧義,但調(diào)用的順序也需要如此(不過稍后我將給出該規(guī)則的一個例外情況)。repeatString函數(shù)說明了默認(rèn)規(guī)則,即第1個參數(shù)沒有外部名,其他參數(shù)則有。為何說這是默認(rèn)規(guī)則呢?一個原因在于第1個參數(shù)通常不需要外部名,因為函數(shù)名通常能夠清晰表明第1個參數(shù)的含義——就像repeatString函數(shù)一樣(重復(fù)一個字符串,而該字符串應(yīng)該由第1個參數(shù)提供)。另一個原因也是在實際情況中更為重要的,那就是這個約定使得Swift函數(shù)能夠與Objective-C方法交互,而后者采取的就是這種方式。比如,下面是CocoaNSString方法的Objective-C聲明:-(NSString*)stringByReplacingOccurrencesOfString:(NSString*)target
withString:(NSString*)replacement
該方法接收兩個NSString參數(shù)并返回一個NSString。第2個參數(shù)的外部名是顯而易見的,即withString。不過第1個參數(shù)的名字卻不那么明顯。一方面,你可以說它是stringByReplacingOccurrencesOfString。另一方面,它實際上并非參數(shù)真正的名字;它更像是方法名。實際上,該方法的正式名字是整個字符串stringByReplacingOccurrencesOfString:withString:。不過,Swift函數(shù)調(diào)用語法通過圓括號將函數(shù)名與外部參數(shù)名區(qū)分開來。因此,如果Swift想要調(diào)用這個Objective-C方法,那么冒號前首先就應(yīng)該是函數(shù)名(位于圓括號之前),然后是第2個實參的標(biāo)簽(位于圓括號中)。SwiftString與CocoaNSString能夠自動橋接彼此,因此可以在SwiftString上調(diào)用這個Cocoa方法,如以下代碼所示:lets="hello"
lets2=s.stringByReplacingOccurrencesOfString("ell",withString:"ipp")
//s2isnow"hippo"
如果函數(shù)是你自
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 公路護欄修建合同范本
- 個人用電協(xié)議合同范例
- 公司運輸購銷合同范本
- 刻字木材出售合同范本
- 個人旅游陪玩合同范本
- 個人住家保姆合同范本
- 勞務(wù)代理加盟合同范例
- fidic銀皮書合同范例
- 出售電廠燒火料合同范本
- fpc代加工合同范本
- (課件)港口安全知識培訓(xùn)講解
- 2022年企業(yè)安全生產(chǎn)知識培訓(xùn)講座PPT課件(帶內(nèi)容)
- 產(chǎn)品設(shè)計思維課件
- 電子直線加速器的工作原理專題培訓(xùn)課件
- 2023年孝感市孝南區(qū)全要素自然資源有限公司招聘筆試題庫及答案解析
- 臨終關(guān)懷成品課件
- 2020外研版九年級英語上全冊課文原文及翻譯
- 大象版科學(xué)四年級下冊12奇妙的植物課件及練習(xí)題和答案
- 三年級下冊豎式脫式計算
- 某大型企業(yè)空調(diào)系統(tǒng)內(nèi)部培訓(xùn)(圖文127)
- 《財務(wù)風(fēng)險的識別與評估管理國內(nèi)外文獻綜述》
評論
0/150
提交評論