AppDelegate模块化瘦身

5,316 阅读5分钟

前言

关于iOS的模块化,要追溯到16年接触的BeeHive了,BeeHive将功能模块化,以module的形式进行构建,以performSelector:的形式进行module的事件响应,以protocol的形式进行module间的通信。可以说思路非常清晰明了了。关于BeeHive的代码传送门alibaba/BeeHive,star已3.2k,关于BeeHive源码解析可参考霜神文章传送门BeeHive —— 一个优雅但还在完善中的解耦框架。实际上我并不认为BeeHive可以真正用到我们项目中来,它确实构建了module,但是module实例带来的内存问题会让人头疼。个人认为将BeeHive思想中的module部分改造一下用在我们的AppDelegate中是完全可行的。下面进入正文。

目录

一、模块拆分

二、模块事件响应

三、模块管理

  • 1.模块注册
  • 2.触发event
  • 3.移除module

四、总结

一、模块拆分

画了一个结构图,module1到module4为我们需要在Appdelegate中进行处理的业务逻辑,比如说我们的数据库处理分享功能推送功能等等。

首先为所有模块定义了三个接口:

@protocol SHRMAppEventModuleProtocol <UIApplicationDelegate>

- (NSInteger)moduleLevel;
- (void)destroyModule;
- (NSString *)moduleID;

@end

接口定义了三个函数,moduleLevel返回module执行的优先级,destroyModule用来对module进行释放,moduleID返回当前module的id。接口的默认实现统一在BaseAppEventModule中进行。BaseAppEventModule为所有module的父类,只有继承了BaseAppEventModule的module才能被管理。

关于BaseAppEventModule的默认实现也很简单,对module进行销毁的时候用到了SHRMAppEventModuleManager下面会讲到,优先级默认设置100.

@interface SHRMBaseAppEventModule : NSObject <SHRMAppEventModuleProtocol>

@end


#define MODULE_LEVEL_DEFAULT 100
@implementation SHRMBaseAppEventModule

- (NSInteger)moduleLevel {
    return MODULE_LEVEL_DEFAULT;
}

- (void)destroyModule {
    [[SHRMAppEventModuleManager sharedInstance] removeModule:[self moduleID]];
    NSLog(@"%@ destroy",NSStringFromClass([self class]));
}

- (NSString *)moduleID {
    return NSStringFromClass([self class]);
}

@end

模块的创建上面说到了必须要继承自SHRMBaseAppEventModule,只有继承了SHRMBaseAppEventModule的module才会被管理,因为只有SHRMBaseAppEventModule遵循了SHRMAppEventModuleProtocol协议。关于module创建部分:

@interface testMudule : SHRMBaseAppEventModule
@end

@implementation testMudule

- (NSInteger)moduleLevel {
    return 1;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [self initMudule];
    [self destroyModule];
    return YES;
}

- (void)initMudule {
    NSLog(@"testMudule init");
}

@end

application: didFinishLaunchingWithOptions:函数的实现展示了一个module的整个生命周期,从创建到销毁的过程,那么application: didFinishLaunchingWithOptions:是怎么响应的,实际上module的头文件并没有暴漏任何接口,到这里就实现了功能的模块化。那为什么还能执行到这里,这要感谢强大的runtime函数performSelector:,下面讲一下我对module事件响应的处理。

二、模块事件响应

还是以上面的application: didFinishLaunchingWithOptions:函数为例,它是怎么来的,很明显这是AppDelegate里面的APP生命周期回调:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [[SHRMAppEventModuleManager sharedInstance] handleApplicationEvent:@selector(application:didFinishLaunchingWithOptions:)
                                                              Complete:^(id  _Nonnull module, SEL  _Nonnull sel) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [module performSelector:sel
                     withObject:application
                     withObject:launchOptions];
#pragma clang diagnostic pop
    }];
    return YES;
}

没看错,这样一搞AppDelegateapplication: didFinishLaunchingWithOptions:就剩这些了,这样一来,所有实现了application: didFinishLaunchingWithOptions:的modlue都会被调用,调用优先级根据接口定义的moduleLevel返回值确定。到这里我们就完成了AppDelegate瘦身的工作,实际上AppDelegate中的其他回调处理是一样的。当然还有一个很重要的没有说,就是SHRMAppEventModuleManager做了什么,通过上面的结构图能够看到SHRMAppEventModuleManager是个中间件,用来处理module与AppDelegate间的关系。下面说到第三部分,模块管理。

三、模块管理

SHRMAppEventModuleManager为一个单例,提供了三个接口:

/**
 初始化所有的AppDelegate相关的Event Modules
 */
- (void)registedAllModules;

/**
 触发event module处理AppDelegate回调事件
 
 @param eventSel AppDelegate 回调事件消息
 @param complete module处理handle
 */
- (void)handleApplicationEvent:(SEL)eventSel
                      Complete:(void(^)(id module,SEL sel))complete;

/**
 移除module对象
 
 @param moduleID module ID
 */
- (void)removeModule:(NSString *)moduleID;

1.模块注册

模块注册的思路是完全按照BeeHive的思想来的,在编译期就将我们的module通过__attribute函数进行注册。在运行期再将我们注册好的module取出来在registedAllModules中进行实例化,按照level的返回值进行排序存储。__attribute函数具体做了什么可以参考我之前的文章写一个易于维护使用方便性能可靠的Hybrid框架(三)—— 配置插件关于插件注册部分的解释。

2.触发event

handleApplicationEvent:Complete:为module事件响应的核心函数:

- (void)handleApplicationEvent:(SEL)eventSel
                      Complete:(void(^)(id module,SEL sel))complete {
   
    NSMutableArray *tmpAppEventModules = [[NSMutableArray alloc] initWithArray:self.appEventModules];
    for (id<SHRMAppEventModuleProtocol>module in tmpAppEventModules)
    {
        if ([module conformsToProtocol:@protocol(SHRMAppEventModuleProtocol)])
        {
            if ([module respondsToSelector:eventSel]) {
                if (complete) {
                    complete(module,eventSel);
                }
            }
        }
    }
}

if ([module respondsToSelector:eventSel])就会执行completemodulesel返回,也就是到了AppDelegate里面,继而执行modulesel函数,并且将参数传递过去。

3.移除module

module的移除,在AppDelegate里面,我们将程序启动之后调用完就不再使用的功能module会手动执行移除操作。这也是前言所说的BeeHive在module移除这一块会稍显复杂,但是在AppDelegate里面,我们是完全可以知道哪些module在加载之后可以立即移除的,另外我们仅在AppDelegate中进行模块化,产生的module实例也会非常少,so,完全没必要担心module所带来的内存开销问题。

- (void)removeModule:(NSString *)moduleID {
    NSInteger index = NSNotFound;
    NSInteger resIndex = 0;
    for (id<SHRMAppEventModuleProtocol>module in self.appEventModules)
    {
        if ([[module moduleID] isEqualToString:moduleID])
        {
            index = resIndex;
            break;
        }
        resIndex++;
    }
    
    if (index != NSNotFound) {
        [self.appEventModules removeObjectAtIndex:index];
    }
}

总结

最后总结一下,关于模块化,现在总体来看比较流行,也有很多介绍模块化,组件化具体实施之路的文章,都很优秀,也值得学习。关于解耦,我更倾向于protocol的方式,接口protocol化,代码易读且清晰。之前看过mrpeak在组件化方面的文章,传送门iOS 组件化方案,个人觉得protocol+version的方案和BeeHive非常像,protocol解耦,version进行module的版本管理,但是还是没有解决module所带来的内存开销问题,module一旦细化,何时销毁也是让开发者头疼的问题。先说这么多,各位小伙伴有任何问题欢迎评论区讨论。

最后附上传送门:《SHRMAppDelegate》