iOS组件化方案
一、蘑菇街url-block
方案
这是蘑菇街中应用的一种页面间调用的方式,通过在启动时注册组件提供的服务,把调用组件使用的url
和组件提供的服务block
对应起来,保存到内存中。在使用组件的服务时,通过url
找到对应的block
,然后获取服务。
具体实现代码如下:
//Mediator.m 中间件 @implementation Mediator typedef void (^componentBlock) (id param); @property (nonatomic, storng) NSMutableDictionary *cache - (void)registerURLPattern:(NSString *)urlPattern toHandler:(componentBlock)blk { [cache setObject:blk forKey:urlPattern]; } - (void)openURL:(NSString *)url withParam:(id)param { componentBlock blk = [cache objectForKey:url];
if (blk) blk(param); } @end
1.先在Mediator中注册url-block
#import "Mediator.h" #import "WRBookDetailViewController.h" + (void)initComponent { [[Mediator sharedInstance] registerURLPattern:@"weread://bookDetail" toHandler:^(NSDictionary *param) { WRBookDetailViewController *detailVC = [[WRBookDetailViewController alloc] initWithBookId:param[@"bookId"]];
[[UIApplication sharedApplication].keyWindow.rootViewController.navigationController pushViewController:detailVC animated:YES];
}]; }
2.通过url在中间件中就能找到对应的block,并执行跳转
#import "Mediator.h" + (void)gotoDetail:(NSString *)bookId {
[[Mediator sharedInstance] openURL:@"weread://bookDetail" withParam:@{@"bookId": bookId}]; }
二、 蘑菇街protocol-class方案
针对url-block
方案无法传递非正规参数,新增一种protocol-class方案
//ProtocolMediator.m 中间件 @implementation ProtocolMediator @property (nonatomic, storng) NSMutableDictionary *protocolCache - (void)registerProtocol:(Protocol *)proto forClass:(Class)cls { NSMutableDictionary *protocolCache; [protocolCache setObject:cls forKey:NSStringFromProtocol(proto)]; } - (Class)classForProtocol:(Protocol *)proto { return protocolCache[NSStringFromProtocol(proto)]; } @end
每一个组件都有一个公共Protocol文件,定义了每一个组件对外提供的接口
//ComponentProtocol.h @protocol BookDetailComponentProtocol <NSObject> - (UIViewController *)bookDetailController:(NSString *)bookId; - (UIImage *)coverImageWithBookId:(NSString *)bookId; @end @protocol ReviewComponentProtocol <NSObject> - (UIViewController *)ReviewController:(NSString *)bookId; @end //BookDetailComponent 组件 #import "ProtocolMediator.h" #import "ComponentProtocol.h" #import "WRBookDetailViewController.h" + (void)initComponent{ [[ProtocolMediator sharedInstance] registerProtocol:@protocol(BookDetailComponentProtocol) forClass:[self class]; } - (UIViewController *)bookDetailController:(NSString *)bookId { WRBookDetailViewController *detailVC = [[WRBookDetailViewController alloc] initWithBookId:param[@"bookId"]]; return detailVC; } - (UIImage *)coverImageWithBookId:(NSString *)bookId { …. }
最后调用者通过 protocol 从 ProtocolMediator 拿到提供这些方法的 Class,再进行调用:
//WRReadingViewController.m 调用者 //ReadingViewController.m #import "ProtocolMediator.h" #import "ComponentProtocol.h" + (void)gotoDetail:(NSString *)bookId { Class cls = [[ProtocolMediator sharedInstance] classForProtocol:BookDetailComponentProtocol]; id bookDetailComponent = [[cls alloc] init]; UIViewController *vc = [bookDetailComponent bookDetailController:bookId]; [self.navigationController pushViewController:vc animated:YES]; }
三、target-action方案
1.先设置一个中间件公共类
@interface CTMediator : NSObject + (instancetype)sharedInstance; // 远程App调用入口 - (id)performActionWithUrl:(NSURL *)url completion:(void(^)(NSDictionary *info))completion; // 本地组件调用入口 - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget; - (void)releaseCachedTargetWithTargetName:(NSString *)targetName; @end // // CTMediator.m // CTMediator // // Created by casa on 16/3/13. // Copyright © 2016年 casa. All rights reserved. // #import "CTMediator.h" @interface CTMediator () @property (nonatomic, strong) NSMutableDictionary *cachedTarget; @end @implementation CTMediator #pragma mark - public methods + (instancetype)sharedInstance { static CTMediator *mediator; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mediator = [[CTMediator alloc] init]; }); return mediator; } /* scheme://[target]/[action]?[params] url sample: aaa://targetA/actionB?id=1234 */ - (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion { NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; NSString *urlString = [url query]; for (NSString *param in [urlString componentsSeparatedByString:@"&"]) { NSArray *elts = [param componentsSeparatedByString:@"="]; if([elts count] < 2) continue; [params setObject:[elts lastObject] forKey:[elts firstObject]]; } // 这里这么写主要是出于安全考虑,防止黑客通过远程方式调用本地模块。这里的做法足以应对绝大多数场景,如果要求更加严苛,也可以做更加复杂的安全逻辑。 NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""]; if ([actionName hasPrefix:@"native"]) { return @(NO); } // 这个demo针对URL的路由处理非常简单,就只是取对应的target名字和method名字,但这已经足以应对绝大部份需求。如果需要拓展,可以在这个方法调用之前加入完整的路由逻辑 id result = [self performTarget:url.host action:actionName params:params shouldCacheTarget:NO]; if (completion) { if (result) { completion(@{@"result":result}); } else { completion(nil); } } return result; } - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget { NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName]; NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName]; id target = self.cachedTarget[targetClassString]; if (target == nil) { Class targetClass = NSClassFromString(targetClassString); target = [[targetClass alloc] init]; } SEL action = NSSelectorFromString(actionString); if (target == nil) { // 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的 return nil; } if (shouldCacheTarget) { self.cachedTarget[targetClassString] = target; } 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顶上的。 [self.cachedTarget removeObjectForKey:targetClassString]; return nil; } } } - (void)releaseCachedTargetWithTargetName:(NSString *)targetName { NSString *targetClassString = [NSString stringWithFormat:@"Target_%@", targetName]; [self.cachedTarget removeObjectForKey:targetClassString]; } #pragma mark - getters and setters - (NSMutableDictionary *)cachedTarget { if (_cachedTarget == nil) { _cachedTarget = [[NSMutableDictionary alloc] init]; } return _cachedTarget; } @end
2.每个组件创建一个Category,在其中实现当前组件的跳转实现
#import <CTMediator/CTMediator.h> #import <UIKit/UIKit.h> @interface CTMediator (TAConfirmOrder) - (UIViewController *)confirmOrderViewControllerWithGoodsId:(NSString *)goodsId goodsName:(NSString *)goodsName ConfirmComplete:(dispatch_block_t)confirmComplete; @end #import "CTMediator+TAConfirmOrder.h" @implementation CTMediator (TAConfirmOrder) - (UIViewController *)confirmOrderViewControllerWithGoodsId:(NSString *)goodsId goodsName:(NSString *)goodsName ConfirmComplete:(dispatch_block_t)confirmComplete { NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; params[@"goodsId"] = goodsId; params[@"goodsName"] = goodsName; params[@"completeBlock"] = confirmComplete; return [self performTarget:@"TAConfirmOrder" action:@"ConfirmOrderViewController" params:params shouldCacheTarget:NO]; } @end
3.通过中间件调用
UIViewController *confirmOrderVC = [[CTMediator sharedInstance] confirmOrderViewControllerWithGoodsId:self.goodsId goodsName:self.goodsName ConfirmComplete:^{
//...
}];