firemail

标题: 来自蘑菇街的开源IM:TeamTalk [打印本页]

作者: Qter    时间: 2020-2-22 23:14
标题: 来自蘑菇街的开源IM:TeamTalk
https://www.biaodianfu.com/teamtalk.html
[color=rgba(0, 0, 0, 0.8)]TeamTalk 是蘑菇街开源的一款企业办公即时通信软件,最初是为自己内部沟通而做的 IM 工具。
[color=rgba(0, 0, 0, 0.8)]项目框架
[color=rgba(0, 0, 0, 0.8)]麻雀虽小五脏俱全,本项目涉及到多个平台、多种语言,简单关系如下图:
[color=rgba(0, 0, 0, 0.8)]
[color=rgba(0, 0, 0, 0.8)]服务端:
[color=rgba(0, 0, 0, 0.8)]客户端:
[color=rgba(0, 0, 0, 0.8)]语言:c++、objective-c、java、php
[color=rgba(0, 0, 0, 0.8)]系统环境:Linux、Windows,Mac, iOS, Android
[color=rgba(0, 0, 0, 0.8)]作为整套系统的组成部分之一,TTServer为TeamTalk 客户端提供用户登录,消息转发及存储等基础服务。TTServer主要包含了以下几种服务器:
[color=rgba(0, 0, 0, 0.8)]当前支持的功能点:
[color=rgba(0, 0, 0, 0.8)]系统结构图
[color=rgba(0, 0, 0, 0.8)]
[color=rgba(0, 0, 0, 0.8)]消息收发流程:
[color=rgba(0, 0, 0, 0.8)]数据库操作:
[color=rgba(0, 0, 0, 0.8)]参考链接:http://www.bluefoxah.org/
[color=rgba(0, 0, 0, 0.8)]TeamTalk 之 Mac 客户端架构分析
[color=rgba(0, 0, 0, 0.8)]项目结构
[color=rgba(0, 0, 0, 0.8)]在软件架构中,一个项目的目录结构至关重要,它决定了整个项目的架构风格。通过一个规范的项目结构,我们应该能够很清楚的定位相应逻辑存放位置,以及能够没有歧义的在指定目录中进行新代码的撰写。项目结构便是项目的骨架,如果存在畸形和缺陷,项目的整体面貌就会受到很大影响。我们来看看TeamTalk的项目根结构:
[color=rgba(0, 0, 0, 0.8)]
[color=rgba(0, 0, 0, 0.8)]从整个项目结构图中,我们大致能猜出一些目录中存放的是什么,以下是这些目录的主要意图:
[color=rgba(0, 0, 0, 0.8)]首先,从总体来说,这样的目录结构划分,似乎可以涵盖到整个项目开发的所有场景,但它存在以下几个很明显的问题:
[color=rgba(0, 0, 0, 0.8)]项目结构的划分应该做到有迹可循,也就是说是按照一定的规则进行划分。这里主要的划分依据是逻辑模块化,这样的方式我还是比较赞同的,虽然有很多细节没有处理好,但主线还是很好的。
[color=rgba(0, 0, 0, 0.8)]网络数据处理
[color=rgba(0, 0, 0, 0.8)]在任何需要联网的应用中,网络数据处理都是非常重要的,这点在IM中更是毋庸置疑。IM与很多其它应用相比,更具挑战,它需要处理很多即时消息,并且很多时候需要自己去构建一套通讯机制。
[color=rgba(0, 0, 0, 0.8)]TeamTalk中,主要使用HTTP和TCP进行通讯,我们知道HTTP是基于TCP的更高层协议,而这里的TCP通讯是指用TCP协议发送自定义格式的报文。TeamTalk在HTTP通讯中使用的是RESTful API,并使用JSON格式与服务器进行交换数据;而在TCP这里,主要是通过ProtocolBuffer序列化协议,加上自定义的包头与服务器进行通信。
[color=rgba(0, 0, 0, 0.8)]HTTP数据处理
[color=rgba(0, 0, 0, 0.8)]HTTP的数据处理,在TeamTalk中显得非常简单,并没有做过多的设计。主要是使用AFNetworking封装了一个HTTP模块:
[color=rgba(0, 0, 0, 0.8)]DDHttpModule.h
[color=rgba(0, 0, 0, 0.8)][backcolor=rgb(248, 248, 255) !important][backcolor=rgb(238, 238, 238) !important][color=rgb(102, 102, 102) !important]










[color=rgb(170, 170, 170) !important]1

[color=rgb(170, 170, 170) !important]2

[color=rgb(170, 170, 170) !important]3

[color=rgb(170, 170, 170) !important]4

[color=rgb(170, 170, 170) !important]5

[color=rgb(170, 170, 170) !important]6

[color=rgb(170, 170, 170) !important]7

[color=rgb(170, 170, 170) !important]8

[color=rgb(170, 170, 170) !important]9

[color=rgb(170, 170, 170) !important]10

[color=rgb(170, 170, 170) !important]11


typedef[color=rgb(0, 111, 224) !important] [color=rgb(128, 0, 128) !important]void[color=rgb(51, 51, 51) !important]([color=rgb(0, 111, 224) !important]^[color=rgb(0, 45, 122) !important]SuccessBlock[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important]([color=teal !important]NSDictionary *[color=rgb(0, 45, 122) !important]result[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];
typedef[color=rgb(0, 111, 224) !important] [color=rgb(128, 0, 128) !important]void[color=rgb(51, 51, 51) !important]([color=rgb(0, 111, 224) !important]^[color=rgb(0, 45, 122) !important]FailureBlock[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important]([color=teal !important]StatusEntity*[color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]error[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];

[color=rgb(51, 51, 51) !important]@[color=rgb(128, 0, 128) !important]interface[color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]DDHttpModule[color=rgb(0, 111, 224) !important] [color=rgb(0, 111, 224) !important]:[color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]DDModule
[color=rgb(0, 111, 224) !important]
[color=rgb(0, 111, 224) !important]-[color=rgb(51, 51, 51) !important]([color=rgb(128, 0, 128) !important]void[color=rgb(51, 51, 51) !important])[color=rgb(0, 45, 122) !important]httpPostWithUri[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=teal !important]NSString *[color=rgb(51, 51, 51) !important])[color=teal !important]uri [color=rgb(0, 45, 122) !important]params[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=teal !important]NSDictionary *[color=rgb(51, 51, 51) !important])[color=teal !important]params [color=rgb(0, 45, 122) !important]success[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=rgb(0, 45, 122) !important]SuccessBlock[color=rgb(51, 51, 51) !important])[color=teal !important]success [color=rgb(0, 45, 122) !important]failure[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=rgb(0, 45, 122) !important]FailureBlock[color=rgb(51, 51, 51) !important])[color=rgb(0, 45, 122) !important]failure[color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]-[color=rgb(51, 51, 51) !important]([color=rgb(128, 0, 128) !important]void[color=rgb(51, 51, 51) !important])[color=rgb(0, 45, 122) !important]httpGetWithUri[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=teal !important]NSString *[color=rgb(51, 51, 51) !important])[color=teal !important]uri [color=rgb(0, 45, 122) !important]params[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=teal !important]NSDictionary *[color=rgb(51, 51, 51) !important])[color=teal !important]params [color=rgb(0, 45, 122) !important]success[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=rgb(0, 45, 122) !important]SuccessBlock[color=rgb(51, 51, 51) !important])[color=teal !important]success [color=rgb(0, 45, 122) !important]failure[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=rgb(0, 45, 122) !important]FailureBlock[color=rgb(51, 51, 51) !important])[color=rgb(0, 45, 122) !important]failure[color=rgb(51, 51, 51) !important];

[color=rgb(51, 51, 51) !important]@end

[color=teal !important]extern [color=teal !important]DDHttpModule*[color=rgb(0, 111, 224) !important] [color=teal !important]getDDHttpModule[color=rgb(51, 51, 51) !important]([color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];



[color=rgba(0, 0, 0, 0.8)]这样一个模块会被其它模块进行使用,直接传递uri请求服务器,并解析响应,以下是一个使用场景:
[color=rgba(0, 0, 0, 0.8)]DDHttpServer.m
[color=rgba(0, 0, 0, 0.8)][backcolor=rgb(248, 248, 255) !important][backcolor=rgb(238, 238, 238) !important][color=rgb(102, 102, 102) !important]








[color=rgb(170, 170, 170) !important]1

[color=rgb(170, 170, 170) !important]2

[color=rgb(170, 170, 170) !important]3

[color=rgb(170, 170, 170) !important]4

[color=rgb(170, 170, 170) !important]5

[color=rgb(170, 170, 170) !important]6

[color=rgb(170, 170, 170) !important]7

[color=rgb(170, 170, 170) !important]8

[color=rgb(170, 170, 170) !important]9

[color=rgb(170, 170, 170) !important]10

[color=rgb(170, 170, 170) !important]11

[color=rgb(170, 170, 170) !important]12

[color=rgb(170, 170, 170) !important]13

[color=rgb(170, 170, 170) !important]14

[color=rgb(170, 170, 170) !important]15

[color=rgb(170, 170, 170) !important]16


[color=rgb(0, 111, 224) !important]-[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]([color=rgb(128, 0, 128) !important]void[color=rgb(51, 51, 51) !important])[color=rgb(0, 45, 122) !important]loginWithUserName[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=teal !important]NSString*[color=rgb(51, 51, 51) !important])[color=teal !important]userName
[color=teal !important]                 [color=rgb(0, 45, 122) !important]password[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=teal !important]NSString*[color=rgb(51, 51, 51) !important])[color=teal !important]password
[color=teal !important]                  [color=rgb(0, 45, 122) !important]success[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=rgb(128, 0, 128) !important]void[color=rgb(51, 51, 51) !important]([color=rgb(0, 111, 224) !important]^[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important]([color=teal !important]id [color=rgb(0, 45, 122) !important]respone[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important])[color=teal !important]success
[color=teal !important]                  [color=rgb(0, 45, 122) !important]failure[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=rgb(128, 0, 128) !important]void[color=rgb(51, 51, 51) !important]([color=rgb(0, 111, 224) !important]^[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important]([color=teal !important]id [color=rgb(0, 45, 122) !important]error[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important])[color=teal !important]failure
[color=rgb(51, 51, 51) !important]{
[color=rgb(0, 111, 224) !important]    [color=teal !important]DDHttpModule*[color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]module[color=rgb(0, 111, 224) !important] [color=rgb(0, 111, 224) !important]=[color=rgb(0, 111, 224) !important] [color=teal !important]getDDHttpModule[color=rgb(51, 51, 51) !important]([color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]    [color=teal !important]NSMutableDictionary*[color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]dictParams[color=rgb(0, 111, 224) !important] [color=rgb(0, 111, 224) !important]=[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important][[color=teal !important]NSMutableDictionary [color=rgb(0, 45, 122) !important]dictionary[color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]   
[color=rgb(0, 111, 224) !important]    [color=rgb(51, 51, 51) !important].[color=rgb(51, 51, 51) !important].[color=rgb(51, 51, 51) !important].[color=rgb(51, 51, 51) !important](省略参数赋值[color=rgb(51, 51, 51) !important])
[color=rgb(0, 111, 224) !important]   
[color=rgb(0, 111, 224) !important]    [color=rgb(51, 51, 51) !important][[color=rgb(51, 51, 51) !important][[color=teal !important]NSURLCache [color=rgb(0, 45, 122) !important]sharedURLCache[color=rgb(51, 51, 51) !important][color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]removeAllCachedResponses[color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]    [color=rgb(51, 51, 51) !important][[color=teal !important]module [color=rgb(0, 45, 122) !important]httpPostWithUri[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]@[color=rgb(221, 17, 68) !important]"user/zlogin/"[color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]params[color=rgb(0, 111, 224) !important]:[color=teal !important]dictParams
[color=teal !important]                    [color=rgb(0, 45, 122) !important]success[color=rgb(0, 111, 224) !important]:[color=rgb(0, 111, 224) !important]^[color=rgb(51, 51, 51) !important]([color=teal !important]NSDictionary *[color=rgb(0, 45, 122) !important]result[color=rgb(51, 51, 51) !important])[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]{[color=rgb(0, 111, 224) !important] [color=teal !important]success[color=rgb(51, 51, 51) !important]([color=rgb(0, 45, 122) !important]result[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]}
[color=rgb(0, 111, 224) !important]                    [color=rgb(0, 45, 122) !important]failure[color=rgb(0, 111, 224) !important]:[color=rgb(0, 111, 224) !important]^[color=rgb(51, 51, 51) !important]([color=teal !important]StatusEntity *[color=rgb(0, 45, 122) !important]error[color=rgb(51, 51, 51) !important])[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]{[color=rgb(0, 111, 224) !important] [color=teal !important]failure[color=rgb(51, 51, 51) !important]([color=rgb(0, 45, 122) !important]error[color=rgb(51, 51, 51) !important].[color=rgb(0, 45, 122) !important]msg[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]}
[color=rgb(0, 111, 224) !important]    [color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important];
[color=rgb(51, 51, 51) !important]}



[color=rgba(0, 0, 0, 0.8)]即便是这样的一个封装,在后续的迭代中似乎也慢慢失去了作用,目前大部分所使用到HTTP的代码里,都是直接使用AFNetworking,那么这样的一个封装已经没有存在的必要了。
[color=rgba(0, 0, 0, 0.8)]TCP数据处理
[color=rgba(0, 0, 0, 0.8)]在TeamTalk里,针对TCP的数据处理略显复杂,因为没有类似AFNetworking这样的类库,所以需要自己封装一套处理机制。大致类图如下:
[color=rgba(0, 0, 0, 0.8)]
[color=rgba(0, 0, 0, 0.8)]通过这样的一个类图,我们大致可以推断出设计者的抽象思维,他把所有网络操作抽象为API。基于这样思路,这里有三个最核心的类:
[color=rgba(0, 0, 0, 0.8)]基于这样一个设计,我们来看一个基本的登录操作序列图:
[color=rgba(0, 0, 0, 0.8)]
[color=rgba(0, 0, 0, 0.8)]所有基于请求响应模式的操作,都是与上图类似,而服务端推送过来的消息,也是类似,只是没有了请求的过程。通过我的分析,大家觉得这样的设计怎么样?首先从扩展性的角度考虑,每一个API都相对独立,增加新的API非常容易,所以扩展性还是很不错的;其次从健壮性的角度考虑,每一个API都由调度器管理,调度器可以对API进行一些容错处理,API本身也可以做一些容错处理,这一点也还是可以的;最后从使用者的角度考虑,API对外暴露的接口非常简单,并且对于异步操作使用Block返回,对于组织代码还是非常有用的,所以使用者也觉得良好。
[color=rgba(0, 0, 0, 0.8)]那么,这是一个完美的设计了么?我说过,没有完美的设计,只有符合特定场景的设计。针对这个设计,撇开它一些命名问题,以下是我觉得它不足的地方:
[color=rgba(0, 0, 0, 0.8)]总体来说,这样的一个框架还是不错的,因为它的抽象层次不高,很容易去理解和维护,并且完成了大家的预期,这样或许就已经足够了。
[color=rgba(0, 0, 0, 0.8)]本地持久化
[color=rgba(0, 0, 0, 0.8)]本地持久化是个可以有很多设计的地方,但在APP中,进行设计的情况并不是很多,因为APP本身对于持久化的要求没有MIS高,一般只是做些离线缓存,而在IM中,它还负责存储历史消息等结构化数据。TeamTalk对于持久化这块,也没有做什么设计,只是依托于FMDB封装了一个MTDatabaseUtil,这是一个类似于Helper的存在,里面聚集了所有APP会用到的存储方法。毋庸置疑,这样的封装会导致类比较庞大,好在TeamTalk中存储方法并不多,并且使用了Catagory对方法进行了分类,所以总体感觉也还是可以的。另外,从残存的目录结构中可以看出,TeamTalk原本可能是想采用CoreData,但最终放弃了,或许是觉得CoreData整体不够轻量级吧。
[color=rgba(0, 0, 0, 0.8)]MTDatabaseUtil和API一样,都只能算是基础设施(Infrastructure),给高层模块提供支持,高层模块会使用这些基础设施根据业务逻辑进行封装,可以看一个具的代码片段:
[color=rgba(0, 0, 0, 0.8)]MTGroupModule.m
[color=rgba(0, 0, 0, 0.8)][backcolor=rgb(248, 248, 255) !important][backcolor=rgb(238, 238, 238) !important][color=rgb(102, 102, 102) !important]










[color=rgb(170, 170, 170) !important]1

[color=rgb(170, 170, 170) !important]2

[color=rgb(170, 170, 170) !important]3

[color=rgb(170, 170, 170) !important]4

[color=rgb(170, 170, 170) !important]5

[color=rgb(170, 170, 170) !important]6

[color=rgb(170, 170, 170) !important]7

[color=rgb(170, 170, 170) !important]8

[color=rgb(170, 170, 170) !important]9

[color=rgb(170, 170, 170) !important]10

[color=rgb(170, 170, 170) !important]11

[color=rgb(170, 170, 170) !important]12

[color=rgb(170, 170, 170) !important]13

[color=rgb(170, 170, 170) !important]14

[color=rgb(170, 170, 170) !important]15

[color=rgb(170, 170, 170) !important]16


[color=rgb(0, 111, 224) !important]-[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]([color=rgb(128, 0, 128) !important]void[color=rgb(51, 51, 51) !important])[color=rgb(0, 45, 122) !important]getOriginEntityWithOriginIDsFromRemoteCompletion[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=teal !important]NSArray*[color=rgb(51, 51, 51) !important])[color=teal !important]originIDs [color=rgb(0, 45, 122) !important]completion[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]([color=rgb(0, 45, 122) !important]DDGetOriginsInfoCompletion[color=rgb(51, 51, 51) !important])[color=teal !important]completion[color=rgb(51, 51, 51) !important]{
[color=rgb(0, 111, 224) !important]   
[color=rgb(0, 111, 224) !important]    [color=rgb(51, 51, 51) !important].[color=rgb(51, 51, 51) !important].[color=rgb(51, 51, 51) !important].(省略)
[color=rgb(0, 111, 224) !important]   
[color=rgb(0, 111, 224) !important]    [color=teal !important]DDGroupInfoAPI *[color=rgb(0, 45, 122) !important]api[color=rgb(0, 111, 224) !important] [color=rgb(0, 111, 224) !important]=[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important][[color=rgb(51, 51, 51) !important][[color=teal !important]DDGroupInfoAPI [color=rgb(0, 45, 122) !important]alloc[color=rgb(51, 51, 51) !important][color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]init[color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]    [color=rgb(51, 51, 51) !important][[color=teal !important]api [color=rgb(0, 45, 122) !important]requestWithObject[color=rgb(0, 111, 224) !important]:[color=teal !important]param [color=rgb(0, 45, 122) !important]Completion[color=rgb(0, 111, 224) !important]:[color=rgb(0, 111, 224) !important]^[color=rgb(51, 51, 51) !important]([color=teal !important]id [color=rgb(0, 45, 122) !important]response[color=rgb(51, 51, 51) !important],[color=rgb(0, 111, 224) !important] [color=teal !important]NSError *[color=rgb(0, 45, 122) !important]error[color=rgb(51, 51, 51) !important])[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]{
[color=rgb(0, 111, 224) !important]        if[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]([color=rgb(0, 111, 224) !important]![color=rgb(0, 45, 122) !important]error[color=rgb(51, 51, 51) !important])[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important]{
[color=rgb(0, 111, 224) !important]            [color=teal !important]NSMutableArray*[color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]groupInfos[color=rgb(0, 111, 224) !important] [color=rgb(0, 111, 224) !important]=[color=rgb(0, 111, 224) !important] [color=rgb(51, 51, 51) !important][[color=teal !important]response [color=rgb(0, 45, 122) !important]objectForKey[color=rgb(0, 111, 224) !important]:[color=rgb(51, 51, 51) !important]@[color=rgb(221, 17, 68) !important]"groupList"[color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]            [color=rgb(51, 51, 51) !important][self[color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]addMaintainOriginEntities[color=rgb(0, 111, 224) !important]:[color=rgb(0, 45, 122) !important]groupInfos[color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]            [color=rgb(51, 51, 51) !important][[color=rgb(51, 51, 51) !important][[color=teal !important]MTDatabaseUtil [color=rgb(0, 45, 122) !important]instance[color=rgb(51, 51, 51) !important][color=rgb(0, 111, 224) !important] [color=rgb(0, 45, 122) !important]insertGroups[color=rgb(0, 111, 224) !important]:[color=rgb(0, 45, 122) !important]groupInfos[color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]            [color=teal !important]completion[color=rgb(51, 51, 51) !important]([color=rgb(0, 45, 122) !important]groupInfos[color=rgb(51, 51, 51) !important],[color=rgb(0, 45, 122) !important]error[color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]        [color=rgb(51, 51, 51) !important]}else[color=rgb(51, 51, 51) !important]{
[color=rgb(0, 111, 224) !important]            [color=teal !important]DDLog[color=rgb(51, 51, 51) !important]([color=rgb(51, 51, 51) !important]@[color=rgb(221, 17, 68) !important]"erro:%@"[color=rgb(51, 51, 51) !important],[color=rgb(51, 51, 51) !important][[color=teal !important]error [color=rgb(0, 45, 122) !important]domain[color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important])[color=rgb(51, 51, 51) !important];
[color=rgb(0, 111, 224) !important]        [color=rgb(51, 51, 51) !important]}
[color=rgb(0, 111, 224) !important]    [color=rgb(51, 51, 51) !important]}[color=rgb(51, 51, 51) !important][color=rgb(51, 51, 51) !important];
[color=rgb(51, 51, 51) !important]}



[color=rgba(0, 0, 0, 0.8)]理想中,只会在业务模块里依赖持久化操作库,但从TeamTalk总体使用情况中看,并不是这么理想,很多Controller里面直接对MTDatabaseUtil进行了操作,这样就削弱了模块化封装的意义。显然,Controller的职责不应该牵扯到数据持久化,这些都应该放置在相应的业务模块里,统一对外屏蔽这些实现细节。
[color=rgba(0, 0, 0, 0.8)]模块化设计
[color=rgba(0, 0, 0, 0.8)]模块化设计是更高层次的抽象和复用,也是业务不断发展后必然的设计趋势。在进入目前公司的第二周例会上,我便分享了一个亲手设计的模块化框架,这个框架和TeamTalk模块化框架有很多类似之处,好坏暂不做对比,我们先看看TeamTalk中的一个模块化架构。在TeamTalk的DDLogic目录下,隐藏着一个模块化的设计,这也是整个项目中模块设计的基础构件,以下是这个设计的核心类图:
[color=rgba(0, 0, 0, 0.8)]
[color=rgba(0, 0, 0, 0.8)]整个设计还是很简单明了的,但不知是TeamTalk设计者更换了,还是原设计者变心了,导致这个模块化设计没有起到它预期的作用。具体原因就不细究了,但这样的设计还是值得去推演的,就目前这样的设计而言,也还是缺少了一些东西:
[color=rgba(0, 0, 0, 0.8)]虽然我觉得有点缺失,但还是很欣慰的看到了这样的模块化设计,又让我想起一些往事,这种心情,就像遇见了一个和初恋很像的人。
[color=rgba(0, 0, 0, 0.8)]UI相关设计
[color=rgba(0, 0, 0, 0.8)]整个UI设计也没什么特别之处,主要还是采用了xib进行布局,然后连线到相应的Controller中,这里主要的WindowController是DDMainWindowController,它是在登录窗口消失后出现的,也就是DDLoginWindowController所控制的窗口消失后。
[color=rgba(0, 0, 0, 0.8)]值得一提的是,这里将所有的UI都放置到了相应的业务模块中,这也是我比较推崇的做法。一个模块本就应该能够自成一系,它应该有自己的Model,有自己的View,也有自己的Controller,还可以有自己的Service等。这样设计下的模块才会显得更加内聚,其实设计就是这么简单,小到类,大到组件都应该遵循内聚的原则。
[color=rgba(0, 0, 0, 0.8)]其它组件
[color=rgba(0, 0, 0, 0.8)]TeamTalk中还使用了一些个第三方组件,具体罗列如下:
[color=rgba(0, 0, 0, 0.8)]总结
[color=rgba(0, 0, 0, 0.8)]TeamTalk作为一个敢于开源出来的IM,还是非常值得赞扬的,国内的技术氛围一直提高不起来,大家似乎都在闭门造车。如果多一些像蘑菇街这样的开源行为,应该能够更好的促进圈子里的技术生态。虽然,这篇博文里提出了很多TeamTalk Mac客户端架构的不足之处,但,设计本身就是如此,根本没有最好的设计,而,每个设计者的眼光也不相同,或许我说得都不正确也不见得。
[color=rgba(0, 0, 0, 0.8)]所以,只要有颗敢于尝试设计的心,开放的态度,一切问题都不是问题。
[color=rgba(0, 0, 0, 0.8)]原文地址:http://blog.makeex.com/2015/05/30/the-architecture-of-teamtalk-mac-client/
[color=rgba(0, 0, 0, 0.8)]TT流程随笔
[color=rgba(0, 0, 0, 0.8)]细节:
[color=rgba(0, 0, 0, 0.8)]接下来就是对服务端发送消息过来的分析
[color=rgba(0, 0, 0, 0.8)]消息的处理都是在相关的管理器类实例内完成
[color=rgba(0, 0, 0, 0.8)]该存库的存库,该更新内存的,更新内存,然后发送事件总线更新ui  或者通知service中的相关订阅者,完成业务逻辑的数据相关处理
[color=rgba(0, 0, 0, 0.8)]相关网址:






欢迎光临 firemail (http://www.firemail.wang:8088/) Powered by Discuz! X3