JChat Swift 演進(jìn)過程 - 打造一款通用的 IM UI 庫

內(nèi)容簡(jiǎn)介:

近年來隨著APP應(yīng)用社交化的發(fā)展,越來越多的應(yīng)用開始接入即時(shí)通訊 SDK ,以便快速實(shí)現(xiàn)社交功能。同時(shí)開發(fā)者希望有一款通用的 IM UI 來避免重復(fù)開發(fā),提高開發(fā)效率。本次分享將會(huì)結(jié)合極光推送公司JChat產(chǎn)品的開發(fā)經(jīng)驗(yàn),介紹如何優(yōu)雅地實(shí)現(xiàn)一款通用的 IM UI 庫,并談?wù)勯_發(fā)過程中遇到的坑及相應(yīng)的解決方法,以及如何減少重復(fù)開發(fā)和增加代碼的可擴(kuò)展性。

演講 PPT 點(diǎn)這下載

最近半年的時(shí)間里,我從 SDK 開發(fā)轉(zhuǎn)到 IM UI 庫的開發(fā)(其實(shí)就是一個(gè)完整 IM APP),也完全過度到使用 Swift 進(jìn)行開發(fā)。直到某天,領(lǐng)導(dǎo)對(duì)我說:“你去做個(gè)關(guān)于我們極光 IM 的演講吧”,當(dāng)時(shí)我就懵逼了,看來唯有談?wù)勎易罱肽甑墓ぷ餍牡昧?,我作為一位?zhí)行力比較強(qiáng)的小跟班,領(lǐng)導(dǎo)的話我肯定是服從安排,所以很直接就把主題定為:

如何使用 極光 IM JMessage

嗯...目測(cè)這樣直接打廣告,顯得水平就不太高,主辦方也無情地拒絕了,雖然我們是同一家公司的,但他們還是很堅(jiān)守原則的,所以只能稍微把演講主題改了一下:

如何打造一款通用的 IM UI 庫

UI 庫與 APP 的差別

開發(fā)者 VS 用戶

在座的各位,估計(jì)大部分都是 iOS 開發(fā)者,我們當(dāng)中,可能較多的人都是從事 APP 開發(fā),估計(jì)也有部分是從事 SDK 開發(fā)的同學(xué)。我們都知道,APP 主要面向的用戶,用戶更加注重的是應(yīng)用的使用和功能,而SDK 面向的則是開發(fā)者,開發(fā)者關(guān)注的則是 SDK 具有哪些功能和這些功能是如何去實(shí)現(xiàn)和使用的。同樣,UI 庫其實(shí)也可以說成是 SDK,它只是針對(duì)界面層的 SDK,它面向同樣是開發(fā)者,有著和 SDK 類似的特點(diǎn)。

重復(fù)造輪子

“Stop Trying to Reinvent the Wheel”

因?yàn)槲宜诘牟块T本身就是開發(fā) IM SDK 的,去寫這么一個(gè) UI 庫的主要目的還是避免開發(fā)者重復(fù)開發(fā)輪子,畢竟時(shí)間可貴,珍惜生命,少寫重復(fù)代碼。在我們實(shí)際開發(fā)中,我們不應(yīng)該重復(fù)造輪子,聽好幾個(gè)朋友說過,他們公司不允許使用任何的第三方庫,這個(gè)不知道是出于什么原因,但個(gè)人感覺這是一種浪費(fèi)生命的行為,對(duì)于那些優(yōu)秀的開源框架,比如說像 AFNetWorking,當(dāng)我們需要使用相關(guān)的功能時(shí),我們完全有理由去拿來直接使用,而不是花大量的時(shí)間去開發(fā)新的輪子,軟件是有生命周期的,可能待你把所以有輪子造好,你的軟件就已經(jīng)可以和市場(chǎng) say goodbye 了,并且輪子造好時(shí),還需要花費(fèi)大量的人力和時(shí)間去進(jìn)行測(cè)試和驗(yàn)收。

作為一個(gè)開發(fā)者,在工作上面壓力很多時(shí)候都不會(huì)輕,在我們有限的開發(fā)生涯中,應(yīng)該如何有效利用時(shí)間來做一些更有價(jià)值的事情,而且不是浪費(fèi)在造輪子上。顯然,羅馬不是一天建成的,也不是一個(gè)人建成的。我們需要學(xué)會(huì)把自己和別人寫的代碼組織起來,高效地利用,并以此為基礎(chǔ)構(gòu)建軟件。如何優(yōu)雅地實(shí)現(xiàn)一款通用的組件,在方便自己工作的同時(shí),給其它開發(fā)者帶來方便,這就是我今天想講的主題,下面都會(huì)以 IM UI 庫為例進(jìn)行演講。

可兼容性

作為一款通用的 IM UI 庫,首先兼容性是必不可少的,它不是單純的一個(gè) APP,它應(yīng)該更具有通用性,兼容各類型的 IM SDK,而不單單是針對(duì)自己公司的產(chǎn)品,最理想的姿勢(shì)當(dāng)然是支持所有類型的 IM SDK,但理想都是美好的,現(xiàn)實(shí)卻總是會(huì)時(shí)不時(shí)打擊下我們。這里就先不管能不能支持所有的 IM SDK,這是一個(gè) target,前方路的還很漫長,我們尚需努力。下面將從 JChat 的架構(gòu)設(shè)計(jì)來你介紹整個(gè) UI 庫的兼容性實(shí)現(xiàn)和解耦過程。

JChat 架構(gòu)設(shè)計(jì)

舊 JChat 消息處理

圖片.png

在接手 JChat Swift 開發(fā)之前,有一個(gè)年久失修的 OC 版本的 JChat,它在消息處理層上,是直接使用 SDK里面的 JMSGMessage 作為整個(gè)應(yīng)用的消息體對(duì)象來使用(這里說明下,這里的 SDK 指的是我們極光 IM SDK,下面不重復(fù)說明),這樣做,也不是說不可以,多么簡(jiǎn)單明了,但是,如果某一天,領(lǐng)導(dǎo)說:“這個(gè) IM SDK 滿足不了我們當(dāng)前業(yè)務(wù),我們需要更換成 xxxx IM SDK,下周出新版本”。

笑著活下去

估計(jì)如果是新來接手這個(gè)項(xiàng)目的人,肯定懵逼了,我想整個(gè)應(yīng)用的業(yè)務(wù)邏輯層都需要去改,這其中到底有多苦逼,只有自己去真正去體驗(yàn)一把,試過才能知道其中有多艱辛。希望位都不會(huì)遇到這種神項(xiàng)目,如果真的碰到了,我也只能對(duì)你說一句:“兄弟,笑著活下去吧”。

程序員為何為難程序員

同時(shí)也希望所有人盡量不要寫這種代碼,說不定某天剛好與接手你項(xiàng)目的同事或前同事相遇街角,狹路相逢,說不定你就需要躺著出來。

在就里只是和大家開個(gè)玩笑,但并不是不可能的,好了,下面回到正題。

JChat Swift 消息處理

圖片.png

在 JChat Swift 里面,不再使用這種高耦合的方式,而是在上層再封裝一層與 IM SDK 無關(guān)的 JCMessage,只保留消息展示所需的信息,在應(yīng)用的業(yè)務(wù)邏輯層里面,都只依賴于 JCMessage,這樣不管你使用的極光的 IM 也好,還是環(huán)信的 IM 也好,或者其它的 IM SDK,只需要去修改從 xxxMessage -> JCMessage 的解析方法就可以了,其它的業(yè)務(wù)邏輯就基本不需要去改動(dòng)了。

同時(shí),為了提供更好擴(kuò)展性,我們應(yīng)該提供一個(gè) JCMessageType 協(xié)議:

protocol JCMessageType: class {    
    var msgId: String { get }
    var content: JCMessageContentType { get }
    var options: JCMessageOptions { get }
    var targetType: MessageTargetType { get }
    // ...
}
圖片.png

這樣不管是 JCMessage 還是 XMessage, 只需要實(shí)現(xiàn) JCMessageType 協(xié)議:

class JCMessage: NSObject, JCMessageType {
    init(content: JCMessageContentType) {
        self.content = content
        self.options = JCMessageOptions(with: content)
        super.init()
    }
    open var msgId = ""
    open var targetType: MessageTargetType = .single
}

那么在原來的邏輯上都不需要改動(dòng),開發(fā)者還可以自定義一些字段或者做一些其它的擴(kuò)展,使用的自由度更大。

這里雖然是 IM UI 庫的實(shí)現(xiàn)為,但其實(shí)在其它地方上也是同理的,比如使用某些第三方閉源包時(shí),在上層提供一層穩(wěn)定的 api,使上層的業(yè)務(wù)邏輯保持穩(wěn)定,當(dāng) SDK Api 或者內(nèi)部實(shí)現(xiàn)發(fā)生變動(dòng)時(shí),我們只需要在底層的實(shí)現(xiàn)去做適配就可以了,上層業(yè)務(wù)層就不會(huì)受影響,把受影響范圍控制在最小。

消息類型的擴(kuò)展

在做 IM 應(yīng)用的時(shí)候,變動(dòng)最多的莫過于各種類型的消息添加了,比如今天只需要最簡(jiǎn)單的文本消息、語音消息和圖片消息,過兩天就需要你添加片名消息、閱后即焚消息等。所以在 IM UI 庫中,如何設(shè)計(jì)各種消息體的實(shí)現(xiàn)就很重要了。

protocol JCMessageContentType: class  {    
    // 消息體展示的大小
    func sizeThatFits(_ size: CGSize) -> CGSize   
    // 消息類型
    static var viewType: JCMessageContentViewType.Type { get }
}

protocol JCMessageContentViewType: class {
    init()
    // 渲染消息
    func apply(_ message: JCMessageType)
}

消息的展示,其實(shí)只需要知道消息內(nèi)容和類型就可以繪制出來,所以在這里定義了 JCMessageContentType 協(xié)議和 JCMessageContentViewType 協(xié)議,消息 Content 實(shí)現(xiàn) JCMessageContentType 時(shí)需要實(shí)現(xiàn) sizeThatFits 方法來返回 content 的 size,來確定它在界面上顯示的大小,同時(shí)需要定義它的 ContentViewType,就是它的類型。消息的展示 View 實(shí)現(xiàn) JCMessageContentViewType 時(shí),需要實(shí)現(xiàn) apply 方法,通過 apply 方法來把 message 的信息渲染到界面上。

在 ChatViewLayout(MessageCell 布局文件) 中,通過 JCMessageContentType 的 sizeThatFits 來獲取 MessageCell 的大小,在 MessageCell 中,則是通過 JCMessageContentViewType 的 apply 來設(shè)置展示的內(nèi)容,不管你是什么類型的消息,只要你符合協(xié)議的要求,ChatView 就可以把 Message 渲染出來,這樣就可以降低 ChatView 與 Message Type 的耦合,使用者就可以更快更方便地實(shí)現(xiàn)各種類型的消息,并且不需要原來的代碼進(jìn)行改動(dòng)。

API 設(shè)計(jì)

最小化原則

  1. 盡可能少的接口來完成任務(wù)

  2. 盡可能少的訪問權(quán)限

ChatView

下面說下整個(gè) UI 庫最復(fù)雜的界面 ChatView 的 API。

ChatView.png
public func insert(_ newMessage: JCMessageType, at index: Int)
public func insert(contentsOf newMessages: Array<JCMessageType>, at index: Int)

public func append(_ newMessage: JCMessageType)
public func append(contentsOf newMessages: Array<JCMessageType>)
    
public func update(_ newMessage: JCMessageType, at index: Int)
    
public func removeAll() 
public func remove(at index: Int)
public func remove(contentOf indexs: Array<Int>)

基于 UI 庫的特點(diǎn),相較于 app 開發(fā),需要更著重地考慮 API 的設(shè)計(jì)。你標(biāo)記為 public 的內(nèi)容將是使用者能看到的內(nèi)容。提供什么樣的 API 在很大程度上決定了其他的開發(fā)者會(huì)如何使用該 UI 庫。

在 API 設(shè)計(jì)的時(shí)候,從原則上來說,我們一開始可以提供盡可能少的接口來完成必要的任務(wù),這有利于控制整個(gè) UI 庫的復(fù)雜程度。 在 ChatView 中我們只提供必須的添加、刪除和修改消息的接口,只需要向 ChatView 傳遞正確 JCMessageType,ChatView 就會(huì)負(fù)責(zé)在界面上渲染出來,使用者不需要再去關(guān)心 ChatView 的顯示過程,只需要保證傳遞正確 JCMessageType 序列就可以了。最少的接口也減少了開發(fā)者的學(xué)習(xí)成本,減少不必須的歧義,如果后期需要,開發(fā)者可以對(duì)其進(jìn)行二次開發(fā),添加所需的公共方法,或者把原有的一些私有方法設(shè)置成公有。

OC 與 Swift 命名兼容

OC 方法名兼容 Swift 調(diào)用

JChat 性能優(yōu)化

緩存

在 JChat Swift 實(shí)現(xiàn)中,為了提高性能,很多地方都添加了緩存,就像緩存計(jì)算出來的 Message Cell 的 size、圖片加載資源加載等,這里以 JChat 主題管理功能為例,詳細(xì)說下。

JChat 主題管理 UML

JChat 的主題管理功能是通過 bundle 來管理圖片,不同的主題皮膚的圖片資源放在對(duì)應(yīng)的 bundle 里面,共同的資源放在默認(rèn)的 bundle 中,當(dāng)監(jiān)聽到主題切換時(shí),只需要切換圖片訪問路徑并刷新界面就可以了。

關(guān)于 JChat 主題管理功能的實(shí)現(xiàn)的詳細(xì)可以參考:

再談 Swift 換膚功能

在 JChat 中,聊天的時(shí)候,較多界面上都有進(jìn)行頻繁的刷新,就如聊天列表或消息列表,這里就會(huì)有大頻率的重復(fù)訪問本地的圖片的,特別是當(dāng)用戶長時(shí)間沒有登錄,積累了大量離線消息時(shí),下次登錄時(shí),會(huì)一次性收到大量的離線消息,在上層刷新頻繁就會(huì)非常大了,一些應(yīng)用里面的默認(rèn)圖片的訪問量可能就會(huì)比較大,我們通過文件的方式來加載本地圖片時(shí),就會(huì)存在性能的問題,所以在訪問圖片資源的時(shí)候,如果該圖片如果已經(jīng)緩存在內(nèi)存中時(shí),我們就從緩存中讀,如果 緩存中沒有,則從硬盤里面讀取,并把該圖片緩存到內(nèi)存中,這樣的話,資源圖片實(shí)質(zhì)上都只加載一次,而不需要多次去加載。需要注意的是,因?yàn)閳D片一直緩存在內(nèi)存中時(shí),就需要監(jiān)聽系統(tǒng)是否有內(nèi)存警告,如果系統(tǒng)發(fā)出內(nèi)存警告時(shí),就需要手動(dòng)去清空緩存,避免應(yīng)用 crash。

其它

  1. 離屏渲染(Offscreen-Rendered)
  2. 圖層混合(Blended Layers)
  3. 復(fù)雜界面不使用 autolayout
  4. ...

結(jié)束語

簡(jiǎn)單的小結(jié)下,雖然整個(gè)演講都以 IM UI 為例,但實(shí)際上,在其它方面的開發(fā)也是類似套路的,以不變應(yīng)萬變,萬變不離其宗,程序開發(fā),最重要的是思路。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,347評(píng)論 25 708
  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一種新的協(xié)議。它實(shí)...
    香橙柚子閱讀 24,830評(píng)論 8 183
  • 書開了 頁跑了 逃逸的墨點(diǎn)追上了每縷陽光 連接思考的眼和所有的光明 只有你的名字 在吸引注意
    心種閱讀 173評(píng)論 0 0
  • 還能想起你第一天上學(xué)路上的所遇所見嗎 還是只記得那份激動(dòng)緊張了。 這本書,最先吸引我的是作者名和其中給人稚嫩淳樸讓...
    阿佛閱讀 2,238評(píng)論 0 0
  • 偶然,辦公室里談到送花給女朋友。一個(gè)男同事說,他曾經(jīng)送花過。那一年,沒有互聯(lián)網(wǎng),沒有微信包月訂花,沒有淘寶。他打了...
    若有一天閱讀 541評(píng)論 0 0

友情鏈接更多精彩內(nèi)容