寫作原因:因?yàn)榈谝环莨ぷ饔行液?年iOS經(jīng)驗(yàn)上司一起從頭開始寫項(xiàng)目(項(xiàng)目持續(xù)了半年),所以對(duì)于項(xiàng)目架構(gòu)有點(diǎn)感悟孙蒙,在這里獻(xiàn)給大家(是自己寫的項(xiàng)目项棠,但是90%還原上司項(xiàng)目基礎(chǔ)架構(gòu),數(shù)據(jù)庫從CoreData換成了Realm挎峦,不過寫法和功能甚至都一樣)香追,先給一個(gè)碼云地址(github不知道抽什么風(fēng),提交不上去)
推薦閱讀:iOS開發(fā)——2019 最新 BAT面試題合集(持續(xù)更新中)
https://git.oschina.net/liyongshi.com/BaseiOSProject.git坦胶;你們下載下載后可以直接以此開始寫你們的項(xiàng)目(已經(jīng)拿來寫過三個(gè)公司項(xiàng)目了:快活透典、快照和幫幫管理助手(這個(gè)的源碼在我的github地址上))晴楔,所以肯定是沒問題的,常用的第三方峭咒、分類税弃、工具、網(wǎng)絡(luò)封裝(基于AFN)和本地緩存(realm)等常用的都已經(jīng)做好了(UtilKits.h中可查看)凑队;并且我已經(jīng)把業(yè)務(wù)的前兩層做好了(就是登陸则果、歡迎界面、首頁轉(zhuǎn)換邏輯漩氨,只供參考你們可以修改為你們的西壮,第三步的需要自己跟著寫下代碼),你們可以按照這個(gè)思路來搭建頁面和業(yè)務(wù)叫惊;這個(gè)系列總體分為兩部分:介紹和使用
作為一個(gè)開發(fā)者款青,有一個(gè)學(xué)習(xí)的氛圍跟一個(gè)交流圈子特別重要,這是一個(gè)我的iOS交流群:638302184霍狰,不管你是小白還是大牛歡迎入駐 抡草,分享BAT,阿里面試題、面試經(jīng)驗(yàn)蔗坯,討論技術(shù)康震, 大家一起交流學(xué)習(xí)成長(zhǎng)!
群內(nèi)提供數(shù)據(jù)結(jié)構(gòu)與算法步悠、底層進(jìn)階签杈、swift、逆向鼎兽、整合面試題等免費(fèi)資料
附上一份收集的各大廠面試題(附答案) ! 群文件直接獲取
各大廠面試題
看前須知:1:里面的代碼我是為了截圖才故意寫的很緊湊;2:如果你有一些自己的習(xí)慣在里面答姥,比如類的nonull宏,那么你可以加上谚咬,基礎(chǔ)工程代碼很少鹦付,加上也不費(fèi)時(shí)間;3:類前綴這個(gè)我沒有加(之前加過,被上司叫重寫了)實(shí)在抱歉;4:互相借鑒择卦,重在參與;5:確實(shí)少了數(shù)據(jù)操作的線程管理敲长,謝謝簡(jiǎn)友提醒
第一步:先瞄一下目錄結(jié)構(gòu)
目錄結(jié)構(gòu)
UtilKits目錄:顧名思義,這個(gè)目錄用來存放分類秉继、非pod導(dǎo)入的第三方庫和設(shè)備信息等“輔助”元素祈噪,比如你還可以建立一個(gè)文件夾ProductInfo表示工程信息等等,然后全局文件(分類尚辑、AFN等)在UtilKits.h中導(dǎo)入辑鲤;
AppDelegate目錄:里面就是放了AppDelegate.h和AppDelegate.m文件,另起文件夾保留目錄層級(jí)杠茬;
AppCustoms目錄:自己寫的輪子放到這里月褥,比如圖片多選等
ModelManager目錄:存放模型弛随、請(qǐng)求和模型管理,如上圖中的Models中放模型宁赤,IdentityHttp是登錄相關(guān)的請(qǐng)求(因?yàn)椴挥脦胗脩粜畔ⅲ┮ㄍ福琁dentityManager操作緩存登錄數(shù)據(jù)(讀取、保存)和登錄相關(guān)行為(退出)决左;其實(shí)看UserManager更一目了然愕够,但是太長(zhǎng)了截圖截不完,你們可以看看UserManager
InterfaceService目錄:網(wǎng)路請(qǐng)求封裝和地址宏
MainViewController目錄:這里就是你的界面了哆窿,按照權(quán)限來存放链烈,比如MainViewController管理登錄(LoginController)、歡迎界面(WelcomeController)和登錄后的界面(BusinessController)挚躯,那么這三個(gè)文件夾就作為MainViewController目錄的子目錄,采用addChildViewController進(jìn)行管理
好了擦秽,目錄結(jié)構(gòu)介紹完了码荔,你們可以各取所需;比如你想要個(gè)定位管理器感挥,那么你在和ModelManager同層目錄建一個(gè)LocationManager即可缩搅,然后你有其他的分類分別放入對(duì)應(yīng)的文件夾即可;當(dāng)然了還有很多比如第三方登錄那些触幼,你自己在相應(yīng)位置加上就可以了(因?yàn)椴灰欢ㄋ许?xiàng)目都有三方登錄硼瓣,但是所有工程都有這個(gè)基礎(chǔ)工程中的內(nèi)容,這里給的的不就是一個(gè)基礎(chǔ)工程嗎)
第二步:部分值得說的思想
1:本地?cái)?shù)據(jù)庫采用realm(你可能會(huì)問了置谦,現(xiàn)在很多數(shù)據(jù)庫啊堂鲤,為啥這都要說),我們知道CoreData的強(qiáng)大是因?yàn)樗袀€(gè)NSFetchedResultsController和能直接存入對(duì)象(來自上司原話)媒峡,存/取對(duì)象目前來說是基本要求了瘟栖,那么你知道NSFetchedResultsController嗎(不知道的去谷歌吧),然后我找了一下除了CoreData之外還有FMDB和realm有類似的庫支持谅阿,但是FMDB不能直接存取對(duì)象我直接拋棄了半哟,然后我找到了realm,它有一個(gè)專有庫RBQFetchedResultsController來實(shí)現(xiàn) CoreData的NSFetchedResultsController效果签餐,這個(gè)非常有用寓涨,讓我們可以輕松的做到界面實(shí)時(shí)顯示最新數(shù)據(jù)(這才是我選realm的原因,前提是很難掌握CoreData)氯檐;
2:本地?cái)?shù)據(jù)存取每個(gè)用戶是一個(gè)單獨(dú)的文件夾戒良,比如用戶id為1的有一個(gè)名為1文件夾,id為2的有一個(gè)名為2的文件夾男摧,然后公共的數(shù)據(jù)可以放到名為0的文件夾然后單獨(dú)一個(gè)manager管理(因?yàn)閡sermanager是綁定了固定用戶私有數(shù)據(jù)的)蔬墩;有的軟件有游客身份译打,其實(shí)就是讀取的默認(rèn)文件夾0/default的數(shù)據(jù)(按照我這種思想),讀取的時(shí)候可以看到UserManager中l(wèi)oadUserWithNo:函數(shù)就可以讀取到對(duì)應(yīng)用戶數(shù)據(jù)了拇颅,而且這樣做可以很容易遷移數(shù)據(jù)(我經(jīng)歷過舊數(shù)據(jù)遷移的疼奏司,從最開始杜絕問題);
3:網(wǎng)絡(luò)請(qǐng)求返回的數(shù)據(jù)和界面顯示需要的數(shù)據(jù)不直接關(guān)聯(lián)樟插,他們和本地?cái)?shù)據(jù)庫直接關(guān)聯(lián)韵洋,這是非常重要的一個(gè)思想,我們最開始的時(shí)候請(qǐng)求回調(diào)中直接把數(shù)據(jù)賦值給tableview的datasource黄锤,然后reloaddata還記得嗎搪缨?這很容易造成顯示錯(cuò)誤,最明顯的就是幾個(gè)頁面都對(duì)同一個(gè)對(duì)象數(shù)組進(jìn)行操作會(huì)很難把控鸵熟,所以我們正確的思路應(yīng)該是:數(shù)據(jù)請(qǐng)求得到的數(shù)據(jù)我們直接存入數(shù)據(jù)庫然后就可以結(jié)束了副编,數(shù)據(jù)庫得到數(shù)據(jù)后發(fā)送一個(gè)回調(diào)(RBQFetchedResultsController的作用),然后所有RBQFetchedResultsController監(jiān)聽的頁面重新從本地獲取最新數(shù)據(jù)再賦值給tableview的datasource后reloaddata流强,這樣的話請(qǐng)求就是請(qǐng)求沒有其他的操作痹届,數(shù)據(jù)變化監(jiān)聽由數(shù)據(jù)庫特性發(fā)出;
4:權(quán)限(包括業(yè)務(wù)層次結(jié)構(gòu)和manager提供的接口合理性等)队腐,我們?cè)诠径际歉魉酒渎殻荒茉綑?quán)柴淘,那么同樣的軟件也是有生命的(可以,很強(qiáng)勢(shì))梗脾;至少我們不能犯這種低級(jí)錯(cuò)誤:我們的軟件基本都是有退出登錄功能的炸茧,那么你至少不應(yīng)該在退出登錄點(diǎn)擊事件執(zhí)行self.window.rootViewcontroller = [LoginViewcontroller new]梭冠,因?yàn)閟elf.window.rootViewcontroller是固定的MainViewController,這個(gè)操作應(yīng)該是發(fā)通知到MainViewController讓它移除BusinessController(退出按鈕肯定在這里面)并加載LoginController,這就是所謂的越權(quán)了偶翅;還有就是如果tableviewcell上的一個(gè)按鈕點(diǎn)擊后導(dǎo)航控制器要push到指定界面聚谁,你至少應(yīng)該是tableviewcell的代理對(duì)象(一直到UIViewController)再push(雖然有人提出一個(gè)self-manager模式,但是我是怎么都不情愿那么做的)到指定界面朵耕;其他的還有很多阎曹,做之前想想應(yīng)該是誰來做怎么做就可以了;
5:少用輪子盡量拆輪子,1:輪子大多數(shù)是綜合考慮的桐早,所以體積會(huì)很大而且里面的邏輯可能還會(huì)和你現(xiàn)有的邏輯沖突,比如解決鍵盤遮擋的IQKeyboardManager是檢測(cè)鍵盤的彈起于消失然后使window的frame改變了陶衅,那么如果你自己對(duì)window有其他的操作就會(huì)沖突搀军,2:用著是挺舒服的,但是你會(huì)慢慢的忘記很多知識(shí)门烂,比如圖片多選我們喜歡網(wǎng)上去找輪子但是又和自己的需求不一樣于是又去找另外一個(gè)蔓姚,其實(shí)自己做比找快多了其實(shí)我們都會(huì)做只是懶坡脐,其實(shí)你把輪子看懂自己拆分取其中一部分也是好的
第三步:做一個(gè)小demo展示界面顯示實(shí)時(shí)刷新
其實(shí)大部分我們都寫過,這里我只講一下RBQFetchedResultsController怎么使用浅役,RBQFetchedResultsController能解決什么問題觉既?1:界面實(shí)時(shí)刷新,我們模型改變了通常是發(fā)一個(gè)通知然后要實(shí)時(shí)的界面接收通知符欠,那么你有想過一個(gè)界面有很多模型的恐怖嗎,而且前面也說了這種數(shù)據(jù)變化監(jiān)聽?wèi)?yīng)該由數(shù)據(jù)庫特性發(fā)出曾撤,2:和第一個(gè)有點(diǎn)異曲同工,也就是請(qǐng)求和顯示分離装悲,請(qǐng)求就是請(qǐng)求,不要去管顯示畏梆,這樣做有好處宪巨,也就是你可以在應(yīng)用任何地方請(qǐng)求網(wǎng)絡(luò)慈格,其他所有地方都自動(dòng)會(huì)刷新蒜田;好了下面我們來寫個(gè)簡(jiǎn)單的demo冲粤,我們?cè)贏ppDelegate.m中填充假數(shù)據(jù):
填充假數(shù)據(jù)
然后我們?cè)贐usinessController文件夾中創(chuàng)建一控制器FirstController(帶xib,為了方便)窝撵,在中間展示當(dāng)前用戶的名字傀顾,下面放一個(gè)輸入框,輸入框下邊一個(gè)按鈕點(diǎn)擊后修改用戶名字為輸入框的內(nèi)容碌奉,然后創(chuàng)建一個(gè)RBQFetchedResultsController代理設(shè)置成自己以便獲取最新的用戶信息锣笨,導(dǎo)航右邊按鈕不斷創(chuàng)建自身并push以測(cè)試效果,界面和代碼如下:
demo界面
FirstController.m上面代碼
FirstController.m下面代碼
上面的RBQFetchedResultsControllerDelegate基本還原CoreData的FetchedResultsControllerDelegate(數(shù)據(jù)庫操作會(huì)觸發(fā)這些回調(diào)道批,很強(qiáng)勢(shì)),然后我們BusinessController(登錄后的界面)中創(chuàng)建一個(gè)導(dǎo)航視圖隆豹,rootviewcontroller設(shè)置成FirstController,代碼就像這樣:
BusinessController.m代碼
然后我們啟動(dòng)模擬器热芹,多點(diǎn)下右上角的“+”號(hào)創(chuàng)建幾個(gè)FirstController實(shí)例,然后我們輸入內(nèi)容點(diǎn)擊按鈕,你再退回來看看是不是所有界面都變了米丘?效果如下:
效果圖
這部分的代碼工程里面沒有梭依,你們跟著寫一下實(shí)踐一下;好了部念,你現(xiàn)在可以拿著這個(gè)基礎(chǔ)工程開始你自己的項(xiàng)目了多柑,對(duì)了realm有幾個(gè)坑,我在https://github.com/TheBloodElf/iOSDevNotices/issues中記錄了一些驻龟,如果你遇到了你可以先去看看;最后坎弯,希望這個(gè)基礎(chǔ)工程可以為你減少項(xiàng)目開發(fā)的時(shí)間
第四步:做一個(gè)小demo展示請(qǐng)求與顯示分離
這篇文章出來后得到很多人的關(guān)注荧嵌,受寵若驚谭网,當(dāng)然也有噴這工程沒有5年沉淀的赃春;我在這里統(tǒng)一說明一下這只是一個(gè)基礎(chǔ)工程层坠,是為了讓更多人拿來即用的;為了滿足一些人的要求尺栖,我就寫一點(diǎn)這個(gè)基礎(chǔ)工程稍微能體現(xiàn)沉淀的例子吧大磺;這一步我們就來寫真實(shí)接入順便講講realm怎么使用斤彼,首先去前面的碼云地址下載項(xiàng)目,然后先編譯一下確保沒有問題姨丈,昨天我看到回復(fù)中有人編譯出錯(cuò)了畅卓,我一看提示大概是這兩個(gè)地方,大家注意了蟋恬,這兩個(gè)地方盡量一樣翁潘,然后還出錯(cuò)就clean項(xiàng)目草戈、重啟模擬器客扎、重啟XCode帚戳、重啟電腦……:
項(xiàng)目最低適配版本
不知道什么意思
為了還原真實(shí)開發(fā)環(huán)境扎即,我定義(YY)了一個(gè)接口(我用新浪云php做了個(gè)后臺(tái)怎虫,想了解后臺(tái)的可以看我相應(yīng)的文章)文檔(只有一個(gè)api谋作,用于演示)如下:
接口文檔
php后臺(tái)添加假數(shù)據(jù)(沒有讀取數(shù)據(jù)庫)代碼(這里沒有處理參數(shù)userNo):
后臺(tái)代碼
然后我們?cè)跒g覽器地址欄中輸入http://haiphp.applinzi.com/user/index.php洲鸠,就能看到這個(gè)效果了:
請(qǐng)求成功
那么這個(gè)怎么接入我們的基礎(chǔ)工程呢疯兼,首先在這里把地址換成文檔中的基礎(chǔ)地址:
1:換地址
然后確認(rèn)解析響應(yīng)是否正確乔遮,我們的code為0表示成功扮超,返回字段和文檔一樣:
2:確認(rèn)解析響應(yīng)邏輯
然后就是創(chuàng)建一個(gè)模型和文檔返回?cái)?shù)據(jù)中data一樣:
3:創(chuàng)建模型
4:設(shè)置主鍵
我們這里假設(shè)用戶已經(jīng)登錄了,所以我們依然在項(xiàng)目入口出寫上假用戶數(shù)據(jù):
5:寫上假當(dāng)前用戶數(shù)據(jù)
當(dāng)然我們要對(duì)UserFriend做本地緩存和表監(jiān)聽蹋肮,在用戶模型管理器中寫好接口(這里我只寫了三個(gè)出刷,你們可以自己加):
6:管理器中寫好接口
7:管理器中寫好實(shí)現(xiàn)
好了,本地緩就做完了坯辩,我們偷個(gè)懶馁龟,直接用這個(gè)控制器(真實(shí)項(xiàng)目用addChlidController加一層再做其他的,為了演示這里就稍微放縱一點(diǎn))來演示吧:
8:選擇要演示的控制器
老規(guī)矩漆魔,我們先創(chuàng)建一個(gè)表格視圖用來演示坷檩,代碼我這里先截一個(gè)最基本的,后面的步驟我就不全屏截圖了改抡,只截部分代碼:
9:創(chuàng)建表格視圖用來展示
上面有一個(gè)警告說的是:
RBQFetchedResultsControllerDelegate警告
這個(gè)不用管矢炼,因?yàn)檫@些回調(diào)我們是選擇性的使用;如果你有警告強(qiáng)迫癥(上家公司項(xiàng)目中沒有一個(gè)警告阿纤,這家公司不行裸删,因?yàn)楹芏嗳綆欤敲慈BQFetchedResultsController源碼改一下就行了:
解決警告
現(xiàn)在我們運(yùn)行一下應(yīng)該就能看到一個(gè)空白的表格視圖了:
10:跑一下看到效果
好了阵赠,現(xiàn)在開始寫展示代碼了涯塔,既然做了本地緩存,那么我們?cè)诳刂破鞒跏蓟臅r(shí)候當(dāng)然要先讀取本地?cái)?shù)據(jù)清蚀,所以我們?cè)賱?chuàng)建一個(gè)UserManager對(duì)象匕荸,用來取數(shù)據(jù):
11:取得本地?cái)?shù)據(jù)
然后我們要把數(shù)據(jù)監(jiān)聽寫上,就像這樣:
12:加上數(shù)據(jù)監(jiān)聽
對(duì)了枷邪,請(qǐng)求怎么能忘記了榛搔,我們?cè)谶@里寫上請(qǐng)求接口:
13:寫上請(qǐng)求接口
14:寫上請(qǐng)求實(shí)現(xiàn)
好了,現(xiàn)在我們還差請(qǐng)求數(shù)據(jù)了东揣,我們創(chuàng)建一個(gè)按鈕践惑,響應(yīng)代碼就像這樣:
15:請(qǐng)求代碼
這時(shí)候我們可以運(yùn)行起來了,然后點(diǎn)擊按鈕就能看到這樣的效果了:
16:請(qǐng)求成功
好了嘶卧,大家可以看看15步中的請(qǐng)求尔觉,我們請(qǐng)求完沒有去管界面顯示的事情,也就是沒有這樣寫:
請(qǐng)求和顯示綁定了
用realm+RBQFetchedResultsController就能達(dá)到分離了芥吟,甚至15步這段請(qǐng)求代碼可以放到項(xiàng)目任何一個(gè)地方(控制器瘦身侦铜?),只要數(shù)據(jù)改變這個(gè)控制器自動(dòng)就會(huì)去刷新了(我有深刻體會(huì))钟鸵;好了先更新到這里钉稍,如果有什么需要了解的請(qǐng)回復(fù)在下面,我會(huì)持續(xù)更新這篇文章
第五步:realm解決多線程操作同一對(duì)象混亂問題
相信大家在使用realm時(shí)被各種崩潰弄個(gè)狗血淋頭棺耍,我這里大概說一下最重要的兩點(diǎn)
1:直接從realm讀出來的對(duì)象不能直接修改
其實(shí)就是通過objectsInRealm讀出來的對(duì)象不能直接改變屬性贡未,如果你要改變那么請(qǐng)你自己重新copy一份
2:只有直接從realm讀出來的對(duì)象才能被刪除
也就是調(diào)用deleteObject的參數(shù)對(duì)象必須是從objectsInRealm直接讀出來的對(duì)象
3:realm可以異步操作,網(wǎng)上很多說只能主線程操作是錯(cuò)誤的
我文章下面的回復(fù)我也說錯(cuò)了蒙袍,不好意思俊卤;而且文章開頭也提出來了,我工程沒有加線程操作部分左敌,實(shí)在抱歉瘾蛋,你們可以自己加上
可能現(xiàn)在有人比較蒙蔽了,我怎么能控制好呢矫限?我當(dāng)然需要?jiǎng)h除或者修改哺哼,那我怎么知道當(dāng)前是copy還是直接讀出來的呢?其實(shí)我總結(jié)了一個(gè)方法叼风,現(xiàn)在你的程序就不會(huì)在realm部分崩潰了(我也是使用了兩個(gè)月后才發(fā)現(xiàn)這樣一個(gè)解決方法取董,可能網(wǎng)上還有其他的方法吧)
1:讀出數(shù)據(jù)后直接copy
也就是我們從objectsInRealm讀出后直接copy,這樣修改沒問題了无宿,代碼就像這樣:
直接copy讀出來的對(duì)象
那么有人可能會(huì)說了茵汰,這樣我刪除怎么辦?
2:刪除時(shí)再次讀取
刪除時(shí)再讀取一次
因?yàn)閷?duì)象有唯一標(biāo)示孽鸡,所以這樣再查一次也不費(fèi)時(shí)間蹂午,而且這樣做了后你的增刪改查都不會(huì)崩潰了栏豺;通過以上的兩點(diǎn)解決方法我們就可以完美的解決多線程操作同一對(duì)象混亂的問題了。