iOS组件化实现

业务背景

APP项目不断迭代,业务不断增加,项目工程越来越复杂。开发团队不断变大,各个人员有自己习惯的代码风格,代码库,第三方等。为了保证业务不断增长扩充,更加便捷管理代码。提出组件化思想。不同模块不同组分别 维护开发。组件化思想核心:解耦合。


模块.png

组件化优势

组件和组件之间没有明确的约束,组件单独开发、单独测试,不能揉入主项目中开发,测试也可以针对性的测试。


路由图.png

实现思路

简单来说就是每个组件可以想象成一个单独的APP,单独的项目。各玩个的。但是不同模块直接又存在着交互,数据交互,页面交互,跳转等等。想要跳转又不能相互藕合,就需要一个“中间商”,中心路由,就是我们要研究的核心。我们主要解决的就是这个问题。

项目管理实现

不同组件传入不同Git地址,制作podspec支持pod导入。很简单不细说,网上很多资料。我们主要来看看组件间的通信。

实现方式

网上有很多种实现方法,总结大概一下三种:

1.url-block

2.protocol-class

3.target-action

一、url-block

概述:A组件使用URL将Block注册给路由,路由以URL为KEY,将Block存储起来。B组件通过URL调用路由的方法,找到对应Block.完成对A的调用。

  //
//  Router.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

typedef void(^RouterBlock)(NSDictionary *dic);
@interface Router : NSObject
#pragma mark - URL

// A:aTestAction  参数 @{@"text":...}

#pragma mark -
+ (instancetype)sharedInstance;
// 注册
- (void)registerUrl:(NSString *)url block:(RouterBlock)block;
// 执行
- (void)excuteBlockWithUrl:(NSString *)key params:(NSDictionary *)params;

@end

NS_ASSUME_NONNULL_END

//
//  Router.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import "Router.h"

@interface Router()

@property (strong, nonatomic) NSMutableDictionary *map;

@end

@implementation Router

+ (instancetype)sharedInstance {
    static Router *_sharedSingleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedSingleton = [[super allocWithZone:NULL] init];
    });
    return _sharedSingleton;
}
// 防止外部调用alloc 或者 new
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [Router sharedInstance];
}
// 防止外部调用copy
- (id)copyWithZone:(nullable NSZone *)zone {
    return [Router sharedInstance];
}
// 防止外部调用mutableCopy
- (id)mutableCopyWithZone:(nullable NSZone *)zone {
    return [Router sharedInstance];
}

// 注册
- (void)registerUrl:(NSString *)url block:(RouterBlock)block{
    if (!url || !block) return;
    self.map[url] = block;
}
// 执行
- (void)excuteBlockWithUrl:(NSString *)key params:(NSDictionary *)params {
    if (!key) return;
    RouterBlock block = self.map[key];
    if (!block) return;
    block(params);
}
@end

//
//  AModule.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface AModule : UIViewController

@end

NS_ASSUME_NONNULL_END

//
//  AModule.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import "AModule.h"
#import "Router.h"

@interface AModule ()

@end

@implementation AModule

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 注册
    [[Router sharedInstance] registerUrl:@"A:aTestAction" block:^(NSDictionary * _Nonnull dic) {
        [self aTestAction:dic[@"text"]];
    }];
    
}
- (void)aTestAction:(NSString *)text{
    NSLog(text);
}

@end

//
//  BModule.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BModule : UIViewController

@end

NS_ASSUME_NONNULL_END

//  BModule.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.

#import "BModule.h"
#import "Router.h"

@interface BModule ()

@end

@implementation BModule

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // B组件调用A组件
    [[Router sharedInstance] excuteBlockWithUrl:@"A:aTestAction" params:@{@"text":@"文字"}];
}
@end

注册所产生的内存占用并不是负担,主要是大量的注册可能会明显拖慢启动速度。缺点显而易见: 调用的时候传参不明确

 // B组件调用A组件
    [[Router sharedInstance] excuteBlockWithUrl:@"A:aTestAction" params:@{@"text":@"文字"}];

2.protocol-class

同样也是需要存储,根据协议存储类名,注册

//
//  Router1.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

//协议
@protocol AService <NSObject>
- (void)aTestAction:(NSString *)text;
@end

@interface Router1 : NSObject

+ (instancetype)sharedInstance;
- (void)registerService:(Protocol *)service class:(Class)cls;
- (id)getObject:(Protocol *)service;

@end



NS_ASSUME_NONNULL_END

//
//  Router1.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import "Router1.h"
@interface Router1()

@property (strong, nonatomic) NSMutableDictionary *map;

@end

@implementation Router1

+ (instancetype)sharedInstance {
    static Router1 *_sharedSingleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedSingleton = [[super allocWithZone:NULL] init];
    });
    return _sharedSingleton;
}
// 防止外部调用alloc 或者 new
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [Router1 sharedInstance];
}
// 防止外部调用copy
- (id)copyWithZone:(nullable NSZone *)zone {
    return [Router1 sharedInstance];
}
// 防止外部调用mutableCopy
- (id)mutableCopyWithZone:(nullable NSZone *)zone {
    return [Router1 sharedInstance];
}

- (void)registerService:(Protocol *)service class:(Class)cls {
    if (!service || !cls) return;
    self.map[NSStringFromProtocol(service)] = cls;
}
- (id)getObject:(Protocol *)service {
    if (!service) return nil;
    Class cls = self.map[NSStringFromProtocol(service)];
    id obj = [cls new];
    if ([obj conformsToProtocol:service]) {
        return obj;
    }
    return nil;
}

@end

//
//  AModule.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface AModule : UIViewController

- (void)aTestAction:(NSString *)text;

@end

NS_ASSUME_NONNULL_END

//
//  AModule.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import "AModule.h"
#import "Router1.h"

@interface AModule ()

@end

@implementation AModule

- (void)viewDidLoad {
    [super viewDidLoad];
    // 注册
    [[Router1 sharedInstance] registerService:@protocol(AService) class:self.class];
}
- (void)aTestAction:(NSString *)text{
    NSLog(@"%@", text);
}

@end

//
//  BModule.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BModule : UIViewController

@end

NS_ASSUME_NONNULL_END

//
//  BModule.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import "BModule.h"
#import "Router1.h"
#import "AModule.h"

@interface BModule ()

@end

@implementation BModule

- (void)viewDidLoad {
    [super viewDidLoad];
    
    AModule *service = [[Router1 sharedInstance] getObject:@protocol(AService)];
    [service aTestAction:@"文字"];
}

@end

3.target-action

通过performSelector 方法调用,无需注册

//
//  Router2.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface Router2 : NSObject

+ (instancetype)sharedInstance;

/// 调用方法
/// @param target 类
/// @param action 方法
/// @param params 参数
- (id)performWithTarget:(NSString *)target action:(NSString *)action params:(NSDictionary *)params;

@end

NS_ASSUME_NONNULL_END

//
//  Router2.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import "Router2.h"

@implementation Router2

+ (instancetype)sharedInstance {
    static Router2 *_sharedSingleton = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedSingleton = [[super allocWithZone:NULL] init];
    });
    return _sharedSingleton;
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    return [Router2 sharedInstance];
}
- (id)copyWithZone:(nullable NSZone *)zone {
    return [Router2 sharedInstance];
}
- (id)mutableCopyWithZone:(nullable NSZone *)zone {
    return [Router2 sharedInstance];
}

- (id)performWithTarget:(NSString *)target action:(NSString *)action params:(NSDictionary *)params {
    Class cls; id obj; SEL sel;
    cls = NSClassFromString(target);
    if (!cls){
        return nil;
    }
    sel = NSSelectorFromString(action);
    if (!sel){
        return nil;
    }
    obj = [cls new];
    if (![obj respondsToSelector:sel]) {
        return nil;
    }
    return [obj performSelector:sel withObject:params];
}

@end

//
//  AModule.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface AModule : UIViewController

- (void)aTestAction:(NSString *)text;

@end

NS_ASSUME_NONNULL_END

//
//  AModule.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import "AModule.h"

@implementation AModule

- (void)viewDidLoad {
    [super viewDidLoad];
}
- (void)aTestAction:(NSString *)text{
    NSLog(@"%@", text);
}

@end

//
//  BModule.h
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BModule : UIViewController

@end

NS_ASSUME_NONNULL_END

//
//  BModule.m
//  XXOCDemo
//
//  Created by admin on 2021/12/22.
//

#import "BModule.h"
#import "Router2.h"

@interface BModule ()

@end

@implementation BModule

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [[Router2 sharedInstance] performWithTarget:@"AModule" action:@"aTestAction:" params:@{@"text":@"文字"}];
}

@end

总结

以上就是所有方法实现,可根据个人喜好选择。用的最多的是URL-Block方式,简单注册就好,不用写协议方法,方法调用统一入口,不会像protocol,需要写协议,调用方法散落各处。实际上也没有太大差别。个人推荐protocol方式,需要写协议,同时参数看起来更加明确。

个人感悟,交流学习,如有不准确请指出!!!

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

推荐阅读更多精彩内容

  • 最近研究了一下项目的组件化,把casa、bang、limboy的有关组件化的博客看了一遍,学到了不少东西,对目前业...
    宇立阅读 1,126评论 0 3
  • 前言 看了一些关于组件化文章,决定写篇文章稍稍做些总结。 一、组件化的误解 首先笔者认为组件化这个词用的不合适,应...
    ZhengYaWei阅读 4,661评论 3 18
  • 背景 随着公司业务的不断发展,项目的功能越来越复杂,各个业务代码耦合也越来越多,代码量也是急剧增加,传统的`MVC...
    China架构师阅读 816评论 0 4
  • 最近研究了一下项目的组件化,把casa、bang、limboy的有关组件化的博客看了一遍,学到了不少东西,对目前业...
    飞鱼湾阅读 8,814评论 2 45
  • 一、蘑菇街 App 的组件化之路 二、iOS应用架构谈 组件化方案 三、蘑菇街 App 的组件化之路·续 四、iO...
    钱嘘嘘阅读 831评论 0 1