早期 iCome iOS客户端设计
时间:2022-06-01
本文章向大家介绍早期 iCome iOS客户端设计,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
应用启动的主要流程
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方式设计把项目按照某个具体业务逻辑功能划分、采用中间调度模块IComMediator管理输入与回调内容进行模块化解耦
#import <Foundation/Foundation.h>
typedef void (^IComMediatorObjectHandler)(id result);
@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;
// 充分模块化解耦 存储block用页面跳转调用
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params completion:(IComMediatorObjectHandler)completion;
// 所有b页面回调
- (void)toHandlerTargetName:(NSString *)targetName action:(NSString *)actionName param:(id)param;
@end
项目功能模块
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];
模块注册 每个模块注册IComMediator的Category后声明需要提供外部模块调用的类
#import "IComMediator+DynamicModuleActions.h"
// action 类名 Work 模块统一用Work
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];
}
}
模块声明
#ifndef ICModuleHeaderFile_h
#define ICModuleHeaderFile_h
#import "IComMediator+MineModuleActions.h"
#import "IComMediator+ApplicationActions.h"
#import "IComMediator+LoginModuleActions.h"
#import "IComMediator+JobModuleActions.h"
#import "IComMediator+SpaceModuleActions.h"
#import "IComMediator+MessageModuleActions.h"
#import "IComMediator+ContactsModuleActions.h"
#import "IComMediator+DynamicModuleActions.h"
#import "IComMediator+RedPacketModuleActions.h"
#import "IComMediator+Email.h"
#import "IComMediator+LifeCircleActions.h"
#import "IComMediator+Calendar.h"
#import "IComMediator+News.h"
#import "IComMediator+Window.h"
#import "IComMediator+ChatModuleActions.h"
#import "IComMediator+WeexSDK.h"
#endif /* ICModuleHeaderFile_h */
模块调用
case ICWindowRoomViewTypeGrow:
{
NSDictionary * dict = @{@"title":@"成长空间",@"spaceType":@2};
UIViewController *viewController =[[IComMediator sharedInstance] IComMediator_SpaceViewController:dict];
[self.navigationController pushViewController:viewController animated:YES];
}
break;
case ICWindowRoomViewTypeInfor:
{
UIViewController *newsViewController = [[IComMediator sharedInstance] IComMediator_NewsViewController:nil];
[self.navigationController pushViewController:newsViewController animated:YES];
}
break;
case ICWindowRoomViewTypeCare:
{
//NSDictionary * dict = @{@"title":@"关怀空间",@"spaceType":@3};
UIViewController *lifeCircleViewController = [[IComMediator sharedInstance] IComMediator_LifeCircleViewController:nil];
[self.navigationController pushViewController:lifeCircleViewController animated:YES];
// UIViewController *viewController =[[IComMediator sharedInstance] IComMediator_SpaceViewController:dict];
// [self.navigationController pushViewController:viewController animated:YES];
}break;
```
##2. TCP连接模块设计
![B215DD52-0EB9-4EA8-B342-CA7DCAB4C0F5.png](http://upload-images.jianshu.io/upload_images/1775503-da62d481d2c5d647.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
```flow
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. 页面统一调用逻辑
通过URL Router方式处理原生、网页、weex页面的调用
- (void)pushCustomViewControllerURL:(NSString *)URL
fromViewController:(UIViewController *)ViewController
animated:(BOOL)animated {
if ([URL hasPrefix:@"icome"]) { // 打开本地自定义界面
NSString *tempUrl = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *pushUrl = [NSURL URLWithString:tempUrl];
NSString *scheme = pushUrl.scheme;
NSString *host = pushUrl.host;
NSString *path = pushUrl.path;
NSString *query = pushUrl.query;
NSDictionary *queryDict = nil;
if (!kStringIsEmpty(query)) {
queryDict = [NSDictionary dictionaryWithURLQuery:query];
}
NSString *coustId = nil;
if (!kStringIsEmpty(path)) {
coustId = [[path componentsSeparatedByString:@"/"] objectAtIndex:1]; //
}
NSString *class = host;
// 日程
if ([class isEqualToString:@"schedule"]) {
NSDictionary *param = nil;
if (!kStringIsEmpty(queryDict[@"beginDate"])) {
param = @{@"beginDate":queryDict[@"beginDate"]};
}
UIViewController *viewController = [[IComMediator sharedInstance] IComMediator_CalendarViewController:param];
[ViewController.navigationController pushViewController:viewController animated:YES];
}
}
9. 第三方使用
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
10. 后期架构调整
邮箱登录逻辑
st=>start: 进入邮箱界面
sync=>operation: 默认邮箱地址进行登录
syncStatus=>condition: 邮箱登录成功?
saveDB=>operation: 保存邮箱地址与密码供下次登录
mailLogin=>operation: 邮箱登录界面
userEdt=>operation: 用户编辑邮箱信息登录
check=>condition: 本地校验邮箱格式?
login=>operation: 登录中..
loginStatus=>condition: 第二次登录是否成功?
traverseLogin=>operation: 遍历登录邮箱...
e=>end
st->sync->syncStatus(yes, right)->saveDB->e
syncStatus(no)->mailLogin->userEdt->check(yes)->login->loginStatus(yes)->saveDB->e
check(no)->userEdt
loginStatus(no)->traverseLogin->saveDB->e
- apache开启openssl支持
- 微信小程序开发常见问题(二)
- PHP数据结构(二十一) ——希尔排序
- PHP数据结构(二十二) ——快速排序
- PHP数据结构(二十三) ——快速排序
- PHP数据结构(二十四) ——堆排序
- PHP数据结构(二十五) ——并归排序
- PHP数据结构(二十六) ——基数排序实现36进制数排序
- Apache配置
- jquery事件
- 设计模式专题(二)——策略模式
- ASP.NET AJAX(10)__Authentication ServiceAuthentication ServiceAuthentication Service属性Authentication
- 高效开发 MVVM 和 databinding 你需要使用的工具
- ASP.NET AJAX(9)__Profile Service什么是ASP.NET Profile如何使用ASP.NET ProfileProfile ServiceProfile Service预
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- Python闭包及装饰器运行原理解析
- Django中Q查询及Q()对象 F查询及F()对象用法
- keras.layer.input()用法说明
- python入门:argparse浅析 nargs='+'作用
- PHP7导出Excel报ERR_EMPTY_RESPONSE解决方法
- YII框架行为behaviors用法示例
- 浅谈Python里面None True False之间的区别
- python如何导入依赖包
- 深入理解Python 多线程
- Yii2框架自定义验证规则操作示例
- 浅析PHP 中move_uploaded_file 上传中文文件名失败
- 结束运行python的方法
- 解析Tensorflow之MNIST的使用
- 面向新手解析python Beautiful Soup基本用法
- 基于keras中的回调函数用法说明