應(yīng)用啟動(dòng)的主要流程
st=>start: AppDelegate
islogin=>condition: isLogin?
login=>operation: http登錄
tcp=>operation: tcp注冊(cè)登錄
newdesc=>operation: 功能引導(dǎo)
rootVC=>operation: 主控制器
mainVC=>operation: 主頁
sync=>operation: 數(shù)據(jù)同步
e=>end
st->islogin
islogin(yes)->tcp->rootVC->e
islogin(no)->newdesc->login(left)->tcp->rootVC->mainVC->sync->e
1. 整體架構(gòu)與模塊化劃分設(shè)計(jì)
項(xiàng)目采用Category方式設(shè)計(jì)把項(xiàng)目按照某個(gè)具體業(yè)務(wù)邏輯功能劃分、模塊之間未能夠完全解耦,所以導(dǎo)致項(xiàng)目沒辦法使用pod方式管理
(采用通知回調(diào)是能夠完全解耦,不實(shí)際、未采用)
項(xiàng)目功能模塊
384CB19E-4141-47C3-8AB5-D93DA9A509DC.png
項(xiàng)目文件結(jié)構(gòu)
項(xiàng)目文件結(jié)構(gòu).png
每一個(gè)模塊文件結(jié)構(gòu)相同,模塊中的Actions與Category 負(fù)責(zé)本模塊的功能被調(diào)度
項(xiàng)目文件結(jié)構(gòu)展開.png
#import "IComMediator+DynamicModuleActions.h"
// action 類名 Dynamic 模塊統(tǒng)一用Dynamic 模塊名
NSString * const kIComMediatorTargetDynamic = @"Dynamic";
// 類對(duì)應(yīng)方法名 一個(gè)模塊下不同控制器的類名
NSString * const kIComNativeFetchDynamicViewController = @"nativeFetchDynamicViewController";
NSString * const kIComNativeFetchDiscoverViewController = @"nativeFetchDiscoverViewController";
//DiscoverViewController
@implementation IComMediator (DynamicModuleActions)
- (UIViewController *)IComMediator_DynamicViewController:(NSDictionary *)params {
UIViewController *viewController = [self performTarget:kIComMediatorTargetDynamic
action:kIComNativeFetchDynamicViewController
params:params];
if ([viewController isKindOfClass:[UIViewController class]]) {
// view controller 交付出去之后,可以由外界選擇是push還是present
return viewController;
} else {
// 這里處理異常場(chǎng)景,具體如何處理取決于產(chǎn)品
return [[UIViewController alloc] init];
}
}
- (UIViewController *)IComMediator_DiscoverViewController: (NSDictionary *)params
{
UIViewController *viewController = [self performTarget:kIComMediatorTargetDynamic
action:kIComNativeFetchDiscoverViewController
params:params];
if ([viewController isKindOfClass:[UIViewController class]]) {
return viewController;
} else {
return [[UIViewController alloc] init];
}
}
@end
公共部分負(fù)責(zé)項(xiàng)目每個(gè)模塊的整體調(diào)度與協(xié)作
負(fù)責(zé)公共部分.png
每個(gè)模塊的Category調(diào)度器均為主調(diào)度器(IComMediator)的Category
IComMediator.h
#import <Foundation/Foundation.h>
@interface IComMediator : NSObject
+(instancetype)sharedInstance;
// 遠(yuǎn)程App調(diào)用入口
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion;
// 本地組件調(diào)用入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;
@end
IComMediator.m
#import "IComMediator.h"
#import "NSDictionary+URL.h"
@implementation IComMediator
+(instancetype)sharedInstance {
static IComMediator *mediator;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mediator = [[IComMediator alloc] init];
});
return mediator;
}
// 遠(yuǎn)程App調(diào)用入口
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion {
if (![url.scheme isEqualToString:@"icom"]) { // 外部啟動(dòng)規(guī)則
// 這里就是針對(duì)遠(yuǎn)程app調(diào)用404的簡(jiǎn)單處理了
return @(NO);
}
NSDictionary *params = [NSDictionary dictionaryWithURLQuery:[url query]];
NSString *actionName =
[url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
id result = [self performTarget:url.host action:actionName params:params];
if (completion) {
if (result) {
completion(@{ @"result" : result });
} else {
completion(nil);
}
}
return result;
}
// 本地組件調(diào)用入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params {
// 運(yùn)行時(shí)方式 讓對(duì)應(yīng)的目標(biāo)類執(zhí)行對(duì)應(yīng)的目標(biāo)方法
NSString *targetClassString =
[NSString stringWithFormat:@"Target_%@", targetName];
NSString *actionString =
[NSString stringWithFormat:@"Action_%@:", actionName];
Class targetClass = NSClassFromString(targetClassString);
id target = [[targetClass alloc] init];
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 這里是處理無響應(yīng)請(qǐng)求的地方之一,這個(gè)demo做得比較簡(jiǎn)單,如果沒有可以響應(yīng)的target,就直接return了。實(shí)際開發(fā)過程中是可以事先給一個(gè)固定的target專門用于在這個(gè)時(shí)候頂上,然后處理這種請(qǐng)求的
return nil;
}
if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
} else {
// 這里是處理無響應(yīng)請(qǐng)求的地方,如果無響應(yīng),則嘗試調(diào)用對(duì)應(yīng)target的notFound方法統(tǒng)一處理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
} else {
// 這里也是處理無響應(yīng)請(qǐng)求的地方,在notFound都沒有的時(shí)候,這個(gè)demo是直接return了。實(shí)際開發(fā)過程中,可以用前面提到的固定的target頂上的。
return nil;
}
}
}
模塊間調(diào)度
UIViewController *viewController = [[IComMediator sharedInstance] IComMediator_DiscoverViewController:nil];
[self.navigationController pushViewController:viewController animated:YES];
2. 長(zhǎng)連接模塊設(shè)計(jì)
B215DD52-0EB9-4EA8-B342-CA7DCAB4C0F5.png
st=>start: TCP登錄
islogin=>condition: TCP登錄成功?
sendBeat=>operation: 創(chuàng)建輪詢發(fā)送心跳請(qǐng)求
sendBeatStatus=>condition: 心跳包發(fā)送成功?
reSendBeat=>operation: 重新發(fā)送機(jī)制
isbeatFailOut=>condition: 失敗次數(shù)超限?
reSendF=>operation: 失敗次數(shù)達(dá)到上限后判斷是否斷開連接,做相應(yīng)處理
reConnect=>operation: tcp斷開重新連接機(jī)制
reIsSuccess=>condition: 重連接機(jī)?
disConnect=>condition: 斷開連接?
alert=>operation: 提示用戶無法正常連接
login=>operation: http登錄
tcp=>operation: tcp注冊(cè)登錄
newdesc=>operation: 功能引導(dǎo)
rootVC=>operation: 主控制器
mainVC=>operation: 主頁
e=>end
st->islogin
islogin(yes)->sendBeat->sendBeatStatus(yes)->e
sendBeatStatus(no)->isbeatFailOut(yes,right)->reConnect
isbeatFailOut(no)->e
disConnect(no,bottom)->e
islogin(no)->reConnect(right)->reIsSuccess
reIsSuccess(yes,left)->sendBeat
reIsSuccess(no)->alert->e
3. iCome數(shù)據(jù)同步機(jī)制流程
B3EBE84C-9FF0-47C6-99F7-F9300C91CBF9.png
st=>start: TCP登錄成功
sync=>operation: imserver/groupList
syncStatus=>condition: grouplist?
saveDB=>operation: 修改(狀態(tài)、未讀數(shù)等)
updateGroupInfo=>operation: 更新群組信息
saveDB2=>operation: 存儲(chǔ)群組最新信息
syncCmd=>operation: imserver/cmdList
syncCmdStatus=>condition: cmdList?
updateStatus=>operation: 同步本地的信息(修改頭像、踢出群組、日程同步等)
e=>end
st->sync->syncStatus(yes)->saveDB->updateGroupInfo->saveDB2->syncCmd->updateStatus->e
syncStatus(no)->e
4. 消息發(fā)送機(jī)制
st=>start: 用戶輸入
isFile=>condition: file?
checkFile=>condition: 服務(wù)器存在?
createMessage=>operation: 創(chuàng)建消息體、生成消息唯一標(biāo)識(shí)
sendFile=>operation: 創(chuàng)建臨時(shí)消息體,發(fā)送文件拿到filekey
saveDB=>operation: 消息存儲(chǔ)
sendMessage=>operation: 更新UI界面,消息發(fā)送
isSuccess=>condition: 消息發(fā)送成功?
disConnect=>condition: tcp連接?
update=>operation: 更新DB和UI
sendFail=>operation: 更新UI
sysReSend=>operation: tcp重新連接后系統(tǒng)重新發(fā)送
isTimeOut=>condition: 消息發(fā)送超時(shí)?
e=>end
st->isFile
isFile(yes)->checkFile
isFile(no)->createMessage->saveDB->sendMessage->disConnect(yes)->isSuccess
checkFile(yes,right)->createMessage
checkFile(no)->sendFile->createMessage
disConnect(no)->isTimeOut(no)->sysReSend->isSuccess
isTimeOut(yes)->sendFail
isSuccess(no)->sendFail->e
isSuccess(yes,right)->update->e
5.消息接收機(jī)制
進(jìn)入聊天模塊調(diào)用
st=>start
loadData=>operation: 本地?cái)?shù)據(jù)庫加載數(shù)據(jù)
cheak=>condition: db.lastId-db.firstId>=max?
repullMessage=>operation: repullMessage
saveDB=>operation: save DB
update=>operation: 更新UI界面
e=>end
st->loadData->cheak
cheak(yes)->repullMessage->saveDB->update->e
cheak(no)->update->e
消息返拉邏輯調(diào)用
st=>start
loadData=>operation: 本地?cái)?shù)據(jù)庫加載數(shù)據(jù)(data.firstId)
cheakCount=>condition: count>0?
cheak=>condition: data.firstId-db.firstId>=max?
repullMessage=>operation: repullMessage
cheakMaxId=>condition: maxId == nil?
isLastId=>condition: 最后一條數(shù)據(jù)?
update=>operation: 更新UI界面
e=>end
st->loadData->cheakCount
cheakCount(yes)->cheak
cheakCount(no)->cheakMaxId
cheakMaxId(yes)->repullMessage
cheakMaxId(no)->isLastId
isLastId(yes, right)->update
isLastId(no)->repullMessage
cheak(yes)->repullMessage->update->e
cheak(no)->update->e
消息接收邏輯調(diào)用
st=>start
tcpReceived=>operation: messageDidReceived
cmd=>condition: isCmd ?
updateCmd=>operation: updateCmdList
cmdListAction=>operation: 處理對(duì)應(yīng)action
cheak=>condition: messageType == 0
groupList=>operation: updateGroupList
isExist=>condition: groupIsExist?
updateGroupStatus=>operation: 更新狀態(tài)、未讀、聲音提示等信息
saveDB=>operation: 消息存儲(chǔ)到本地?cái)?shù)據(jù)庫
update=>operation: 通知更新聊天與消息列表界面
e=>end
st->tcpReceived->cmd
cmd(yes,right)->updateCmd->cmdListAction->e
cmd(no)->cheak
cheak(yes, right)->groupList->e
cheak(no)->isExist
isExist(yes)->updateGroupStatus->saveDB->update->e
isExist(no)->groupList->e
6. 數(shù)據(jù)存儲(chǔ)
C6CB068E-0EA6-4AA7-BD03-6F3DC90781CD.png
7. JS與WKWebView交互模塊
st=>start: 協(xié)議定制
jsrequest=>operation: js請(qǐng)求
isBase=>condition: 基礎(chǔ)接口?
isValidData=>condition: ValidData?
validData=>operation: 權(quán)限校驗(yàn)
hasPermission=>condition: 權(quán)限?
load=>operation: 調(diào)用客戶端邏輯
loadSuccess=>condition: isSuccess?
success=>operation: 成功回調(diào)
fail=>operation: 錯(cuò)誤回調(diào)
e=>end
st->jsrequest->isBase
isBase(yes)->load->loadSuccess
isBase(no)->hasPermission
hasPermission(yes)->load
hasPermission(no)->validData->loadSuccess
loadSuccess(yes)->success->e
loadSuccess(no)->fail->e
8. 第三方使用
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
target 'ICome' do
pod 'FMDB', '~> 2.6.2'
pod 'MBProgressHUD', '~> 1.0.0'
pod 'Masonry', '~> 1.0.2'
pod 'SDWebImage', '~> 3.7.6' #修改過源代碼 請(qǐng)勿更新
pod 'CocoaAsyncSocket', '~> 7.4.3'
pod 'SSKeychain', '~> 1.4.0'
pod 'MJExtension', '~> 3.0.10'
pod 'YYKit', '~> 1.0.5' #修改過源代碼 請(qǐng)勿更新
pod 'MLLabel', '~> 1.9.1' #修改過源代碼 請(qǐng)勿更新
pod 'UITableView+FDTemplateLayoutCell', '~> 1.4'
pod 'AFNetworking', '~> 3.1.0'
pod 'BaiduMapKit', '~> 3.3.1'
pod 'MJRefresh', '~> 3.1.12'
pod 'UMengAnalytics', '~> 4.2.4'
pod 'FDFullscreenPopGesture', '~>1.1'
pod 'CYLTabBarController', '~> 1.6.0' #修改過源代碼 請(qǐng)勿更新
pod 'CocoaLumberjack', '~> 3.0.0' #修改過源代碼 請(qǐng)勿更新
pod 'JSPatchPlatform', '~> 1.6.4'
pod 'TZImagePickerController', '1.7.9' #修改過源代碼 請(qǐng)勿更新
pod 'mailcore2-ios', '0.6.4'
pod 'DKNightVersion', '2.4.3'
pod 'LKDBHelper', '2.4.2'
pod 'Bugtags'
end