iOS组件化及多项目开发解决方案

组件化开发是大家经常讨论的话题,也是随着项目的迭代、工程会变得越来越臃肿而想到的解决方案,其实目前网上组件化方案非常多,每一种都有自己的优缺点,我个人觉得没有最好的方案,只有最适合自己项目的方案。

在这里给大家介绍下最近我在公司用的一套组件化解决方案:项目中组件通过protocol协议依赖,达到组件间的解耦合的效果,组件不需要知道其它组件具体实现类,即使其它组件不存在也可以正常编译运行,业务组件可快速集成到其它项目中,同时可通过配置文件解决多个项目中存在差异化的问题。

下图是项目的架构层级图:

项目架构层级图

这里主要介绍下基础组件层中三个组件的具体实现:

  • YPLaunchManager 用于管理各个业务模块在 UIApplicationDelegate 事件中的任务,隔离业务与App Delegate之间的耦合;

  • YPProtocolMediator Protocol-ClassName 组件化实现方案的中间件,用于各个业务模块之间的通信;

  • YPAppModuleConfigManager 管理各个业务module的配置,主要解决一个module用于不同项目中时且存在差异的问题。

1,YPLaunchManager 用于对AppDelegate与业务功能/模块解耦

顾名思义是一个启动任务管理器,iOS 项目启动相关的事件都在AppDelegate.m 文件里面,因此YPLaunchManager是为了解决 AppDelegate 过于复杂且易耦合其它业务模块的问题。

YPLaunchManager 用于管理YPLaunchTask任务,每个业务功能/模块需继承自YPLaunchTask,并且实现相应的协议方法,通过 load 方案进行自动注册,还可以通过finishAfter 来控制业务功能/模块的生命周期,

@interface YPXXXXXLaunchTask : YPLaunchTask
@end

......

// load 方法里面增加 YPLaunchTaskInitNotification 初始化注册的通知,在通知里面调用 +add 来注册self
+ (void)load {
    [[NSNotificationCenter defaultCenter] addObserverForName:YPLaunchTaskInitNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        [[self class] add];
    }];
}

// 通过 priority 来控制每个任务的优先级调用
- (NSUInteger)priority {
    return YPCoreSessionPriority;
}

// 通过finishAfterRun控制Task的生命周期
- (BOOL)finishAfterRun {
    return NO;
}

// 实现 YPLaunchTaskDelegate,与 UIApplicationDelegate 代理方法一致
- (void)runAppDidFinishLauchingBeforeUI {
    // 初始化网络相关的数据
}

- (void)runAppDidFinishLauchingAfterUI {
}

最后AppDelegate 里面的代码如下:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 发送 YPLaunchTaskInitNotification 通知,注册所有的LaunchTask
    [[YPLaunchManager defautlManager] loadAllLaunchTask];
    [[YPLaunchManager defautlManager] runAppDidFinishLauchingBeforeUI];

    //这里需要 设置 self.window.rootViewController = xxx;

    [[YPLaunchManager defautlManager] runAppDidFinishLauchingAfterUI];

    return YES;
}

- (void)applicationDidEnterBackground:(UIApplication *)application {
    [[YPLaunchManager defautlManager] runAppDidEnterBackground];
}

附YPLaunchManager的时序图:

YPLaunchManager的时序图
2,YPProtocolMediator 是一种 Protocol-ClassName 组件化方案的中间件

YPProtocolMediator 通过Protocol与Modlue 的 ClassName匹配进行查找到具体实现的组件类,然后组件类实现的Protocol协议方法来实现通信的一种机制。

每个组件如果需要外界主动调用则需要实现port协议,如果需要向外界传递数据则应实现delegate协议,以下是 YPProtocolMediator 的框架图:

YPProtocolMediator
  • XXX_Port 定义其它模块调用的接口协议

  • XXX_Delegate 定义其它模块接收数据的接口协议;

以下是A与B模块的通信的实例:

// ModuleA 从ModuleB 获取数据

// 方式一,通过 port_excuteSelector 方法通信,即使@selector 为声明编译也不会报错
 NSString *uid = [YPMediatorPort(YPUserCenterPort) port_excuteSelector:@selector(port_Uid)];

// selector 带参数时用下面的方法,注意的是args 传入的是可变参数,参数不能为nil
[YPMediatorPort(YPUserCenterPort) port_excuteSelector:@selector(port_Login:password:) args:PhoneNumber, YPParamVaule(Value), nil];

// 方式二:通过获取 ModuleB 的实例,直接调用方法
NSNumber *userId = [YPMediatorPort(YPUserCenterPort) port_Uid];

[YPMediatorPort(ModuleXXXB_Port) port_Login:@"phoneNumber" password:@"password"];

方式一 和 方式二的区别是,如果 ModuleXXXB_Port 更新且不兼容旧版,删除了某个方法。

方式一可以编译通过,但是会报警告;

方式二编译不通过,直接报错;

所以当 ModuleA 强依赖 ModuleB 里面的某个方法时,则建议使用方式二通信,其它情况则建议方式一进行通信。

ModuleA 接收 ModuleB 的数据传递:

// 方法一:通过 YPProtocolMediator 组件实现的给类对象动态添加函数的方法(-addSelector:forProtocol:block:bindTarget:)进行通信 
// 向 ModuleA 类添加方法通过 block 回调来实现数据传递
[self yp_addDelegateSelector:@selector(userCenter:loginStatus:) paramBlock:^(YPParams *params) {
    id targetB = params[0];
    NSNumber *loginStatus = [params objectAtIndex:1];
    NSLog(@"target %@ %@, status is %@",targetB, loginStatus);
} forProtocol:@protocol(YPUserCenterDelegate)];

// 方法二:可以像传统的delegate来实现数据传递
// 1,将self 与 ModuleXXXB_Delegate 代理绑定
YPMediatorBind(self, ModuleXXXB_Delegate);
// 2,ModuleA 实现 ModuleXXXB_Delegate 的代理方法
- (void)userCenter:(id<ModuleXXXB_Port>)moduleXXXB loginStatus:(BOOL)loginStatus {
}

YPProtocolMediator 组件查找实现:

诚如上面说的 ,YPProtocolMediator 查找Module 是通过Protocol 与 ClassName 匹配进行查找的。

实现代码如下:

/**根据传入的组件协议返回实现该协议的类的对象*/
- (id)moduleInstanceFromProtocol:(Protocol *)protocol {
    // 获取协议名字
    NSString *className = NSStringFromProtocol(protocol);

    //......此处省略代码
    if ([className hasSuffix:self.portSuffix]) {
        className = [className substringToIndex:className.length - self.portSuffix.length];
    }
    // 通过协议名字来查找对应class
    Class aClass = NSClassFromString(className);

    //......此处省略代码
    // 没有找到对应的module,是否设置了 defaultModule 来处理相关事件
    if (!aClass && self.defaultModule) {
        aClass = NSClassFromString(self.defaultModule);
    }
    // 生成 module 实例
    module = [[aClass alloc] init];
    if ([module conformsToProtocol:protocol]){
        self.modulesDictionary[NSStringFromProtocol(protocol)] = module;
        return module;
    }

    // 未找到实现该协议的组件
    return nil;
}

3,YPAppModuleConfigManager 是各个业务组件的配置管理类,主要解决业务组件用于不同项目中又存在差异的问题

YPAppModuleConfigManager 通过加载json配置文件数据,然后 Module 通过 YPAppModuleConfigManager 获取自己模块的ModuleConfig

设计框架图如下:

YPAppModuleConfigManager

实际使用:

// 可结合 YPLaunchManager 使用
- (void)runAppDidFinishLauchingBeforeUI {
    // 加载 SystemConfig
    NSString *systemConfigPath = [[NSBundle mainBundle] pathForResource:@"SystemConfig" ofType:@"json"];
    [YPConfigManager loadModuleConfigWithPath:ModuleConfigPath];
    // 加载 Module 的config
    NSString *ModuleConfigPath = [[NSBundle mainBundle] pathForResource:@"ModuleAConfig" ofType:@"json"];
    [YPConfigManager loadModuleConfigWithPath:systemConfigPath];
}

------ moduleA 获取配置
YPModuleA_ModuleConfig *configModel = [[YPAppModuleConfigManager sharedManager] parseModuleConfigClass:[YPModuleA_ModuleConfig class]];
// 设置相应的颜色和字体 等等
self.navigation.color = configModel.navigationBarTintColor
self.navigation.titleFont = configModel.navigationTitleFont

json 文件示例:
{
    "YPBaseControllerModuleConfig" : {
        "navigationBarTintColor":"#ffffff", // navigation 颜色
        "navigationTitleFont":"Medium 18.0f", // navigation 字体
        "backNavigationItemImageName" : "yp_navigation_back", // navigation 返回图片
    },
}

最后附项目Demo地址

参考资料:iOS组件化方案对比

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容

  • 宿命 夜未眠 是谁的颂唱 月未缺 是谁的留恋 问天静 不忍将清风扰乱 向世安 只欲把庭界看穿...
    陈汐年阅读 449评论 4 8
  • 邻居王叔年轻时经营一家汽配店,招了几个学徒,生意红红火火,大约在10年前,就成为我们当地有钱的生意人,开豪车,买了...
    苍穹之下小人儿阅读 1,040评论 1 6
  • 今年新接手的班级,班里有几个孩子不太上趟儿,总是不习惯交家庭作业。哼!换了新老师,还想偷懒不交作业来?这群毛孩子,...
    笑盈子阅读 807评论 4 8
  • 二、成功的人,是自律的普通人 1.悔恨录:只长年龄,不长见识的人 人的工作能力和社会能力,真的不会和年龄一起增长,...
    庄颖琦阅读 1,018评论 0 0