101 - 框架搭建
大家好,從本節(jié)課開始喷舀,我會一步一步地教大家把這個頭條這個 app 實(shí)現(xiàn)出來。
今天的課程主要目的是搭建項(xiàng)目主框架。
1.新建項(xiàng)目
首先打開 xcode赶掖,我這里使用的 xcode 版本是8.3.3,點(diǎn)擊新建一個 xcode 工程膳灶,然后選擇 single view application ,輸入項(xiàng)目名稱,News而柑,Team 一般是個人或者公司的開發(fā)者賬號,這里我選擇默認(rèn),然后輸入組織名稱妙同,一般是公司或是組織的名稱辉浦,我這邊輸入的 hrscy掂恕,下面是組織標(biāo)識符依啰,一般以 com 開頭,后面帶上公司或組織名稱,我這里輸入的是 com.hrscy,語言有兩種忙灼,一種是 swift,另一種是 OC,這里我們選 swift,設(shè)備選 iphone 就可以了,其他都默認(rèn)設(shè)置即可哑诊,然后點(diǎn)擊下一步竞阐,選擇想要保存的位置,我這邊選擇保存到桌面暑劝,
2.項(xiàng)目主框架一般有兩種方式骆莹,一種是使用 storybaord 創(chuàng)建,另一種是使用代碼創(chuàng)建
2.1.使用代碼搭建基本框架担猛,如下圖設(shè)置
2.2 添加資源文件
2.3 自定義 UITabBarController
和 UINavigationController
3.創(chuàng)建四個控制器,首頁傅联,西瓜視頻先改,小視頻,我的
3.1 中間的加號按鈕比較特殊
UITabBarController
下方的工具條稱為UITabBar
蒸走,首先先來看一下 tabbar
的子視圖都有什么:
// 系統(tǒng)view控件準(zhǔn)備好之后調(diào)用這個方法
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print(tabBar.subviews)
}
如下圖:
會發(fā)現(xiàn)四個名為 UITabBarButton
的類仇奶,如果UITabBarController
有4個子控制器,那么UITabBar
內(nèi)部就會有4個UITabBarButton
作為子控件與之對應(yīng)。也就是說比驻,UITabBarButton
是UITabBarController
中各個子控制器在工具條中對應(yīng)的按鈕的稱呼该溯,同時我們可以注意到一個現(xiàn)象,那就是我們創(chuàng)建好UITabBarButton
之后,各個UITabBarButton
在UITabBar
中的位置是均分的,UITabBar
的高度為49别惦。UITabBarButton
是一個隱藏的子類狈茉,所謂隱藏是蘋果沒有在文檔中明確提供卻對視圖的顯示起著至關(guān)重要的作用,UITabBarButton
?面顯?什么內(nèi)容步咪,由對應(yīng)子控制器的tabBarItem
屬性來決定论皆。
下圖就是UITabBar
包含四個UITabBarButton
和中間的UIButton
4.集成 cocoapods
我的 cocoapods 版本為 1.2.0.rc.1。
進(jìn)入工程主目錄猾漫,使用下面兩個命令初始化并安裝 cocoapods:
pod init
pod install
如下圖:
安裝完成后点晴,會生成幾個文件,分別是:
- Podfile:該文件可以放在任意一個目錄下悯周,需要做的是在Podfile中指定工程的路徑粒督,一般會放在工程的主目錄,該文件主要用于管理安裝依賴庫禽翼,工程需要的依賴庫都在這里添加屠橄。
- Podfile.lock:該文件用于保存已經(jīng)安裝的 Pods 依賴庫的版本。
- Pods 文件夾:安裝的依賴庫源文件都在放在這個文件夾里闰挡。
- .xcworkspace 文件:該文件用來管理項(xiàng)目锐墙,它包含你創(chuàng)建的項(xiàng)目和cocoapods 的項(xiàng)目,以后不再打開
.xcodeproj
文件长酗,以后打開這個文件就相當(dāng)打開最初創(chuàng)建的項(xiàng)目了溪北。
現(xiàn)在打開xcworkspace
如下圖:
5.項(xiàng)目架構(gòu)用的比較多的有兩種方式,一種是 MVC,一種 MVVM之拨,這里使用比較傳統(tǒng)的 MVC 設(shè)計(jì)模式茉继。
6.建立文件夾,對項(xiàng)目進(jìn)行模塊劃分
102 - 補(bǔ)充內(nèi)容
下面我來說一下上節(jié)課沒有提到的內(nèi)容蚀乔,簡單說一下 Xcode 的界面是由哪些部分組成以及它們的功能烁竭。
首先,打開項(xiàng)目之后吉挣,界面是可能是這個樣子的派撕。
上圖為主界面。
頂部包括運(yùn)行按鈕听想,停止按鈕腥刹,scheme,scheme 定義了編譯集合中的若干target汉买,以及編譯時的一些設(shè)置和要執(zhí)行的測試集合,我們也可以自定義若干個scheme佩脊,但是同一時刻只能運(yùn)行一個蛙粘。
下圖是導(dǎo)航區(qū),包含八項(xiàng)威彰,出牧,快捷鍵是 cmd+(1-8), 分別是工程導(dǎo)航器(cmd+1)歇盼,項(xiàng)目中的類文件導(dǎo)航器(cmd+2)舔痕,搜索導(dǎo)航器(cmd 的+3),問題導(dǎo)航器(cmd+4)豹缀,測試導(dǎo)航器(smd+5)伯复,調(diào)試導(dǎo)航器(cmd+6),斷點(diǎn)導(dǎo)航器(cmd+7)邢笙,日志信息導(dǎo)航器(cmd+8)啸如。
右邊是檢查器(cmd+option+0),分為上下兩部分:
上部分會有兩種情況,包括文件就檢查器(cmd+option+1)氮惯,快捷幫助檢查器(cmd+option+2)叮雳,類標(biāo)識檢查器(cmd+option+3),屬性檢查器(cmd+option+4)妇汗,尺寸檢查器(cmd+option+5)帘不,連線檢查器(cmd+option+6),如下圖:
下部分包括文件模版(cmd+option+control+1),代碼塊(cmd+option+control+2)杨箭,對象庫(cmd+option+control+3)寞焙,媒體庫(cmd+option+control+4),如下圖。
[圖片上傳失敗...(image-e43754-1533613300593)]
下圖是調(diào)試區(qū)(cmd+shift+y):
下面介紹一下 Xcode 中間的主工作區(qū)棺弊,也可以說是代碼編輯區(qū)晶密,首先點(diǎn)擊項(xiàng)目名稱,會出現(xiàn)上圖主界面中間的樣子模她,只有兩個屬性稻艰,一個是 info,一個 build setting侈净,這兩項(xiàng)是項(xiàng)目的基本信息尊勿。
下面點(diǎn)擊 TARGETS,找到 General畜侦,下面介紹一下它里面的一些設(shè)置:
General
- 修改 Dispaly Name 為 頭條元扔,你也可以設(shè)置別的名稱,這個名稱就是最終 app 的名稱旋膳。
- Version 就是版本號澎语,修改 Version 為 6.3.2,在開發(fā)過程中验懊,也就是在 App Store 最終顯示的版本號擅羞,而 Build 是每次構(gòu)建的版本,比如我在 App Store 準(zhǔn)備提交一次更新义图,設(shè)置的版本為 6.3.2减俏,build 為 1,之后上傳到 App Store碱工,那么在 App Store 中選擇版本的時候就會出現(xiàn) 6.3.2(1) 這個版本娃承,但是存儲之后,還未提交審核之前怕篷,又發(fā)現(xiàn)了一個 bug历筝,那么就需要馬上修改,這個時候 build 就要 +1匙头,或者比之前構(gòu)建的版本大才可以漫谷,比如修改完 bug,設(shè)置 build 為 2蹂析,再次上傳到 App Store舔示,這個時候在版本選擇中就會出現(xiàn)兩個構(gòu)建版本,分別是 6.3.2(1) 和 6.3.2(2)电抚,這是時候就要選擇第二個來提交審核惕稻,也就是構(gòu)建版本可以多次設(shè)置。
- Signing 就是簽名蝙叛,可以配置開發(fā)者賬號和證書信息俺祠,Team 就是設(shè)置的開發(fā)者賬號,現(xiàn)在都是 Xcode 自動管理的,所以只需要添加開發(fā)者賬號蜘渣,然后選擇就可以了淌铐,當(dāng)然你也可以設(shè)置不讓 Xcode 自動管理,這個時候就需要自己來配置一下開發(fā)者賬號了蔫缸,為了避免麻煩腿准,還是設(shè)置默認(rèn)吧。
- Deployment Info 是設(shè)置一些開發(fā)信息拾碌,Deployment Target 就是 app 在安裝的時候吐葱,所能支持的最小系統(tǒng)版本。這里可以依據(jù)公司的約定進(jìn)行設(shè)置校翔,比如要求系統(tǒng)的最低版本我 8.0弟跑,那么選擇 8.0 就可以了。我這里默認(rèn)是 10.3防症。
- App Icons and Launch Images孟辑,是 app 的圖標(biāo)和啟動圖片。
- Embedded Binaries 是添加動態(tài)框架(Framework)蔫敲,不做設(shè)置扑浸。
- Linked Frameworks and Libraries 是鏈接 Framework 和庫,和對應(yīng) Build Phases 的 Linked Frameworks and Libraries燕偶,可以手動添加其他需要用的框架,比如 libz.1.2.8.tbd础嫡,cocoapods 框架會被自動添加到項(xiàng)目里指么。
Capabilities
可以添加一些額外的功能,比如地圖榴鼎,切換到后臺模式伯诬,音頻,鑰匙串巫财,關(guān)聯(lián)相關(guān) app盗似,數(shù)據(jù)保護(hù),HomeKit平项,HealthKit赫舒,無線訪問配置,以及通知功能闽瓢,需要添加哪些功能接癌,把它打開即可。
Resourece Tags 不做設(shè)置
Info 也就是 info.plist 文件扣讼,app 的基本信息缺猛。
Build Settings 編譯設(shè)置,暫不設(shè)置
Build Phases 編譯階段的設(shè)置,暫不設(shè)置
以上就是我對 Xcode 界面的一些簡單介紹荔燎。
201 - 我的界面分析
經(jīng)過上節(jié)課的學(xué)習(xí)耻姥,我們把項(xiàng)目的大體框架搭了出來。
對比了一下項(xiàng)目的這幾個模塊有咨,我覺得『我的界面』實(shí)現(xiàn)起來還算是比較簡單的琐簇,如果一開始就實(shí)現(xiàn)比較復(fù)雜的界面,可能有些基礎(chǔ)不好的人會接受不了摔吏,所以我決定從簡單的界面實(shí)現(xiàn)鸽嫂,然后再實(shí)現(xiàn)復(fù)雜的界面,這樣一步一步地實(shí)現(xiàn)我們想要完成的項(xiàng)目征讲。
當(dāng)然這幾個模塊的關(guān)系不是很大据某,你也可以先實(shí)現(xiàn)其他模塊。
下面我們就具體分析一下诗箍,我的界面是如何實(shí)現(xiàn)出來的癣籽。
我的界面
我的界面由 UITableView
實(shí)現(xiàn),分為上下兩個部分滤祖,上邊是頭部筷狼,下邊是 cell〗惩總體來說比較簡單埂材,比較復(fù)雜的一點(diǎn)就是 cell 的數(shù)據(jù)是從后臺獲取的,不是直接寫在代碼里的汤求。
2.頭部的分析
2. cell 的分析
2.1 第一個 cell
第一個 cell 有三種種情況俏险,分為無任何關(guān)注,有一個關(guān)注扬绪,有兩個及以上關(guān)注的情況竖独,如下圖:
其中圖一是無任何關(guān)注,會隱藏『我的關(guān)注』 cell挤牛。
其中圖二是當(dāng)沒有關(guān)注用戶或者只關(guān)注了一個用戶的情況莹痢,沒有關(guān)注用戶則右側(cè)的用戶頭像和用戶昵稱則隱藏。
圖三是當(dāng)關(guān)注用戶的數(shù)量在兩個以上的情況墓赴。對于圖二的情況竞膳,我的實(shí)現(xiàn)是使用 UICollectionView
,自定義 UICollectionViewCell
竣蹦,自定義cell
時候還要區(qū)分關(guān)注的用戶是否有新動態(tài)顶猜,有新動態(tài)則顯示cell
右上角的紅點(diǎn),或
者是否是認(rèn)證用戶痘括,是認(rèn)證用戶則顯示『v』字圖標(biāo)长窄。
獲取到數(shù)據(jù)之后根據(jù)返回的數(shù)據(jù)判斷顯示哪種情況滔吠,對于請求關(guān)注用戶的接口,應(yīng)在每次界面顯示時候去請求一次挠日,這樣我的關(guān)注里的數(shù)據(jù)都是最新的數(shù)據(jù)疮绷,就會顯示剛剛關(guān)注的用戶。
2.2 其他 cell
其他 cell
就比較簡單了嚣潜,根據(jù)返回的數(shù)據(jù)冬骚,顯示到對應(yīng)的 label
上就可以了。
cell 分析就這些懂算,這個界面用到了兩個接口只冻,一個是獲取除了『我的關(guān)注』以外的 cell 數(shù)據(jù),另一個是獲取『我的關(guān)注』用戶數(shù)據(jù)计技,數(shù)據(jù)請求我使用的是第三方框架 Alamofire喜德。
根據(jù)返回的數(shù)據(jù),我的界面 cell
分為四組垮媒,其中第一組是『我的關(guān)注』舍悯,第二組是『消息通知』;第二組包括『頭條通知』和『京東特供』睡雇;第三組包括『我要爆料』萌衬,『用戶反饋』和『系統(tǒng)設(shè)置』。
然后在 Podfile 添加 Alamofire
就可以了它抱。
如下代碼:
target 'News' do
use_frameworks!
pod 'Alamofire', '~> 4.5.0' # 數(shù)據(jù)請求 https://github.com/Alamofire/Alamofire秕豫,同 AFNetworking
end
默認(rèn)生成的測試 target 不需要,可以刪掉观蓄。
在 iOS 9 中馁蒂,默認(rèn)情況下蜘腌,是不支持 http 請求的金矛,在 info.plist 添加 App Transport Security Settings 屬性饼酿,然后在添加 Allow Arbitrary Loads 設(shè)置為 YES想鹰,表示允許 http 請求喻犁。
改類為繼承自 UITableViewController
隱藏導(dǎo)航條氓皱。
新建一個 MineOtherCell.swift 文件廷区,繼承自 UITableView
玖绿,勾選 xib 文件。
新建一個 NoLoginHeaderView.swift 文件,繼承自 UIView
贪惹,然后新建一個 xib 文件丝格。
新建一個 swift 文件,命名為 MineCellModel.swift肴裙,作為模型文件宛乃。
3.頭部實(shí)現(xiàn)日間夜間功能
點(diǎn)擊夜間按鈕,app 主題會變?yōu)楹谏谝伲瑢?shí)現(xiàn)方式用的是一個第三方框架 SwiftTheme肋坚,一個不錯的主題替換方案乡括,使用起來比較簡單肃廓,該框架的介紹請看它的 github 主頁,介紹的很詳細(xì)诲泌。
讓我們來思考一個問題盲赊,就是 Swift 的核心是什么?
不知道大家有沒有看過 WWDC 2015 的視頻敷扫,其中有一個編號為 408 的視頻解釋了這個問題哀蘑,下面是視頻鏈接:Protocol-Oriented Programming in Swift。
視頻中介紹了從 OOP(面向?qū)ο缶幊蹋?到 POP(面向協(xié)議編程)的轉(zhuǎn)變過程葵第。
Swift is a Protocol-Oriented Programming Language
Swift 是一門面向協(xié)議 (POP) 開發(fā)的語言
我說一下我的體會吧绘迁,我剛開始做 iOS 開發(fā)的時候使用 OC 來開發(fā)的,后來學(xué)習(xí)了 Swift卒密,當(dāng)時也是有一搭沒一搭的學(xué)缀台,了解了一下 Swift 的基本語法,感覺還是很簡單的哮奇,因?yàn)楫?dāng)時 Swift 也是剛出來膛腐,很不穩(wěn)定,1.0 到 2.0鼎俘,甚至都不兼容哲身,所以也就沒有選擇使用 Swift 來開發(fā)。
直到去年而芥,也就是16 年律罢,在 2.2 版本出來之后,感覺 Swift 還算比較穩(wěn)定了棍丐,才決定使用 Swift 來開發(fā)一個簡單的項(xiàng)目误辑,當(dāng)時決定模仿一個 app 來練手,也就是現(xiàn)在在我的 github 上的那個項(xiàng)目了歌逢。但是現(xiàn)在看來巾钉,那個項(xiàng)目寫的不是很好,雖然是使用 Swift 來開發(fā)的秘案,但是并沒有按照 Swift 的標(biāo)準(zhǔn)來寫 Swift 的項(xiàng)目砰苍,反而是以 OC 的習(xí)慣來寫 Swift,也就是還是按照面向?qū)ο蟮乃枷雭韺?Swift阱高,雖然也能寫出可以運(yùn)行的項(xiàng)目赚导,但是面向?qū)ο蟮乃枷刖秃?Swift 的編程思想還是有本質(zhì)的區(qū)別的。這里我不想著重介紹關(guān)于 OC 這門編程語言赤惊,畢竟我們現(xiàn)在是用 Swift 來開發(fā)的吼旧,但是有些東西還是要說明一下,首先面向?qū)ο缶幊痰奶卣魇?class未舟,繼承圈暗,封裝和多態(tài)掂为,其實(shí) OC 還不能說是一門純面向?qū)ο蟮牡恼Z言,只能說 OC 是 C 語言的超集员串,或者說是 C 語言的擴(kuò)展积锅,在 C 語言的基礎(chǔ)上增加了面向?qū)ο蟮乃枷胫弁5窃?Swift 里就不一樣了瞭恰,Swift 里 class 并不是最重要的腮郊。
我前面說了 Swift 是面向協(xié)議的編程,那么究竟什么是面向協(xié)議編程呢访忿?
要回答這個問題瞧栗,我們可以參考一下剛剛提到的面向?qū)ο缶幊蹋诿嫦驅(qū)ο缶幊汤锖C菑囊粋€ class 開始的迹恐,那要是照這樣說,在面向協(xié)議編程里就是從一個 protocol 了嗎卧斟?這樣解釋對不對呢殴边?我們可以在剛剛提到視頻里找找答案,如果看過上面的視頻珍语,你會發(fā)現(xiàn)在上面的視頻中 Apple 自己都說:
"從一個 protocol 開始锤岸,別從 class 開始。" ——Dave Abrahams: 毀你三觀教授
protocol 就是協(xié)議的意思板乙。當(dāng)然是偷,可以從protocol 開始,但是從 protocol 開始了之后募逞,該怎么做呢蛋铆?
是的,這也是我們該思考的問題放接,我這里不會太著重去介紹 Swift 的基礎(chǔ)刺啦,因?yàn)槲夷J(rèn)看我視頻的同學(xué)都已經(jīng)掌握了 Swift 的基礎(chǔ)了,所以關(guān)于 protocol 的概念我也不在詳細(xì)介紹了纠脾,回到我們剛才的問題玛瘸,現(xiàn)在我們已經(jīng)有了 protocol,接下來我們要做的就是使用非常強(qiáng)大的 extension 了苟蹈,額…糊渊,關(guān)于 extension 的概念我也不再詳細(xì)介紹了,如果感覺基礎(chǔ)不好的同學(xué)可以先去看一下基礎(chǔ)慧脱,然后再來看我的視頻吧渺绒,關(guān)于 extension,可以為現(xiàn)有的 class,struct芒篷,enum,protocol 添加新功能采缚,注意剛剛我提到了 protocol针炉,所以我們先現(xiàn)在可以在 protocol 的extension 里添加任何你需要添加的東西了。
那好扳抽,功能也添加了篡帕,那怎么該怎么使用這個 protocol 呢?
這也是個問題贸呢,讓我們再分析一下镰烧,protocol 不同于 class 或者 struct,因?yàn)楹髢烧呖梢愿髯哉{(diào)用它們的類型方法或者實(shí)例方法楞陷,但是 protocol 卻不能直接使用怔鳖,也不能實(shí)例化,既然都不行固蛾,那該怎么做敖嶂础?別著急艾凯,既然不能直接用献幔,那我們就要考慮用上面提到的 class 或者 struct 了,那我們該用哪個呢趾诗?我們先來看一張圖:
這張圖是我在網(wǎng)上找到的一篇文章中的截圖蜡感,下面是文章地址: 不要用子類!Swift的核心是面向協(xié)議 恃泪,雖然這篇文章是2015年的文章了郑兴,不過還是推薦大家看一下。在上面的圖中悟泵,可以看出在 Swift 的標(biāo)準(zhǔn)庫中杈笔,僅有 4 個class,其余下的有 87 個 struct 和 8 個 enum 的實(shí)例共同構(gòu)建了 Swift 功能的核心糕非。如今已經(jīng)過去兩年蒙具,我想 struct 的數(shù)量應(yīng)該更多了。既然 Swift 里用了這么多 struct朽肥,為什么我們不試試用 struct 呢禁筏?
我們前面也說過了 class 是面向?qū)ο罄锏臇|西,那我們試試用 struct衡招,現(xiàn)在可以新建一個 struct篱昔,然后讓它遵守我們的 protocol 就可以了,之后就可以實(shí)例化一個 struct,接著就可以用 struct 調(diào)用 protocol 里的方法或者屬性了州刽。
聽上去還不是錯的空执,但是總感覺是不是有點(diǎn)太麻煩了,要是按照上面說的穗椅,我們直接創(chuàng)建一個 struct 不就完了嘛辨绊,還要 protocol 干什么,這么說聽上去也沒有問題匹表,當(dāng)然在開發(fā)中也是可以的.
但是我們還要考慮一個問題门坷,在實(shí)際開發(fā)中我們是不是只有 struct 呢?
當(dāng)然不是袍镀,因?yàn)槲覀冞€要和 cocoa 框架打交道默蚌,說到 cocoa 框架,我們還要提一下 UIKit 這個框架苇羡,這是 iOS 開發(fā)中一個十分重要的框架绸吸,但是由于歷史關(guān)系,為了兼容 OC宣虾,UIKit 里的類都是繼承自 NSObject 的惯裕,也就是說都是 class 類型的,比如在開發(fā)中有幾十個控制器都繼承自某個自定義的基類绣硝,就會把基類的所有的方法也繼承下來蜻势,但是這些方法對每一個子類都有用嗎?答案肯定是否定的鹉胖。所以握玛,既然子類不需要,何必要繼承父類的方法呢甫菠?自己的方法應(yīng)該由自己決定才對的挠铲,而現(xiàn)在是基類幫著子類決定了它的方法。
所以這樣就引出了 protocol寂诱,讓自己的類實(shí)現(xiàn)自己所要遵守的 protocol拂苹,這里我說的并不是某一個 class,我這里指的是有那么幾個 class 都要實(shí)現(xiàn)功能的時候痰洒,選擇用 protocol 是個不錯的選擇瓢棒,而且還可以把幾個方法抽象成一個方法,需要的 class 只需要遵守這個 protocol 就可以了丘喻。這樣解釋可能不太清楚脯宿,我舉一個栗子。
當(dāng)我自定義 UIView 的時候泉粉,我想讓 view 從 xib 加載连霉,那么我就需要在每個類里都寫一個從 xib 加載的類方法榴芳,如下代碼:
static func classMethodCreateView() -> MyCustomView {
return Bundle.main.loadNibNamed("\(self)", owner: nil, options: nil)?.last as! MyCustomView
}
這樣在每個代碼都寫一,很是麻煩跺撼,有什么方法可以簡單一點(diǎn)嗎窟感?方法當(dāng)然是有的,可以做一下優(yōu)化歉井,如下代碼:
protocol LoadNibProtocol {}
extension LoadNibProtocol where Self: UIView {
/// 提供加載 Xib 方法
static func loadViewFromNib(name: String? = nil) -> Self {
return Bundle.main.loadNibNamed(name ?? "(self)", owner: nil, options: nil)?.last as! Self
}
}
接下來讓需要從 xib 加載的 view 遵守 LoadNibProtocol 協(xié)議就可以了肌括,是不是簡單了許多呢?上面只是 protocol 的一個簡單應(yīng)用酣难,在后面的項(xiàng)目中,我會介紹其他用法黑滴,這里就不再過多說明了憨募,關(guān)于協(xié)議暫時先介紹這么多。下面還有一個問題袁辈,需要思考一下菜谣,就是 Swift 里既然有 class 和 struct,那么他們的區(qū)別是什么呢晚缩?
- 我想大多數(shù)人的第一反應(yīng)應(yīng)該是 struct 是值類型 class 是引用類型尾膊,也就是說 struct 的實(shí)例在被賦予變量或者常量或者被函數(shù)調(diào)用時都會被復(fù)制,但是 class 的實(shí)例會被引用荞彼,引用的就是已經(jīng)存在的實(shí)例本身而不是復(fù)制冈敛。還可以這樣理解 struct 的復(fù)制相當(dāng)于在內(nèi)存上又開辟了一塊內(nèi)存空間,和之前的 struct 沒有關(guān)系了鸣皂,我個人感覺也可以理解成深拷貝抓谴,而 class 則是創(chuàng)建一個指針,指向的還是原來的內(nèi)存地址寞缝,可以理解成淺拷貝癌压。
- class 可以繼承,struct 不能繼承荆陆,某些需要繼承的地方還是需要用 class滩届,不能用 struct。
- struct 類型方法要加 static修飾被啼,class類型方法要加 class 修飾帜消。
- struct 有默認(rèn)的初始化方法,class 需要指定變量的初始值趟据。
下面代碼關(guān)于 class 和 struct 的在初始化的時候的一些區(qū)別券犁。
struct MyStruct1 {
var text: String
var tip: Int
}
struct MyStruct2 {
var text: String = "MyStruct2"
var tip: Int = 2
}
struct MyStruct3 {
var text: String
var tip: Int
init(text: String, tip: Int) {
self.text = text
self.tip = tip
}
}
class MyClass1 {
var text: String = "MyClass1"
var tip: Int = 11
}
class MyClass2 {
var text: String
var tip: Int
init() {
text = "MyClass2"
tip = 22
}
}
class MyClass3 {
var text: String
var tip: Int
init(text: String, tip: Int) {
self.text = text
self.tip = tip
}
}
let myStruct1 = MyStruct1(text: "MyStruct1", tip: 1)
let myStruct2 = MyStruct2()
let myStruct3 = MyStruct3(text: "MyStruct3", tip: 3)
let myClass1 = MyClass1()
let myClass2 = MyClass2()
let myClass3 = MyClass3(text: "MyClass3", tip: 33)
還有一點(diǎn),就是關(guān)于 struct 和 class 的性能差異汹碱,可以閱讀下面的文章:理解Swift中struct和class在不同情況下性能的差異粘衬,文章介紹的很詳細(xì),我這里也不再詳細(xì)介紹了。 上面是我對 struct 和 class 做的簡單說明稚新,以及 Swift 面向協(xié)議編程的簡單說明勘伺,如果還覺得意猶未盡,或者想了解更多內(nèi)容褂删,請自行去網(wǎng)上找找相關(guān)文章飞醉。 說了這么多,最后還是希望你們能明白 Swift 是面向協(xié)議的編程屯阀, 在開發(fā)過程中請多使用 struct 和 protocol缅帘,當(dāng)你沒有選擇的時候再使用 class。
新增一篇參考文章:面向協(xié)議的 MVVM 架構(gòu)介紹难衰。這篇文章也比較早了钦无。
下面我們就繼續(xù)寫代碼吧。
首先新建兩個 Swift 文件盖袭,一個命名為 MyCellModel.Swift失暂,作為我的界面 cell 的模型。
另一個命名為 NetworkTool.Swift鳄虱,作為網(wǎng)絡(luò)請求的相關(guān)文件弟塞。
然后在 Podfile 添加我們需要的第三方框架,分別是 Alamofire
拙已,SwiftyJSON
决记,HandyJSON
。
如下代碼:
target 'News' do
use_frameworks!
pod 'Alamofire', '~> 4.5.0' # 數(shù)據(jù)請求 https://github.com/Alamofire/Alamofire倍踪,同 AFNetworking
pod 'HandyJSON', '~> 1.7.2' # JSON序列化/反序列化庫 https://github.com/alibaba/HandyJSON/
pod 'SwiftyJSON' # json 解析 https://github.com/SwiftyJSON/
end
默認(rèn)生成的測試 target 先不需要霉涨,可以刪掉。關(guān)于上面的第三方框架可以去 github 看一下他們的介紹和用法惭适,我這里就不詳細(xì)說明了笙瑟,看我是怎么寫的就可以了,跟著我寫癞志,寫著寫著就知道怎么用了往枷。
讓我們來思考一個問題,就是 Swift 的核心是什么凄杯?
不知道大家有沒有看過 WWDC 2015 的視頻错洁,其中有一個編號為 408 的視頻解釋了這個問題,下面是視頻鏈接:Protocol-Oriented Programming in Swift戒突。
視頻中介紹了從 OOP(面向?qū)ο缶幊蹋?到 POP(面向協(xié)議編程)的轉(zhuǎn)變過程屯碴。
Swift is a Protocol-Oriented Programming Language
Swift 是一門面向協(xié)議 (POP) 開發(fā)的語言
我說一下我的體會吧,我剛開始做 iOS 開發(fā)的時候使用 OC 來開發(fā)的膊存,后來學(xué)習(xí)了 Swift导而,當(dāng)時也是有一搭沒一搭的學(xué)忱叭,了解了一下 Swift 的基本語法,感覺還是很簡單的今艺,因?yàn)楫?dāng)時 Swift 也是剛出來韵丑,很不穩(wěn)定,1.0 到 2.0虚缎,甚至都不兼容撵彻,所以也就沒有選擇使用 Swift 來開發(fā)。
直到去年实牡,也就是16 年陌僵,在 2.2 版本出來之后,感覺 Swift 還算比較穩(wěn)定了创坞,才決定使用 Swift 來開發(fā)一個簡單的項(xiàng)目拾弃,當(dāng)時決定模仿一個 app 來練手,也就是現(xiàn)在在我的 github 上的那個項(xiàng)目了摆霉。但是現(xiàn)在看來,那個項(xiàng)目寫的不是很好奔坟,雖然是使用 Swift 來開發(fā)的携栋,但是并沒有按照 Swift 的標(biāo)準(zhǔn)來寫 Swift 的項(xiàng)目,反而是以 OC 的習(xí)慣來寫 Swift咳秉,也就是還是按照面向?qū)ο蟮乃枷雭韺?Swift婉支,雖然也能寫出可以運(yùn)行的項(xiàng)目,但是面向?qū)ο蟮乃枷刖秃?Swift 的編程思想還是有本質(zhì)的區(qū)別的澜建。這里我不想著重介紹關(guān)于 OC 這門編程語言向挖,畢竟我們現(xiàn)在是用 Swift 來開發(fā)的,但是有些東西還是要說明一下炕舵,首先面向?qū)ο缶幊痰奶卣魇?class何之,繼承,封裝和多態(tài)咽筋,其實(shí) OC 還不能說是一門純面向?qū)ο蟮牡恼Z言溶推,只能說 OC 是 C 語言的超集,或者說是 C 語言的擴(kuò)展奸攻,在 C 語言的基礎(chǔ)上增加了面向?qū)ο蟮乃枷搿5窃?Swift 里就不一樣了辐赞,Swift 里 class 并不是最重要的硝训。
我前面說了 Swift 是面向協(xié)議的編程新思,那么究竟什么是面向協(xié)議編程呢表牢?
要回答這個問題,我們可以參考一下剛剛提到的面向?qū)ο缶幊檀扌耍诿嫦驅(qū)ο缶幊汤铮菑囊粋€ class 開始的蛔翅,那要是照這樣說敲茄,在面向協(xié)議編程里就是從一個 protocol 了嗎?這樣解釋對不對呢山析?我們可以在剛剛提到視頻里找找答案堰燎,如果看過上面的視頻,你會發(fā)現(xiàn)在上面的視頻中 Apple 自己都說:
"從一個 protocol 開始笋轨,別從 class 開始秆剪。" ——Dave Abrahams: 毀你三觀教授
protocol 就是協(xié)議的意思。當(dāng)然爵政,可以從protocol 開始仅讽,但是從 protocol 開始了之后,該怎么做呢钾挟?
是的洁灵,這也是我們該思考的問題,我這里不會太著重去介紹 Swift 的基礎(chǔ)掺出,因?yàn)槲夷J(rèn)看我視頻的同學(xué)都已經(jīng)掌握了 Swift 的基礎(chǔ)了徽千,所以關(guān)于 protocol 的概念我也不在詳細(xì)介紹了,回到我們剛才的問題汤锨,現(xiàn)在我們已經(jīng)有了 protocol双抽,接下來我們要做的就是使用非常強(qiáng)大的 extension 了,額…闲礼,關(guān)于 extension 的概念我也不再詳細(xì)介紹了荠诬,如果感覺基礎(chǔ)不好的同學(xué)可以先去看一下基礎(chǔ),然后再來看我的視頻吧位仁,關(guān)于 extension钧嘶,可以為現(xiàn)有的 class有决,struct书幕,enum苛骨,protocol 添加新功能痒芝,注意剛剛我提到了 protocol严衬,所以我們先現(xiàn)在可以在 protocol 的extension 里添加任何你需要添加的東西了请琳。
那好,功能也添加了,那怎么該怎么使用這個 protocol 呢局冰?
這也是個問題康二,讓我們再分析一下,protocol 不同于 class 或者 struct产雹,因?yàn)楹髢烧呖梢愿髯哉{(diào)用它們的類型方法或者實(shí)例方法,但是 protocol 卻不能直接使用瘟判,也不能實(shí)例化拷获,既然都不行赢笨,那該怎么做爸视?別著急九昧,既然不能直接用铸鹰,那我們就要考慮用上面提到的 class 或者 struct 了,那我們該用哪個呢剖毯?我們先來看一張圖:
這張圖是我在網(wǎng)上找到的一篇文章中的截圖,下面是文章地址: 不要用子類胶滋!Swift的核心是面向協(xié)議 究恤,雖然這篇文章是2015年的文章了,不過還是推薦大家看一下窟赏。在上面的圖中涯穷,可以看出在 Swift 的標(biāo)準(zhǔn)庫中作煌,僅有 4 個class粟誓,其余下的有 87 個 struct 和 8 個 enum 的實(shí)例共同構(gòu)建了 Swift 功能的核心。如今已經(jīng)過去兩年悲酷,我想 struct 的數(shù)量應(yīng)該更多了设易。既然 Swift 里用了這么多 struct,為什么我們不試試用 struct 呢屠尊?
我們前面也說過了 class 是面向?qū)ο罄锏臇|西知染,那我們試試用 struct,現(xiàn)在可以新建一個 struct嫌吠,然后讓它遵守我們的 protocol 就可以了凭戴,之后就可以實(shí)例化一個 struct者冤,接著就可以用 struct 調(diào)用 protocol 里的方法或者屬性了涉枫。
聽上去還不是錯的愿汰,但是總感覺是不是有點(diǎn)太麻煩了,要是按照上面說的吗跋,我們直接創(chuàng)建一個 struct 不就完了嘛小腊,還要 protocol 干什么秩冈,這么說聽上去也沒有問題,當(dāng)然在開發(fā)中也是可以的.
但是我們還要考慮一個問題芬失,在實(shí)際開發(fā)中我們是不是只有 struct 呢?
當(dāng)然不是颊糜,因?yàn)槲覀冞€要和 cocoa 框架打交道衬鱼,說到 cocoa 框架,我們還要提一下 UIKit 這個框架抛蚤,這是 iOS 開發(fā)中一個十分重要的框架岁经,但是由于歷史關(guān)系朽们,為了兼容 OC骑脱,UIKit 里的類都是繼承自 NSObject 的叁丧,也就是說都是 class 類型的,比如在開發(fā)中有幾十個控制器都繼承自某個自定義的基類稚瘾,就會把基類的所有的方法也繼承下來,但是這些方法對每一個子類都有用嗎些椒?答案肯定是否定的免糕。所以,既然子類不需要尼斧,何必要繼承父類的方法呢?自己的方法應(yīng)該由自己決定才對的烛恤,而現(xiàn)在是基類幫著子類決定了它的方法。
所以這樣就引出了 protocol币喧,讓自己的類實(shí)現(xiàn)自己所要遵守的 protocol,這里我說的并不是某一個 class史翘,我這里指的是有那么幾個 class 都要實(shí)現(xiàn)功能的時候,選擇用 protocol 是個不錯的選擇洪唐,而且還可以把幾個方法抽象成一個方法脉让,需要的 class 只需要遵守這個 protocol 就可以了。這樣解釋可能不太清楚滚澜,我舉一個栗子。
當(dāng)我自定義 UIView 的時候萝招,我想讓 view 從 xib 加載,那么我就需要在每個類里都寫一個從 xib 加載的類方法纽窟,如下代碼:
static func classMethodCreateView() -> MyCustomView {
return Bundle.main.loadNibNamed("\(self)", owner: nil, options: nil)?.last as! MyCustomView
}
這樣在每個代碼都寫一,很是麻煩审孽,有什么方法可以簡單一點(diǎn)嗎?方法當(dāng)然是有的搓萧,可以做一下優(yōu)化次和,如下代碼:
protocol LoadNibProtocol {}
extension LoadNibProtocol where Self: UIView {
/// 提供加載 Xib 方法
static func loadViewFromNib(name: String? = nil) -> Self {
return Bundle.main.loadNibNamed(name ?? "(self)", owner: nil, options: nil)?.last as! Self
}
}
接下來讓需要從 xib 加載的 view 遵守 LoadNibProtocol 協(xié)議就可以了,是不是簡單了許多呢踏施?上面只是 protocol 的一個簡單應(yīng)用石蔗,在后面的項(xiàng)目中,我會介紹其他用法畅形,這里就不再過多說明了养距,關(guān)于協(xié)議暫時先介紹這么多。下面還有一個問題日熬,需要思考一下棍厌,就是 Swift 里既然有 class 和 struct,那么他們的區(qū)別是什么呢弄慰?
- 我想大多數(shù)人的第一反應(yīng)應(yīng)該是 struct 是值類型 class 是引用類型第献,也就是說 struct 的實(shí)例在被賦予變量或者常量或者被函數(shù)調(diào)用時都會被復(fù)制,但是 class 的實(shí)例會被引用蔫慧,引用的就是已經(jīng)存在的實(shí)例本身而不是復(fù)制。還可以這樣理解 struct 的復(fù)制相當(dāng)于在內(nèi)存上又開辟了一塊內(nèi)存空間侮繁,和之前的 struct 沒有關(guān)系了彬祖,我個人感覺也可以理解成深拷貝,而 class 則是創(chuàng)建一個指針淡喜,指向的還是原來的內(nèi)存地址,可以理解成淺拷貝嚼鹉。
- class 可以繼承浑侥,struct 不能繼承尖昏,某些需要繼承的地方還是需要用 class巍沙,不能用 struct。
- struct 類型方法要加 static修飾厦滤,class類型方法要加 class 修飾。
- struct 有默認(rèn)的初始化方法凿滤,class 需要指定變量的初始值爵川。
下面代碼關(guān)于 class 和 struct 的在初始化的時候的一些區(qū)別。
struct MyStruct1 {
var text: String
var tip: Int
}
struct MyStruct2 {
var text: String = "MyStruct2"
var tip: Int = 2
}
struct MyStruct3 {
var text: String
var tip: Int
init(text: String, tip: Int) {
self.text = text
self.tip = tip
}
}
class MyClass1 {
var text: String = "MyClass1"
var tip: Int = 11
}
class MyClass2 {
var text: String
var tip: Int
init() {
text = "MyClass2"
tip = 22
}
}
class MyClass3 {
var text: String
var tip: Int
init(text: String, tip: Int) {
self.text = text
self.tip = tip
}
}
let myStruct1 = MyStruct1(text: "MyStruct1", tip: 1)
let myStruct2 = MyStruct2()
let myStruct3 = MyStruct3(text: "MyStruct3", tip: 3)
let myClass1 = MyClass1()
let myClass2 = MyClass2()
let myClass3 = MyClass3(text: "MyClass3", tip: 33)
還有一點(diǎn)刻恭,就是關(guān)于 struct 和 class 的性能差異,可以閱讀下面的文章:理解Swift中struct和class在不同情況下性能的差異瓮顽,文章介紹的很詳細(xì),我這里也不再詳細(xì)介紹了。 上面是我對 struct 和 class 做的簡單說明沈跨,以及 Swift 面向協(xié)議編程的簡單說明,如果還覺得意猶未盡,或者想了解更多內(nèi)容,請自行去網(wǎng)上找找相關(guān)文章。 說了這么多组橄,最后還是希望你們能明白 Swift 是面向協(xié)議的編程缨历, 在開發(fā)過程中請多使用 struct 和 protocol以蕴,當(dāng)你沒有選擇的時候再使用 class。
新增一篇參考文章:面向協(xié)議的 MVVM 架構(gòu)介紹辛孵。這篇文章也比較早了丛肮。
下面我們就繼續(xù)寫代碼吧。
首先新建兩個 Swift 文件魄缚,一個命名為 MyCellModel.Swift宝与,作為我的界面 cell 的模型。
另一個命名為 NetworkTool.Swift冶匹,作為網(wǎng)絡(luò)請求的相關(guān)文件习劫。
然后在 Podfile 添加我們需要的第三方框架,分別是 Alamofire
嚼隘,SwiftyJSON
诽里,HandyJSON
。
如下代碼:
target 'News' do
use_frameworks!
pod 'Alamofire', '~> 4.5.0' # 數(shù)據(jù)請求 https://github.com/Alamofire/Alamofire嗓蘑,同 AFNetworking
pod 'HandyJSON', '~> 1.7.2' # JSON序列化/反序列化庫 https://github.com/alibaba/HandyJSON/
pod 'SwiftyJSON' # json 解析 https://github.com/SwiftyJSON/
end
默認(rèn)生成的測試 target 先不需要须肆,可以刪掉匿乃。關(guān)于上面的第三方框架可以去 github 看一下他們的介紹和用法桩皿,我這里就不詳細(xì)說明了,看我是怎么寫的就可以了幢炸,跟著我寫泄隔,寫著寫著就知道怎么用了。
212 - Any 和 AnyObject 的分析
我下面的代碼中聲明了一個 struct宛徊,一個 class佛嬉,一個閉包,一個無返回值的方法闸天,一個有返回值的方法以及一個枚舉:
struct MyStruct {
var text: String = "MyStruct"
var tip: Int = 1
}
class MyClass {
var text: String = "MyClass"
var tip: Int = 11
}
/// 閉包
typealias closure = (Int, Int) -> ()
/// 無返回值的方法
func testNoReturn() {
print("testNoReturn")
}
/// 有返回值的方法
func testReturn(a: Int) -> Int {
return a
}
/// 枚舉
enum TestEnum: String {
case language1 = "Swfift"
case language2 = "Objective-C"
}
let myStruct = MyStruct()
let myClass = MyClass()
如下代碼:
// let array7: [AnyObject] = [1, "2", myStruct1, myClass1, TestEnum.language1]
let array7: [AnyObject] = [1 as AnyObject, "2" as AnyObject, myStruct1 as AnyObject, myClass1, TestEnum.language1 as AnyObject]
// print [1, "2", {NSObject}, {text "MyClass", tip 11}, {NSObject}]
// 上面的打印是在 Playground 中的結(jié)果暖呕。
let array8: [Any] = [1, "2", myStruct1, myClass1, testNoReturn,testReturn(a: 1), closure.self, TestEnum.language1]
// print [1, "2", {text "MyStruct", tip 1}, {text "MyClass", tip 11}, () -> (), 1, ((Int, Int) -> ()).Type, language1]
// -----------------print array7-------------------------
for item in array7 {
if item is Int {
print("\(item) is Int!")
} else if item is NSNumber {
print("\(item) is NSNumber!")
}
if item is NSString {
print("\(item) is NSString!")
}
if item is NSString {
print("\(item) is NSString!")
}
}
// 1 is Int!
// 1 is NSNumber!
// 2 is String!
// 2 is NSString!
// 上面的代碼打印整數(shù),并且打印了兩行苞氮,說明 1 既是 Int 類型的湾揽,又是 NSNumber 類型的。
// 上面的代碼打印字符串,并且打印了兩行库物,說明字符串既是 String 類型的霸旗,又是 NSString 類型的。
// 那么這個 AnyObject 究竟是什么呢戚揭?
// AnyObject 是一個成員為空的協(xié)議诱告,所有 class 都隱式地實(shí)現(xiàn)了這個協(xié)議,
// 這也限制了 AnyObject 是只適用于 class 類型的原因民晒,相當(dāng)于 OC 中的 id 類型精居,
// Swift 為了與 Cocoa 架構(gòu)進(jìn)行協(xié)作開發(fā),就將原來的 id 用 AnyObject 來進(jìn)行替代潜必。
// 也就是說在聲明的時候箱蟆,僅僅是把字符串強(qiáng)轉(zhuǎn)成了 AnyObject 類型,所以在判斷的時候刮便,編譯器并不知道具體的聲明類型空猜,所以就會打印兩種結(jié)果,
// 但是在 Swift 中 String 是結(jié)構(gòu)體類型的恨旱,也就是說辈毯,當(dāng)轉(zhuǎn)成 AnyObject 類型之后
// 系統(tǒng)默認(rèn)把 String 轉(zhuǎn)成了 NSObject 類型,這就說明 Swift 和 Objective-C 存在相互轉(zhuǎn)化
// 并且這種轉(zhuǎn)化是自動的搜贤,并且實(shí)現(xiàn)了 Swift 和 Objective-C 的無縫橋接谆沃。
// 上面的解釋只是我個人的理解,如有不對的地方仪芒,還請指出唁影。
// -----------------print array8-------------------------
for item in array8 {
if item is Int {
print("\(item) is Int!")
}
if item is String {
print("\(item) is String!")
}
if item is NSString {
print("\(item) is NSString!")
}
}
// 1 is Int!
// 1 is NSNumber!
// 2 is String!
// 2 is NSString!
// Any 是一個空協(xié)議集合的別名,它表示沒有實(shí)現(xiàn)任何協(xié)議掂名,可以表示任意類型据沈,包括 class, struct 和 enum 在內(nèi)的所有類型饺蔑,甚至包括方法 (func) 類型锌介。
AnyClass 是 AnyObject.Type 的別名而已。
參考文章
Any vs. AnyObject in Swift 3.0
下面這篇文章是比較老的文章猾警,可以參考孔祸,但是 Swift3 中很多地方不如文章所述。