app框架整体设计

简书无法正常显示部分流程图


应用启动的主要流程

st=>start: AppDelegate
islogin=>condition: isLogin?
login=>operation: http登录
tcp=>operation: tcp注册登录
newdesc=>operation: 功能引导
rootVC=>operation: 主控制器
mainVC=>operation: 主页
sync=>operation: 数据同步

e=>end



st->islogin
islogin(yes)->tcp->rootVC->e
islogin(no)->newdesc->login(left)->tcp->rootVC->mainVC->sync->e

1. 整体架构与模块化划分设计

项目采用Category方式设计把项目按照某个具体业务逻辑功能划分、模块之间未能够完全解耦,所以导致项目没办法使用pod方式管理 (采用通知回调是能够完全解耦,不实际、未采用)

项目功能模块

384CB19E-4141-47C3-8AB5-D93DA9A509DC.png


项目文件结构

项目文件结构.png

每一个模块文件结构相同,模块中的Actions与Category 负责本模块的功能被调度

项目文件结构展开.png

#import "IComMediator+DynamicModuleActions.h"
// action 类名 Dynamic 模块统一用Dynamic 模块名
NSString * const kIComMediatorTargetDynamic = @"Dynamic";

// 类对应方法名 一个模块下不同控制器的类名
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 {
    // 这里处理异常场景,具体如何处理取决于产品
    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

公共部分负责项目每个模块的整体调度与协作

负责公共部分.png

每个模块的Category调度器均为主调度器(IComMediator)的Category

IComMediator.h

#import <Foundation/Foundation.h>

@interface IComMediator : NSObject

+(instancetype)sharedInstance;

// 远程App调用入口
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion;

// 本地组件调用入口
- (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;
}

// 远程App调用入口
- (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion {
if (![url.scheme isEqualToString:@"icom"]) { // 外部启动规则
    // 这里就是针对远程app调用404的简单处理了
    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;
}

// 本地组件调用入口
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params {
// 运行时方式 让对应的目标类执行对应的目标方法
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) {
    // 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的
    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 {
    // 这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理
    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 {
        // 这里也是处理无响应请求的地方,在notFound都没有的时候,这个demo是直接return了。实际开发过程中,可以用前面提到的固定的target顶上的。
        return nil;
    }
}
}

模块间调度

    UIViewController *viewController = [[IComMediator sharedInstance]         IComMediator_DiscoverViewController:nil];
    [self.navigationController pushViewController:viewController animated:YES];

2. 长连接模块设计

B215DD52-0EB9-4EA8-B342-CA7DCAB4C0F5.png

st=>start: TCP登录
islogin=>condition: TCP登录成功?
sendBeat=>operation: 创建轮询发送心跳请求
sendBeatStatus=>condition: 心跳包发送成功?
reSendBeat=>operation: 重新发送机制
isbeatFailOut=>condition: 失败次数超限?
reSendF=>operation: 失败次数达到上限后判断是否断开连接,做相应处理


reConnect=>operation: tcp断开重新连接机制
reIsSuccess=>condition: 重连接机?
disConnect=>condition: 断开连接?
alert=>operation: 提示用户无法正常连接

login=>operation: http登录
tcp=>operation: tcp注册登录
newdesc=>operation: 功能引导
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数据同步机制流程

B3EBE84C-9FF0-47C6-99F7-F9300C91CBF9.png

st=>start: TCP登录成功
sync=>operation: imserver/groupList
syncStatus=>condition: grouplist?
saveDB=>operation: 修改(状态、未读数等)
updateGroupInfo=>operation: 更新群组信息
saveDB2=>operation: 存储群组最新信息
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. 消息发送机制

st=>start: 用户输入
isFile=>condition: file?
checkFile=>condition: 服务器存在?
createMessage=>operation: 创建消息体、生成消息唯一标识

sendFile=>operation: 创建临时消息体,发送文件拿到filekey

saveDB=>operation: 消息存储

sendMessage=>operation: 更新UI界面,消息发送
isSuccess=>condition:  消息发送成功?

disConnect=>condition: tcp连接?

update=>operation: 更新DB和UI
sendFail=>operation: 更新UI
sysReSend=>operation: tcp重新连接后系统重新发送

isTimeOut=>condition: 消息发送超时?
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.消息接收机制

进入聊天模块调用

st=>start
loadData=>operation: 本地数据库加载数据
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

消息返拉逻辑调用

st=>start
loadData=>operation: 本地数据库加载数据(data.firstId)
cheakCount=>condition: count>0?
cheak=>condition: data.firstId-db.firstId>=max?
repullMessage=>operation: repullMessage
cheakMaxId=>condition: maxId == nil?
isLastId=>condition: 最后一条数据?
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

消息接收逻辑调用

st=>start
tcpReceived=>operation: messageDidReceived
cmd=>condition: isCmd ?
updateCmd=>operation: updateCmdList
cmdListAction=>operation: 处理对应action
cheak=>condition: messageType == 0
groupList=>operation: updateGroupList
isExist=>condition: groupIsExist?
updateGroupStatus=>operation: 更新状态、未读、声音提示等信息
saveDB=>operation: 消息存储到本地数据库
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. 数据存储

C6CB068E-0EA6-4AA7-BD03-6F3DC90781CD.png

7. JS与WKWebView交互模块

st=>start: 协议定制
jsrequest=>operation: js请求
isBase=>condition: 基础接口?
isValidData=>condition: ValidData?
validData=>operation: 权限校验
hasPermission=>condition: 权限?
load=>operation: 调用客户端逻辑
loadSuccess=>condition: isSuccess?
success=>operation: 成功回调
fail=>operation: 错误回调

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' #修改过源代码 请勿更新
pod 'CocoaAsyncSocket', '~> 7.4.3'
pod 'SSKeychain',   '~> 1.4.0'
pod 'MJExtension',  '~> 3.0.10'
pod 'YYKit',    '~> 1.0.5' #修改过源代码 请勿更新
pod 'MLLabel',  '~> 1.9.1' #修改过源代码 请勿更新
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' #修改过源代码 请勿更新
pod 'CocoaLumberjack',  '~> 3.0.0' #修改过源代码 请勿更新
pod 'JSPatchPlatform', '~> 1.6.4'
pod 'TZImagePickerController',  '1.7.9'  #修改过源代码 请勿更新
pod 'mailcore2-ios', '0.6.4'
pod 'DKNightVersion', '2.4.3'
pod 'LKDBHelper', '2.4.2'
pod 'Bugtags'

end

9. 后期架构调整

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏C/C++基础

设计模式(8)——命令模式(Command Pattern,行为型)

使用设计模式可以提高代码的可复用性、可扩充性和可维护性。命令模式(Command Pattern)属行为型,将请求封装成对象,以便使用不同的请求、请求日志或请求...

552
来自专栏北京马哥教育

运维开发:你可能会忽略的 Git 提交规范

如果你有一个项目,从始至终都是自己写,那么你想怎么写都可以,没有人可以干预你。可是如果在团队协作中,大家都张扬个性,那么代码将会是一团糟,好好的项目就被糟践了。...

681
来自专栏腾讯移动品质中心TMQ的专栏

应用宝基于Robotium自动化测试(上)

1. 背景目的 应用宝项目组采用FT(Feature Team)模式,整个项目组分为多个FT,而每个FT又同时有多个需求分支在并行运作着,几乎每天都有多新特性合...

2346
来自专栏葡萄城控件技术团队

HTML5 & CSS3初学者指南(3) – HTML5新特性

介绍 本文介绍了 HTML5 的一些新特性。主要包含以下几个方面: Web 存储 地理位置 拖放 服务器发送事件 Web存储 HTML5...

1898
来自专栏jessetalks

从Membership 到 .NET4.5 之 ASP.NET Identity

我们前面已经讨论过了如何在一个网站中集成最基本的Membership功能,然后深入学习了Membership的架构设计。正所谓从实践从来,到实践从去,在我们把...

2876
来自专栏假装我会写代码

Laravel 5.5 LTS 正式发布!

1003
来自专栏逸鹏说道

探索ASP.NET MVC5系列之~~~5.缓存篇(页面缓存+二级缓存)

其实任何资料里面的任何知识点都无所谓,都是不重要的,重要的是学习方法,自行摸索的过程(不妥之处欢迎指正) 汇总:http://www.cnblogs.com/d...

2755
来自专栏一“技”之长

iOS中Today扩展插件与宿主APP的交互 原

        扩展是iOS8后系统开发给开发者的新开发思路与接口,每一个扩展都可以理解为一个简单的小应用程序,只是其不是独立存在的,要寄附于某一个主应用上。介...

561
来自专栏.NET开发者社区

(码友推荐)2018-07-05 .NET及相关开发资讯速递

4.High-Quality Code- Naming Classes, Interfaces, Enumerations

983
来自专栏WindCoder

Laravel API教程:如何构建和测试RESTful API

本文原文:Laravel API Tutorial: How to Build and Test a RESTful API

1222

扫码关注云+社区