今天回來補(bǔ)下10月的部分,簡(jiǎn)書9月10月的時(shí)候沒法寫文章,并且這段時(shí)間趕進(jìn)度天天加班,所以留下了很多天沒有更新過,大致記了下部分問題,慢慢補(bǔ)上去。
19.10.08
構(gòu)建環(huán)信好友體系
環(huán)信自有的體系無法很輕易地?cái)U(kuò)展我們的業(yè)務(wù),主要體現(xiàn)在主鍵關(guān)聯(lián)、業(yè)務(wù)姓名、業(yè)務(wù)頭像、好友關(guān)系、群關(guān)系等等,需要我們自己去設(shè)計(jì)一套關(guān)聯(lián)方案使得環(huán)信體系與我們業(yè)務(wù)建立聯(lián)系。
關(guān)聯(lián)對(duì)后端來說比較容易,對(duì)兩者主鍵進(jìn)行關(guān)聯(lián)即可建立兩個(gè)對(duì)象間的聯(lián)系,但對(duì)前端來說,要考慮一個(gè)實(shí)時(shí)更新的問題,即有更新則需盡可能快速反應(yīng)到頁(yè)面上,例如好友更新了一個(gè)姓名,在聊天列表中我希望盡可能快地獲取到這個(gè)信息,再例如群里加入了一個(gè)人,我需要立刻就了解到這個(gè)人在我們業(yè)務(wù)中的信息。這類更新往往是一個(gè)被動(dòng)知道的狀態(tài),即更新者不會(huì)也不可能進(jìn)行廣播消息,我們能獲取的途徑有兩種,第一種像服務(wù)器請(qǐng)求這個(gè)人的最新信息,第二種通過環(huán)信消息系統(tǒng)。第一種的劣勢(shì)很明顯,因?yàn)槭潜粍?dòng)狀態(tài)請(qǐng)求時(shí)機(jī)無法獲知,同時(shí)會(huì)對(duì)服務(wù)器產(chǎn)生負(fù)擔(dān);第二種前面已經(jīng)說了不可能進(jìn)行廣播,所以需要把握更新的時(shí)機(jī)。
目前項(xiàng)目中我們的設(shè)計(jì)如下(分為2步):
1.打開App時(shí)向服務(wù)器請(qǐng)求數(shù)據(jù),進(jìn)行一次好友更新維護(hù),并同步到本地?cái)?shù)據(jù)庫(kù)。這個(gè)步驟有兩個(gè)目的:a.斷網(wǎng)狀態(tài)保證能正常顯示數(shù)據(jù);b.處理無法實(shí)時(shí)更新時(shí)消息列表信息顯示的問題。這個(gè)保證了歷史狀態(tài)(你更新的這一刻)最新。
2.每次發(fā)送消息時(shí)帶上自身業(yè)務(wù)的識(shí)別信息放在ext中,當(dāng)ext內(nèi)容與緩存模型不一致時(shí),更新緩存模型,并同步到數(shù)據(jù)庫(kù)。我可能不知道群中新加入發(fā)言的那個(gè)人他在我們業(yè)務(wù)中是誰(shuí),但他肯定知道他自己是誰(shuí)。發(fā)送者知道自身是誰(shuí),基于這點(diǎn)我們可以解決實(shí)時(shí)更新的問題,只要這個(gè)人給我發(fā)送消息,我就知道這個(gè)人是誰(shuí),在App運(yùn)行中去實(shí)時(shí)更新這個(gè)人的狀態(tài)。但他的弊端也是有的,就是無發(fā)言時(shí)我們無法更新其狀態(tài),這時(shí)就需要1來去解決這個(gè)問題。這個(gè)保證了實(shí)時(shí)狀態(tài)的最新。
上述1和2的結(jié)合也會(huì)存在部分空檔期:App運(yùn)行階段消息列表中某個(gè)人更新了狀態(tài)并且未向你發(fā)送消息,那么他的狀態(tài)更新則會(huì)推遲到下一次App啟動(dòng)。
19.10.09
外部業(yè)務(wù)如何并入現(xiàn)有業(yè)務(wù)線
這里還以環(huán)信的場(chǎng)景為例,在會(huì)話列表中,我們維護(hù)的不單單只是環(huán)信的一組會(huì)話列表,還有我們自有的業(yè)務(wù)場(chǎng)景,例如系統(tǒng)消息、紅包信息快捷操作等等,當(dāng)這些業(yè)務(wù)和環(huán)信這種第三方業(yè)務(wù)混在一起時(shí)我們應(yīng)該如何進(jìn)行操作?
操作前要明確一點(diǎn),任何其它業(yè)務(wù)和自有業(yè)務(wù)的混合都應(yīng)該是以自有業(yè)務(wù)為主,其它業(yè)務(wù)為輔,也就意味著如果強(qiáng)迫一方做出改變,那么改變的一方不應(yīng)該是我們的業(yè)務(wù)體系。
我們需要考慮兩點(diǎn),一是方式二是時(shí)機(jī)。
方式指的是我們?nèi)绾未罱ㄒ蛔鶚蛄?,讓三方業(yè)務(wù)通過這座橋梁并入到自有業(yè)務(wù)體系中,而又不影響二者的內(nèi)在邏輯。比較好的方式就是模型的組合并入。以開頭的場(chǎng)景來說,環(huán)信自有模型為A,我們自有業(yè)務(wù)模型為B,為了使A和B達(dá)到并線并統(tǒng)一外在,可以使用模型C,C中引用A或B,對(duì)外統(tǒng)一用模型C操作邏輯,區(qū)分則以引用的A或B的存在為條件。這樣一來不改變也不打破原有的業(yè)務(wù)體系,通過組合包裝,使得兩個(gè)體系并入在一起。
時(shí)機(jī)指的是我們?cè)诤螘r(shí)進(jìn)行并入操作。一般來說,這個(gè)時(shí)機(jī)是產(chǎn)生模型并且沒有進(jìn)行處理的時(shí)刻。還以上述場(chǎng)景為例,當(dāng)我調(diào)用環(huán)信的方法拿到所有的會(huì)話列表時(shí),用模型C包裝原始模型,包裝完畢之后環(huán)信的業(yè)務(wù)線就需要開始并入到我們自有的業(yè)務(wù)線中(也就是整合模型列表),此時(shí)環(huán)信業(yè)務(wù)停止,開展自有業(yè)務(wù)。業(yè)務(wù)停止并不意味著不再管環(huán)信的業(yè)務(wù),而是以自己業(yè)務(wù)的模型去處理,不再以環(huán)信的模型處理其業(yè)務(wù)。
19.10.10
繼承環(huán)信好友頭像和姓名的更新
這部分三端討論了不少時(shí)間,在之前的設(shè)計(jì)中又進(jìn)行了一些優(yōu)化和改進(jìn)。
主要數(shù)據(jù):好友群組等信息
次要數(shù)據(jù):群成員頭像姓名映射信息
1.本地?cái)?shù)據(jù)庫(kù)作為數(shù)據(jù)支撐。每次使用App時(shí)都會(huì)同步一遍主要數(shù)據(jù)(數(shù)據(jù)同步點(diǎn)1,主動(dòng)),同步完成后進(jìn)行增量更新,隨后釋放內(nèi)存,每次取值從數(shù)據(jù)庫(kù)中拿出。(此處數(shù)據(jù)庫(kù)是做了優(yōu)化,拿出過的值在一定時(shí)間內(nèi)會(huì)保存在內(nèi)存中,避免了反饋存?。?;
2.發(fā)送消息攜帶此刻此人的信息(數(shù)據(jù)同步點(diǎn)2,被動(dòng))。由于App只更新了一次數(shù)據(jù)信息,所以在App運(yùn)行的過程中,需要合適的點(diǎn)去進(jìn)行數(shù)據(jù)庫(kù)的更新和同步,這個(gè)點(diǎn)就在信息的傳遞時(shí)機(jī),例如收發(fā)消息,推送等等;
3.單個(gè)信息源查詢(數(shù)據(jù)同步點(diǎn)2,被動(dòng))。當(dāng)出現(xiàn)遺留數(shù)據(jù)未同步到數(shù)據(jù)庫(kù)時(shí),便調(diào)用單個(gè)信息源查詢接口,查詢單個(gè)信息,保存在數(shù)據(jù)庫(kù)中。
19.10.11
Realm主鍵問題
createOrUpdateInRealm:withValue
這個(gè)方法作為增量更新方法的使用前提是需要指定了主鍵才能使用,如果不指定則會(huì)報(bào)錯(cuò)。
一個(gè)場(chǎng)景:每個(gè)用戶在每個(gè)群中都有不同的昵稱,如果我不想做多表關(guān)聯(lián)如何使用這個(gè)方法?群ID或者用戶ID此時(shí)并不是唯一,并不能作為主鍵使用。那么我們?nèi)绾伪WC主鍵唯一性?可以使用群ID和用戶ID拼接起作為主鍵,群ID和用戶ID拼接之后值唯一,可以作為增量更新的依據(jù)。
19.10.14
后臺(tái)返回的數(shù)據(jù)類型
不同的接口返回類型可能會(huì)有很多種,我們目前返回類型各種各樣,并沒有統(tǒng)一的界定。前兩天的一次改動(dòng)讓我認(rèn)識(shí)到這里的返回類型需要做一個(gè)統(tǒng)一的規(guī)定:全部使用字典類型。之前的一個(gè)接口由于簡(jiǎn)單,只需要一串id,后臺(tái)直接把id拋了回來,以至于后期加字段時(shí)必須要重新改變數(shù)據(jù)結(jié)構(gòu)或者重新寫接口,因?yàn)楫?dāng)前的字符串類型無法進(jìn)行擴(kuò)展,這就大大增加了時(shí)間成本,對(duì)已經(jīng)上線的App來說增加了不必要的風(fēng)險(xiǎn)。修改數(shù)據(jù)結(jié)構(gòu)對(duì)于后臺(tái)和前端來說都是非常不友好的一件事,不到萬不得已不應(yīng)該去修改結(jié)構(gòu),那么從一開始就使用最容易擴(kuò)展的結(jié)構(gòu)才是正確的選擇。
19.10.15
layer.borderColor處在當(dāng)前視圖的最上層
有如下代碼:
UIView *backView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 150, 150)];
backView.backgroundColor = [UIColor lightGrayColor];
backView.layer.borderColor = [UIColor redColor].CGColor;
backView.layer.borderWidth = 5;
[self.view addSubview:backView];
//
UIView *maskView = [[UIView alloc] initWithFrame:CGRectMake(-50, -50, 100, 100)];
maskView.backgroundColor = [UIColor yellowColor];
[backView addSubview:maskView];
達(dá)到的效果如圖所示:

可以看到這么一個(gè)現(xiàn)象,父視圖A如果給了border,那么在當(dāng)前這個(gè)父視圖上,此border的層級(jí)為最高,任何加載到A上的子視圖都無法遮擋border。
那么子視圖的border是否遵循層級(jí)關(guān)系呢?我給子視圖加上border,效果如下:

可以看到,子視圖的border并沒有因?yàn)閷蛹?jí)關(guān)系處在上層而遮擋到父視圖的border,父視圖的border依舊在層級(jí)最高位。
那么單純的Layer是否會(huì)影響到呢?猜測(cè)是不會(huì),因?yàn)関iew其實(shí)操作的就是layer,為了嚴(yán)謹(jǐn)一點(diǎn)還是測(cè)試了下,結(jié)果是不會(huì)影響。
可以看出,border并沒有遵循普通的視圖層級(jí)關(guān)系,父視圖的border總是凌駕于當(dāng)前加到這個(gè)父視圖的所以視圖之上。如果想遮擋到這個(gè)view的border,就要從他的父視圖入手,代碼改為下面這樣:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 150, 150)];
[self.view addSubview:view];
UIView *backView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 150, 150)];
backView.backgroundColor = [UIColor lightGrayColor];
backView.layer.borderColor = [UIColor redColor].CGColor;
backView.layer.borderWidth = 5;
[view addSubview:backView];
//
UIView *maskView = [[UIView alloc] initWithFrame:CGRectMake(-50, -50, 100, 100)];
maskView.backgroundColor = [UIColor yellowColor];
maskView.layer.borderWidth = 5;
maskView.layer.borderColor = [UIColor greenColor].CGColor;
[view addSubview:maskView];
還有一個(gè)比較值得注意的地方,borderd的寬度是往內(nèi)擴(kuò)展的,我們打開層級(jí)關(guān)系很容易看到這個(gè):

19.10.16
iOS13之后persent默認(rèn)不再全屏
iOS13之后的persent默認(rèn)是分頁(yè)形式而不是全屏形式,相比全屏的模式,分頁(yè)模式下即使沒有dismiss的按鈕,下滑操作同樣可以觸發(fā)這個(gè)效果,也就是用戶可以隨意關(guān)閉這個(gè)彈出的頁(yè)面,這對(duì)于一些需要強(qiáng)制用戶選擇的地方產(chǎn)生了一些問題,而且分頁(yè)模式下,多個(gè)彈窗會(huì)像卡片一樣錯(cuò)層疊加狀態(tài),越上層的越往下偏移,這和UI上的差距也有點(diǎn)明顯。
對(duì)于改動(dòng)來說,一開始我想在基類中重寫persent的方法,但由于persent的模式和push執(zhí)行又不太一樣,有點(diǎn)問題,所以舍棄了,目前我在基類VC中寫了一個(gè)自定義的persent方法,在這個(gè)方法中去判斷系統(tǒng)版本,進(jìn)行全屏化設(shè)置。
19.10.17
梳理不同枚舉下頁(yè)面的展示和操作問題
枚舉一旦過多,如果組織不好代碼便會(huì)非常冗雜。如何組織這些代碼讓枚舉即使多樣也不會(huì)產(chǎn)生混亂?我覺得主要從以下幾步操作:
1.提取不同和相同
枚舉的意義在于區(qū)分不同的狀態(tài),那么提取不同部分就是首要的任務(wù),利用switch case區(qū)分不同。對(duì)于相同的部分,統(tǒng)一寫好接口進(jìn)行處理;
2.組織流程,讓分支分開與匯流
每種頁(yè)面都有其不同的業(yè)務(wù)流程,如果把這個(gè)業(yè)務(wù)流程比作一條水流,那么枚舉就好像一個(gè)又一個(gè)的石頭,切開這部分水流,然后再次匯到了一起。我們頁(yè)面的流程也是這樣,分支判斷語(yǔ)句負(fù)責(zé)分流然后匯總。
現(xiàn)在的項(xiàng)目里有一個(gè)文章發(fā)布頁(yè)面,這個(gè)頁(yè)面的業(yè)務(wù)流就是發(fā)布填充到頁(yè)面上的數(shù)據(jù),根據(jù)上面的方式,我們要做的像下面這樣:
1.由于存在正常、草稿、審核修改三種枚舉狀態(tài),那么這里的主要不同存在兩處,一處是數(shù)據(jù)源,另外一處是在執(zhí)行返回操作時(shí)是否保存操作,剩下的例如UI布局、文本校驗(yàn)、上傳文件等等操作都是相同的;
2.整個(gè)業(yè)務(wù)流程來說,像如下分部:a數(shù)據(jù)流入->b整合數(shù)據(jù) ->cUI基本交互 ->d發(fā)布或保存,我們根據(jù)枚舉,a和d是主要的分流部分,那么對(duì)a我們進(jìn)行三種數(shù)據(jù)流入的區(qū)分,區(qū)分之后把數(shù)據(jù)匯入到b進(jìn)行整合,在d處再次進(jìn)行分流,根據(jù)不同的枚舉進(jìn)行操作(例如修稿草稿類的把當(dāng)前草稿進(jìn)行刪除),最后匯集到發(fā)布或者保存草稿的狀態(tài)。
19.10.18
重用狀態(tài)下對(duì)不可重用模型的依賴
tableview的重用機(jī)制使得無法利用自身去標(biāo)記事件,需要借助外界不可重用的對(duì)象,我們比較常用的就是Model對(duì)象,若干個(gè)Model放在數(shù)組中對(duì)應(yīng)若干個(gè)cell。
由于cell本身是重用的,在需要特殊標(biāo)識(shí)時(shí)無法通過自身的去進(jìn)行判斷,必須要借助model這類不重用對(duì)象進(jìn)行標(biāo)識(shí)。例如我在cell上展開了一個(gè)下拉圖,如果不進(jìn)行標(biāo)識(shí),那么在重用機(jī)制下,滑動(dòng)到上面或下面某個(gè)區(qū)域時(shí)這個(gè)cell會(huì)再次被重用到,那么這個(gè)下拉圖還是原來的狀態(tài)。因此基于重用機(jī)制的控件的處理必須要用非重用的模型進(jìn)行標(biāo)記處理。
19.10.21
Lottie兼顧性能作出無損效果的動(dòng)畫

像上面的這種動(dòng)畫,打開文件可以看到這個(gè)gif是由141張靜態(tài)圖片組合成的,直接使用Imageview的動(dòng)畫組或者第三方去加載gif的話是非常消耗資源的,甚至在一級(jí)界面UI常駐的情況下,gif會(huì)大量占用內(nèi)存而且無法釋放掉,很容易會(huì)收到內(nèi)存警告。一些情況下,gif的加載甚至比視頻文件還要占用資源。
Lottie是一個(gè)支持多平臺(tái)的動(dòng)畫庫(kù),使用json文件配合素材實(shí)現(xiàn)動(dòng)畫效果,Lottie的動(dòng)畫核心基礎(chǔ)是CAKeyframeAnimation 關(guān)鍵幀動(dòng)畫,因此它的優(yōu)勢(shì)在于對(duì)內(nèi)存資源的占用非常小,通過GPU和很少的CPU進(jìn)行調(diào)度。它的制作也非常簡(jiǎn)單,UI使用AE等工具繪制好動(dòng)畫之后直接導(dǎo)出json文件即可,基本無損還原動(dòng)畫。
官方Git:https://github.com/airbnb/lottie-ios
官方Git目前沒有提供OC版本的,我在網(wǎng)上收集了個(gè):http://www.itdecent.cn/p/b2c0d1109c4d
需要注意一點(diǎn)的是,雖然Lottie對(duì)內(nèi)存消耗的很小,但是會(huì)占用一部分CPU,如果動(dòng)畫一直循環(huán),則CPU的占用無法停止,多個(gè)動(dòng)畫則會(huì)產(chǎn)生占用上的疊加,雖然CPU在沒有滿負(fù)荷的時(shí)候并不會(huì)產(chǎn)生明顯的感覺,但長(zhǎng)時(shí)間使CPU處在高位有一點(diǎn)會(huì)明顯,那就是手機(jī)會(huì)發(fā)熱,這點(diǎn)在動(dòng)畫上可能并不是很明顯,之前做視頻格式轉(zhuǎn)換使用FFmpeg時(shí)非常明顯(占用CPU進(jìn)行轉(zhuǎn)換),1分鐘90%左右的占用會(huì)使手機(jī)明顯升溫,耗電量明顯加速。
19.10.22
列表加載大圖對(duì)內(nèi)存的影響
在做一個(gè)列表的時(shí)候,我發(fā)現(xiàn)滑動(dòng)時(shí)候產(chǎn)生了卡頓,前兩頁(yè)還好點(diǎn),當(dāng)我加載的越多,卡頓越明顯,看了一下內(nèi)存也是在嗖嗖嗖地網(wǎng)上漲,滑個(gè)七八頁(yè)的時(shí)候就收到了內(nèi)存警告,排除了其他原因,最后鎖定到了加載的圖片上,這里出了問題。我把要加載的鏈接放到瀏覽器里,好家伙,基本都是1M以上,我這一梭子滑下去就要加載20M+,連續(xù)滑來滑去那還受得了。
之前對(duì)加載大圖的認(rèn)知是放在了查看高清大圖上,先把圖片下載到本地,再以引用的方式加載,防止內(nèi)存崩潰掉。在tableview上倒是很少注意,因?yàn)橐话闵蟼鞯膱D片也經(jīng)過了壓縮,不會(huì)出現(xiàn)很大的圖片,這次的圖片不知道是怎么弄上去的,應(yīng)該是某一端沒有做限制,導(dǎo)致傳的圖片比較大。因?yàn)閳D片目前都是在OSS保存著,所以解決這個(gè)問題也很方便,直接對(duì)圖片進(jìn)行fill裁剪,裁剪出ImageView三倍的寬高即可。
我用XR測(cè)試了一下,在列表頁(yè)面想要保持流暢的滑動(dòng),加載的主圖(頭像之類小的不算在內(nèi))個(gè)數(shù)應(yīng)該小于6個(gè),每個(gè)大小保持在200KB以下,副圖比如頭像之類的,大小最好控制在50KB以下。
如果沒有使用OSS這類的存儲(chǔ),對(duì)于這種情況需要強(qiáng)制輸入端限制上傳文件的大小,對(duì)于已經(jīng)存在的大圖,可以采用下載的方式,統(tǒng)一寫一個(gè)方法,對(duì)下載好的圖片,大于500kb的進(jìn)行裁剪壓縮處理,加載壓縮過的圖片。
從這里也可以看出OSS這類云端存儲(chǔ)的一些優(yōu)勢(shì),對(duì)于圖片的各種裁剪提供了很多便利的方式,包括對(duì)圓角的處理也很方便,如果后期能加上一些濾鏡效果的話就更棒了。
19.10.23
HUD的二次封裝問題
今天發(fā)現(xiàn)之前封裝的HUD似乎有點(diǎn)問題,多網(wǎng)絡(luò)請(qǐng)求時(shí)沒法消失。
改進(jìn)了一下,設(shè)計(jì)了兩個(gè)方案:
1.加載到window上,全局保持一個(gè)HUD單例,只要記得hidden就不會(huì)出現(xiàn)不消失的情況,但這種情況的劣勢(shì)也非常的明顯,那就是HUD出現(xiàn)時(shí)全局無法操作,所以這個(gè)暫時(shí)拋棄。
2.加載到某個(gè)VC上,這個(gè)VC上最多只有一個(gè)HUD對(duì)象。這個(gè)方案比加載到window好很多,左滑手勢(shì)可以生效,還可以返回上級(jí)頁(yè)面不至于卡住。在設(shè)計(jì)上我直接在baseVC中加入兩個(gè)方法,showHudWithText:和hidden。show方法中會(huì)懶加載一個(gè)hud(被屬性指針持有),保證這個(gè)VC只擁有一個(gè)hud對(duì)象,不會(huì)反復(fù)alloc多個(gè)。這樣一來只要show和hidden成對(duì)出現(xiàn),就能保證這個(gè)唯一的hud對(duì)象的消失。
另外還有若干的方案,例如和網(wǎng)絡(luò)請(qǐng)求綁定、用單獨(dú)的工具類持有等,前者適用范圍過窄,后者是一個(gè)不錯(cuò)的方法。這個(gè)問題其實(shí)只要保證操作對(duì)象hud是一個(gè)唯一的就可以(至少是當(dāng)前唯一),才不會(huì)出現(xiàn)并發(fā)問題。
19.10.24
數(shù)據(jù)流入阻塞
這個(gè)是最近測(cè)試時(shí)候的問題,在之前ERP項(xiàng)目中也會(huì)存在。
流程1-10,要測(cè)試的部分是789,但由于這樣或者那樣的原因,導(dǎo)致流程卡死在5,并且短時(shí)間內(nèi)無法解決。目前的項(xiàng)目中很多次出現(xiàn)了這樣的問題,為此浪費(fèi)了大量的時(shí)間。由于前期后臺(tái)的業(yè)務(wù)并沒有梳理清晰,每次做出改動(dòng)都影響到了前期的流程,一旦影響,那么流程直接卡死后續(xù)的無法測(cè)試。
回顧一下之前的ERP項(xiàng)目,流程也是非常的長(zhǎng),每個(gè)節(jié)點(diǎn)的關(guān)聯(lián)性也非常地強(qiáng),但阻塞流程的情況卻很少,后臺(tái)的老哥們確實(shí)做出了很大的貢獻(xiàn)。當(dāng)時(shí)我們的后臺(tái)管理比較嚴(yán)格,建表命名關(guān)聯(lián)都是由同一個(gè)人在操作,其他人去寫邏輯,這保證了基礎(chǔ)框架的穩(wěn)定,同時(shí)在數(shù)據(jù)上不會(huì)出現(xiàn)過多的冗余。而目前的工程,后臺(tái)對(duì)于核心部分的管理過于分散,每個(gè)人都可以操作核心業(yè)務(wù),這使得當(dāng)前的代碼十分混亂,每個(gè)人都寫自己的一套代碼,沒有在一個(gè)統(tǒng)一的框架和約束下,使得代碼各種冗余復(fù)雜,牽一發(fā)動(dòng)全身。另外之前的項(xiàng)目中我們自動(dòng)化測(cè)試引入的非常早,隨后一直保持了與Master分支一致的迭代,這使得我們?cè)傩噬洗蟠筇嵘?,主要體現(xiàn)在快速校驗(yàn)主流程已提測(cè)接口、快速創(chuàng)建可用數(shù)據(jù)、快速模擬異常情況等等。而目前的工程來說,由于后臺(tái)的接口不穩(wěn)定,修補(bǔ)頻率過高,引入自動(dòng)化測(cè)試可能出現(xiàn)加重工作負(fù)擔(dān)的情況,所以也暫時(shí)擱置一邊。
目前這個(gè)問題,估計(jì)后臺(tái)需要對(duì)主流程的代碼進(jìn)行重構(gòu)才能解決。
19.10.25
給collectionView添加個(gè)頭視圖
collectionView和tableview不同,collectionView是沒有一個(gè)獨(dú)立的頭視圖的,但是我們可以自己造一個(gè)出來??戳司W(wǎng)上的幾個(gè)方案,我整理出了一個(gè)比較好的方案措施,并且已經(jīng)實(shí)際進(jìn)行了驗(yàn)證,并且這個(gè)效果對(duì)繼承與scrollView的視圖通用。
我們知道,scrollView有一個(gè)屬性是contentInset,在這里能起到布局起始位置的作用,例如下面這樣會(huì)向下偏移150進(jìn)行布局:
_myCollectionView.contentInset = UIEdgeInsetsMake(150, 0, 0, 0);
這樣在這個(gè)collectionView起始位置就預(yù)留了150高度的位置,但是按照正常布局來說,設(shè)置的frame是會(huì)從150以上進(jìn)行布局的,這個(gè)時(shí)候就需要設(shè)置負(fù)值來使其在150以內(nèi)的區(qū)域,像下面這樣:
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, -150, self.view.width, 150)];
[self.myCollectionView addSubview:headerView];
需要注意一點(diǎn),采用這樣方式之后,mjheader之類的刷新控件位置需要重新設(shè)置不然會(huì)被遮擋。
19.10.28
處理非一般的App啟動(dòng)三方數(shù)據(jù)登入問題
一般來說,第三方登入或者綁定都會(huì)在首頁(yè)這個(gè)位置進(jìn)行操作,手上有一個(gè)app比較特殊,是根據(jù)角色來區(qū)分App內(nèi)tabbar上的Item的個(gè)數(shù),也就是這個(gè)App可能不存在首頁(yè)這個(gè)概念,但當(dāng)時(shí)沒有注意這個(gè)問題,還是按照普通App的模式,在首頁(yè)進(jìn)行第三方登入和信息的綁定,在提測(cè)時(shí)遇到了IM消息接收不到,極光推送無法到達(dá)的情況。那么現(xiàn)在就面臨了一個(gè)問題,并沒有一個(gè)確定的業(yè)務(wù)流匯總的頁(yè)面,如何處理三方數(shù)據(jù)登入問題呢?
既然找不到匯總點(diǎn),那么就從分支點(diǎn)進(jìn)行處理:
例如登錄成功之后,跳轉(zhuǎn)到A或B或C頁(yè)面,那么這個(gè)分支點(diǎn)就可以一次三方數(shù)據(jù)登入的操作,先執(zhí)行登入再執(zhí)行跳轉(zhuǎn),這是登錄流程的處理;正常打開App時(shí),也去找這個(gè)分支點(diǎn),例如在AppDelegate中,通過已登錄用戶的權(quán)限來進(jìn)行判斷時(shí)先進(jìn)行三方登錄操作。
這種App的流程和一般正常的流程有些差別,正常的流程是在匯總點(diǎn)處理事務(wù),而上面這種則是在分支點(diǎn)處理事務(wù),由于分支點(diǎn)有多個(gè),所以要處理多個(gè)地方??傊?,對(duì)流程把控來說,分支點(diǎn)和匯總點(diǎn)往往是處理事務(wù)比較好的切入點(diǎn)。
19.10.29
git stash
stash是git一個(gè)非常好用的命令,不過開發(fā)的時(shí)候發(fā)現(xiàn)同事用的并不是很多。這個(gè)命令的作用是將當(dāng)前的變更保留在一個(gè)獨(dú)立的區(qū)域,清空當(dāng)前的工作環(huán)境。一個(gè)比較實(shí)用的場(chǎng)景:我在某個(gè)分支上寫了一些代碼,但突然來了一些緊急需求,需要切換到另外的分支去處理事務(wù),這個(gè)時(shí)候就可以用git stash命令將當(dāng)前分支的更改保存到獨(dú)立的區(qū)域,然后切換到需要處理的分支,待需要處理的分支處理完畢后,切回一開始的分支,應(yīng)用之前stash保存的變更即可。
19.10.30
整合自身數(shù)據(jù)源和三方數(shù)據(jù)源
這里指的是拖入第三方的數(shù)據(jù)源與自身的數(shù)據(jù)源由于模型的不同,如何進(jìn)行一個(gè)數(shù)據(jù)源的同步,在各種操作之后還能保持一致性。這個(gè)問題在之前環(huán)信處理的時(shí)候已經(jīng)提過一次,進(jìn)行數(shù)據(jù)源的并線,至于采取自有向第三方并線還是第三方向自有并線,要看其規(guī)模,如果自有的model涉及面非常小,可以向第三方并線,舉一個(gè)最近的例子。
在文章發(fā)布VC中,我需要一組圖片進(jìn)行發(fā)布,這里的圖片視圖頁(yè)面是個(gè)第三方控件,用的模型是B,這個(gè)發(fā)布VC中我可能出現(xiàn)輸入型數(shù)據(jù)源,即草稿帶入或者審核駁回之后服務(wù)器帶入,這個(gè)時(shí)候我需要處理模型的差異,假設(shè)自有模型為A,三方模型為B,由于模型A涉及面非常小,這個(gè)時(shí)候?yàn)榱撕?jiǎn)化設(shè)計(jì)我們可以直接向三方模型B中組合A,即B中持有一個(gè)A,在輸入數(shù)據(jù)源時(shí)創(chuàng)建A的同時(shí)創(chuàng)建B持有A,這樣共用一組數(shù)據(jù)源,從輸入到UI操作到提交,數(shù)據(jù)源都保持一致性。
當(dāng)然,從自有模型到三方數(shù)據(jù)源中的模型大多還需要進(jìn)行一次轉(zhuǎn)換才可以直接被使用,這里我選擇的是遵從三方原始的賦值方式,例如這個(gè)圖片拖拽頁(yè)面三方用的是Image賦值,由于有被駁回的情況存在,我的模型可能出現(xiàn)只有url的場(chǎng)景,這個(gè)時(shí)候我采取的是url下載轉(zhuǎn)化成Image,而不是在其源碼中進(jìn)行修改用url賦值,這也遵循了封閉原則。
19.10.31
多參數(shù)接口統(tǒng)一外化參數(shù)模型
最近在整理一些之前的代碼,在整合部分接口的過程中發(fā)現(xiàn)接口的參數(shù)會(huì)出現(xiàn)很多問題:不是多了就是少了,改動(dòng)接口參數(shù)之后全局的接口都要相應(yīng)地區(qū)調(diào)整。這個(gè)問題在對(duì)某個(gè)視圖賦值的時(shí)候比較常見,比如上個(gè)版本我只需要顯示加入的人數(shù),接口中我只預(yù)留了peopleNum這個(gè)參數(shù),但在下個(gè)版本中又需要添加活動(dòng)時(shí)間的顯示,這樣一來就需要對(duì)接口進(jìn)行改動(dòng),在開放封閉的原則下這顯然是設(shè)計(jì)有問題的。
對(duì)于這種UI類賦值的接口來說,考慮到后期的擴(kuò)展情況,接口在設(shè)計(jì)時(shí)使用某個(gè)類的模型對(duì)象作為參數(shù)顯然是更合理的,后期的擴(kuò)展不需要更改接口本身。
而對(duì)于工具類的接口來說,接口在設(shè)計(jì)之初,所需的參數(shù)和返回值便是可以確定的,這類接口反而不應(yīng)該使用可變的外化模型作為參數(shù),而是使用確定參數(shù),更加明確接口要表達(dá)的含義。并且作為工具類的接口,所需的參數(shù)應(yīng)該是最直接不需要二次加工的參數(shù),除非是泛型或者反射機(jī)制,否則不應(yīng)該在方法的實(shí)現(xiàn)中再去判斷參數(shù)類型再去進(jìn)行轉(zhuǎn)化分類。
19.11.1
collectionView flowLayout剩下一個(gè)Item時(shí)自動(dòng)居中問題
系統(tǒng)默認(rèn)的flowlayout在某個(gè)分區(qū)僅存在一個(gè)item存在時(shí)會(huì)自動(dòng)將其居中,需要手動(dòng)修改改變其位置,為了修正這個(gè)問題我寫了個(gè)layout繼承自flowlayout,
在.h文件中我預(yù)留了一個(gè)距左邊的距離參數(shù)leftOffest
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
NSArray <UICollectionViewLayoutAttributes *>*attributes = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes *att in attributes) {
if (att.indexPath.row == 0) {
att.frame = CGRectMake(self.sectionInset.left + self.leftOffest, att.frame.origin.y, att.frame.size.width, att.frame.size.height);
}
}
return attributes;
}
一開始我以attributes.count == 1的方式來判斷僅剩下一個(gè)item,后來發(fā)現(xiàn)這個(gè)現(xiàn)象在每個(gè)分區(qū)都會(huì)存在,所以直接用row == 0去判斷。
需要注意的一點(diǎn)是:在實(shí)際操作中我發(fā)現(xiàn)這個(gè)方法不單單對(duì)item生效,對(duì)分組的頭和尾也會(huì)生效。
19.11.4
NSDateFormatter對(duì)性能嚴(yán)重消耗
NSDateFormatter這個(gè)對(duì)象非常消耗性能,之前測(cè)試過,初始化時(shí)消耗的時(shí)間遠(yuǎn)遠(yuǎn)高于設(shè)置日期格式時(shí)的時(shí)間。
在靜態(tài)頁(yè)面上這個(gè)消耗可能在體驗(yàn)上不是很明顯,但在tableview或者collectionView上面就非常直觀的感受到了這個(gè)卡頓現(xiàn)象。
如何處理這種初始化消耗性能的對(duì)象在滑動(dòng)視圖上的問題呢?
對(duì)于這種初始化時(shí)消耗大量資源的對(duì)象,單例無疑是一個(gè)非常好的選擇,單例初始化一個(gè)對(duì)象,全局使用,減少了不必要的消耗。
上面的初始化性能消耗是可以避免的,NSDateFormatter在使用時(shí)stringFromDate/dateFromString這兩個(gè)方法也很消耗性能,但這里似乎無法做出好的優(yōu)化,只能盡可能地做緩存,減少調(diào)用。
項(xiàng)目里有一處是顯示發(fā)布時(shí)間,需要根據(jù)時(shí)間戳和當(dāng)前時(shí)間的對(duì)比進(jìn)行兩次轉(zhuǎn)化:時(shí)間戳->真實(shí)時(shí)間->當(dāng)前需要展示的時(shí)間方案。由于是在滑動(dòng)視圖上,我做了如下的優(yōu)化方案:
1.單例保持NSDateFormatter對(duì)象的初始化;
2.在視圖滑動(dòng)停止時(shí)開始進(jìn)行時(shí)間轉(zhuǎn)換操作,滾動(dòng)時(shí)不進(jìn)行操作;
2.在模型中加入realDate字段,轉(zhuǎn)換后的要展示的時(shí)間進(jìn)行一次緩存,有則直接從緩存中取,不再進(jìn)行stringFromDate的操作。
之前考慮過一個(gè)方案,就是異步處理這些耗時(shí)操作,但在cell重用狀態(tài)下,異步操作反而會(huì)出現(xiàn)更多異常的情況,所以暫時(shí)只在滑動(dòng)停止時(shí)進(jìn)行了處理,這里還需要研究一下。
19.11.5
帶返回參數(shù)的block和帶block參數(shù)的block
有空梳理下,老是把這兩個(gè)弄混。。。
1.帶返回參數(shù)的block,即時(shí)回調(diào)結(jié)果,像下面這樣:
typedef CGFloat(^speedBlock)(CGFloat x);
@interface ViewController ()
@property (nonatomic,copy) speedBlock speed;
@end
self.speed = ^CGFloat(CGFloat x) {
return 20 + x;
};
CGFloat result = self.speed(10);
NSLog(@"%f",result);
很簡(jiǎn)單,打印出30,這種方式一般用的多一點(diǎn),用于 【即時(shí)】 回調(diào)出結(jié)果的情況。
2.帶block參數(shù)的block,用于執(zhí)行某個(gè)結(jié)果,像下面這樣:
typedef void(^numActionBlock)(void(^)(NSInteger num));
@interface ViewController ()
@property (nonatomic,copy) numActionBlock numAction;
@end
NSInteger startNum = 123;
self.numAction = ^(void(^act)(NSInteger num)) {
act(startNum + 2);
};
self.numAction(^(NSInteger num) {
NSLog(@"%ld",num);
});
最簡(jiǎn)單的對(duì)比來說,在使用場(chǎng)景上,第一種block適用于即時(shí)返回計(jì)算值或者比較結(jié)果的場(chǎng)景,偏向于即時(shí)“結(jié)果”的獲取,獲取之后直接在當(dāng)前函數(shù)方法中使用,,例如下載進(jìn)度、快速枚舉等;第二種block適合執(zhí)行某個(gè)操作的場(chǎng)景,同時(shí)也可以控制回調(diào)執(zhí)行時(shí)間,例如延遲執(zhí)行某個(gè)方法時(shí)第一種block顯然不合適。
19.11.6
無網(wǎng)絡(luò)時(shí)緩存點(diǎn)贊等網(wǎng)絡(luò)請(qǐng)求
產(chǎn)品有個(gè)需求是用戶在無網(wǎng)絡(luò)狀態(tài)時(shí)的點(diǎn)贊操作緩存起來,等待有網(wǎng)絡(luò)時(shí)統(tǒng)一提交給后臺(tái)。
這個(gè)需求的路子繞的有多遠(yuǎn),整理下拋給產(chǎn)品:
先說下正常流程:
1.在無網(wǎng)絡(luò)狀態(tài)下,保留一個(gè)請(qǐng)求緩存的隊(duì)列,里面保存著每組請(qǐng)求接口和參數(shù),當(dāng)有網(wǎng)絡(luò)時(shí)異步進(jìn)行請(qǐng)求處理;
2.假如現(xiàn)在有20個(gè)操作需要同步到網(wǎng)絡(luò),那么這20個(gè)請(qǐng)求如何進(jìn)行處理?并行or串行?如果某個(gè)失敗了是進(jìn)行輪詢還是放棄請(qǐng)求?
異常流程:
1.目前我們的請(qǐng)求全都是校驗(yàn)token的,token超時(shí)如何處理?客戶端本身是不知道token超時(shí)的,需要發(fā)出請(qǐng)求之后給予回應(yīng)中;
2.產(chǎn)生錯(cuò)誤返回時(shí)是否屏蔽錯(cuò)誤信息?正??磥砜隙ú荒苣涿钔蝗粡棾鲆粋€(gè)“文章已被刪除”之類的信息;
3.若產(chǎn)生失敗,但本地?cái)?shù)據(jù)又和服務(wù)器數(shù)據(jù)不同步,以哪一方為準(zhǔn)?
拋給產(chǎn)品,估計(jì)最后這個(gè)需求不了了之。。。
19.11.7
正常超時(shí)和異常超時(shí)
正常超時(shí)指的是客戶端發(fā)出請(qǐng)求超出一定時(shí)間后服務(wù)器無任何響應(yīng)操作,此時(shí)服務(wù)器無響應(yīng),之后的一段時(shí)間服務(wù)器也無響應(yīng),異常超時(shí)指的是客戶端達(dá)到一定時(shí)間后認(rèn)為其超時(shí),但在若干時(shí)間后服務(wù)器給出了應(yīng)答。簡(jiǎn)單地說,超時(shí)時(shí)間給的太短,但為了不影響用戶體驗(yàn),一般來說我們都會(huì)給個(gè)15s左右的超時(shí)限制,太長(zhǎng)的時(shí)間未響應(yīng)本身就是程序上的問題。
如何避免這類情況的發(fā)生?我們從事件發(fā)生原因來看,造成這種現(xiàn)象的原因無外乎是服務(wù)器進(jìn)行了復(fù)雜耗時(shí)的操作,常見的比如多表查詢、下載上傳資源文件、服務(wù)器等待另一服務(wù)端響應(yīng)等等。
1.多表查詢。后臺(tái)在表結(jié)構(gòu)設(shè)計(jì)上需要下點(diǎn)功夫,尤其是ERP類型的業(yè)務(wù),業(yè)務(wù)關(guān)聯(lián)性非常強(qiáng),前端發(fā)送請(qǐng)求時(shí)可以適當(dāng)增加參數(shù)輔助后臺(tái)快速查詢;
2.上傳下載資源文件。這個(gè)本就不應(yīng)該在服務(wù)器上進(jìn)行操作,耗費(fèi)服務(wù)器資源,包括用服務(wù)器進(jìn)行視頻轉(zhuǎn)碼操作,這些都應(yīng)該避免,資源文件的操作盡量采用第三方云端服務(wù),例如阿里OSS,七牛云等;
3.服務(wù)器等待另一端服務(wù)器響應(yīng)。這種一般是進(jìn)行了跨服務(wù)器業(yè)務(wù),同樣地,這個(gè)業(yè)務(wù)如果簡(jiǎn)單完全可以在前端進(jìn)行操作,但如果涉及安全方面則必須要有自己的服務(wù)器操作,這個(gè)時(shí)間是無法避免的,而且協(xié)調(diào)起來不那么容易。一個(gè)比較妥協(xié)的方案就是異步無限期等待,發(fā)起之后就異步掛起,響應(yīng)回來之后全局彈出。
19.11.8
涉及UI類第三方庫(kù)盡量使用拖入工程的方式
TZImagePickerController這個(gè)相冊(cè)選擇的第三方相信很多人都用過,我們這個(gè)工程中也不例外,集中使用了pod管理。由于部分UI需要高度自定義,所以直接就在源代碼中進(jìn)行了修改,為了防止有誤操作導(dǎo)致版本變更,強(qiáng)制指定了固定了版本。但即使這樣,還是出現(xiàn)了一些問題,(git對(duì)pod操作的一些忽略,兼容13迫不得已需要升級(jí)三方版本等等),最終不得不重新將其拖入工程中進(jìn)行修改,不再使用Pod管理。
對(duì)于這類UI類型的庫(kù),如果預(yù)見后期需要對(duì)UI有各種改動(dòng),比較穩(wěn)妥的方式就是拖入工程而不是使用pod管理,一旦要修改庫(kù)的源代碼,使用pod管理反而會(huì)增加很多麻煩,誤操作被新版本覆蓋時(shí)甚至丟失代碼。但拖入工程的方式也有個(gè)很大的弊端,就是系統(tǒng)產(chǎn)生兼容性問題時(shí)需要手動(dòng)修改。相比較來說,拖入操作更可控一些,出了問題也很容易定位到。
19.11.11
帶上梯子讓終端pod飛起
pod的操作越來越慢,沒有梯子往往需要等待很久最后給你來個(gè)超時(shí)。之前保存的那個(gè)鏈接打不開了,手動(dòng)記錄一下方式:
1.你要有個(gè)梯子,找到本地socket5地址;
2.在終端輸入一下命令,強(qiáng)制終端走梯子:export https_proxy="本地socket5地址"
3.這個(gè)命令只對(duì)當(dāng)前的終端窗口有效,關(guān)閉則需要重新設(shè)置。
19.11.13
反射模式使問題復(fù)雜化
目前的項(xiàng)目中多次用model進(jìn)行cell的反射,由于要依靠Model,產(chǎn)生了很多“無用”狀態(tài)的Model,僅僅是為了反射到對(duì)應(yīng)的cell,這類model文件數(shù)量還不算少,造成了文件的冗余。
回到為什么使用反射模式的問題上,反射能消除分支節(jié)點(diǎn)的if...else...判斷,加強(qiáng)代碼的擴(kuò)展性,保證了開放封閉的原則,在會(huì)出現(xiàn)大量if...else...的地方使用效果很好,但對(duì)于確定性的比較少量的分支節(jié)點(diǎn)判斷,使用反射模式反而會(huì)使得問題有些復(fù)雜化,主要就體現(xiàn)在上面這種情況,搭建反射的資源過度冗余,對(duì)于這類問題以后要注意。
19.11.14
persent和push的執(zhí)行
persent和push在代碼執(zhí)行上有著差別,persent是逐行執(zhí)行,push是有點(diǎn)類似于FMDB的事務(wù)提交方式,把這部分都設(shè)置完成之后才處理。
有下面代碼:
- (void)action {
QViewController *vc = [[QViewController alloc] init];
[self presentViewController:vc animated:YES completion:nil];
// [self.navigationController pushViewController:vc animated:YES];
NSLog(@"222");
}
在QVC的ViewDidLoad中我打印了111,上述代碼先打印111,后打印222。改變代碼如下
- (void)action {
QViewController *vc = [[QViewController alloc] init];
// [self presentViewController:vc animated:YES completion:nil];
[self.navigationController pushViewController:vc animated:YES];
NSLog(@"222");
}
上面代碼先打印222,后打印111。上面簡(jiǎn)單的例子已經(jīng)足以說明present和push執(zhí)行時(shí)在生命周期上的區(qū)別:對(duì)于present的VC,在present代碼之后再賦值就已經(jīng)遲了,而push則不會(huì)。
19.11.15
今天開始需要周六上班來延長(zhǎng)春節(jié)假期了,工期比較趕暫時(shí)休整一段。