起因
在iOS的项目中,有很多对接SDK或者其他方面的需求,需要在ios项目中的AppDelegate.m的生命周期方法中进行很多类似于初始化一样的操作,但是如果你直接把所有的相关代码直接写在这些生命周期函数中是一件耦合性非常强的工作,后续维护将变得十分难以处理,这一点有过相关开发经验的应该都了解,在我做的第一个也是唯一一个完整项目的时候,就曾经遇到过这样的问题,但是当时没有经验参考,而且项目比较小,没有太过于庞大的代码需要在这一部分处理,所以当时把这个问题遗留下来了,但是对于大的项目来说就是一个不得不面对的问题,所以现在有了前辈写的比较好的方式,所以一定要学习一下。项目中对于appdelegate是使用了阿里的一个框架Beehive的方式进行解耦,虽然仅仅只是对于AppDelegate.m生命周期函数处理这一个模块进行了解耦,但是其解耦原理可以进行总结和学习。
在具体方式总结之前先进行一下概念补充- ARM映像
areas
用于构建objects和映像(images)的代码和数据项。一个area包含代码或初始化的数据,或描述未初始化的或必须先设置为零才能执行映像的内存片段。区域的属性描述了该区域是只读,可读写,可执行,已初始化,零初始化还是未初始化。区域进一步分为更大的构建块,称为sections和regions,用于构建镜像。
Sections
地址空间中连续并具有相同属性的areas序列。一个section与其组成区域的其他sections具有相同的属性。一个section包含只读,可执行区域的部分是只读,可执行section。
regions
这些是地址空间中连续的sections序列。组成部分不必具有相同的属性。一个区域可以包括一个由一个零初始化section跟随的可读写section。
objects
包含一个或多个areas
Images
包含一个或多个regions
- 镜像包含几个部分,原文是说regions、sections、areas。
- 建设一个镜像的内存map,连接需要以下信息
- grouping 描述输入areas是怎样被组织进入sections和regions
- placement 描述镜像区域应该在内存maps的位置
- 有两个信息把这些加入到连接中
- 对于简单的工作使用命令行方式
- Scatter loading是用于复杂一些的案例
一个简单的image
- 包括
- 一个只读ROsection
- 一个读写RWsection
- 一个零初始化ZIsection
attribute((section("name")))
section功能属性可以放置代码在image的不同sections中
在这个例子中Function_Attributes_section_0被放置在RO section中而不是.text文件中
void Function_Attributes_section_0 (void)
__attribute__ ((section ("new_section")));
void Function_Attributes_section_0 (void)
{
static int aStatic =0;
aStatic++;
}
attribute((used))
此函数属性通知编译器,即使未引用静态函数也要保留在目标文件中。
这一部分细节以及下文中使用到的关于section存取可以参考该
文章
实战:对于Appdelegate中生命周期函数调用的解耦
首先是准备性工作
类的存放
-
首先是准备一些宏,定义在名为AppdelegatePluginManager的类中,其中的宏如一下,其作用是将传入的类名存放在一个固定的section中
//AppdelegatePluginManager.h //为一个变量名分配一个特殊的段 #define AppPluginSectName "AppPlugin" #define AppPluginDATA(sectname) __attribute((used, section("__DATA," #sectname " "))) //在QINAppdelegatePluginManager这个类中添加一个成员变量为char类型,变量的名字为k+name+_mod,实际存储的内容推测为调用上面的宏返回的地址 #define AppDelegatePlugin(name) \ class AppdelegatePluginManager; \ char *k##name##_mod AppPluginDATA(AppPlugin) = "" #name "";
-
然后在AppDelegate中进行映射文件的注册
//AppDelegate.m ...... @AppDelegatePlugin(QINChromecastAppPlugin) @AppDelegatePlugin(QINBeaconAppPlugin) @AppDelegatePlugin(QINTPNSAppPlugin) @interface AppDelegate () ...... 实际上在调用宏、编译之后会变成下面这句语法 //“__DATA”为segname,"AppPlugin"是sectname class AppdelegatePluginManager; char * kQINChromecastAppPlugin_mod __attribute((used, section("__DATA,"AppPlugin" "))) = ""QINChromecastAppPlugin"";
结合上面的预备知识,总结来说就是,对于所有的我使用@AppDelegatePlugin宏传入的类,即使没有被引用也要保存在指定的名称为"AppPlugin"的section中,但是这个流程实际是在AppdelegatePluginManager类中进行的。
类的提取
以下代码的作用就是把之前写入section的类取出来放到数组中,方便后续处理,实际上最后是加入到了EventBus的eventDelegates数组中
//AppdelegatePluginManager.m
NSArray *AppPluginReadConfiguration(char *sectionName, const struct mach_header *mhp);
static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide) {
NSArray *mods = AppPluginReadConfiguration(AppPluginSectName, mhp);
for (NSString *modName in mods) {
Class cls;
if (modName) {
cls = NSClassFromString(modName);
if (cls) {
[[QINAppdelegatePluginManager shareInstance] addPlugin:cls];
}
}
}
}
//下面这个指令的作用是在main方法进入之前默认执行
__attribute__((constructor)) void registerDyldCallback() {
_dyld_register_func_for_add_image(dyld_callback);
}
//从section中取出类名
NSArray *AppPluginReadConfiguration(char *sectionName, const struct mach_header *mhp) {
NSMutableArray *configs = [NSMutableArray array];
unsigned long size = 0;
#ifndef __LP64__
uintptr_t *memory = (uintptr_t *)getsectiondata(mhp, SEG_DATA, sectionName, &size);
#else
const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
uintptr_t *memory = (uintptr_t *)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
#endif
unsigned long counter = size / sizeof(void *);
for (int idx = 0; idx < counter; ++idx) {
char *string = (char *)memory[idx];
NSString *str = [NSString stringWithUTF8String:string];
if (!str)
continue;
if (str)
[configs addObject:str];
}
return configs;
}
//注意是写在@interface之前的
@interface AppdelegatePluginManager ()
。。。。。。
对于上文中的[[QINAppdelegatePluginManager shareInstance] addPlugin:cls];间接触发了下面的方法
//EventBus.m
- (void)addDelegate:(NSObject *)delegate{
[self.eventDelegates addObject:delegate];
}
生命周期函数触发流程
-
在AppDelegate生命周期函数中触发的各种事件:
``` - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self.eventProxy publishEvent:APD_EVENT_MSG(eventIDDidFinishLaunchingWithOptions, launchOptions)]; return YES; } //宏的实现 #define APD_EVENT_MSG(EID, EMSG) [QNBEvent eventWithId:[QINAppDelegateEventID EID] withMessage:(EMSG) filePath:__FILE__ fileLine:__LINE__]
- FILE 包含当bai前程序文件名的字符du串
- LINE 表示当前行号的整数
对于Demo中的两个数组的处理和使用
//EventBus.m
//数组声明
//用于存放所有注册了事件的类,不管是需要什么事件
@property (nonatomic,strong) NSMutableArray *> *_Nullable eventDelegates;
//字典,起到缓存作用,key为生命周期事件名,value为实现了事件的类的集合数组
@property (nonatomic,strong) NSMutableDictionary *> *eventNodeDic;
//初始化,也就是首次使用为空
- (instancetype)init
{
self = [super init];
if (self) {
......
_eventDelegates = [[NSMutableArray alloc] init];
_eventNodeDic = [[NSMutableDictionary alloc] init];
.....
}
return self;
}
//eventDelegates数组数据获取入口,在上文的main函数enter之前被默认调用的函数的中被间接调用,该数组使用见下文
- (void)addDelegate:(NSObject *)delegate{
[self.eventDelegates addObject:delegate];
}
- 上文中的didFinshLaunching中调用的publishEvent:APD_EVENT_MSG方法实际上间接触发下面的方法,先看一下不走cache逻辑的部分。这个cache其实是流程图中提及的_eventNodeDic字典,key为事件名,value是一个存放了需要被事件触发的所有类的数组,利用这个字典做了一些缓存工作,在一个事件到来之前可以直接根据事件获取到所有的class
//EventBus.m
-(BOOL)resumeEvent:(nonnull QNBEvent *)event withDelegate:(nullable NSObject *)delegate{
if(!event){
return NO;
}
@try {
NSString *eventName = event.eventName;
NSArray *eventNodes = [_eventNodeDic objectForKey:eventName];
BOOL isPassedDelegate = delegate ? NO : YES;
BOOL isBlocked = NO;
//走cache逻辑
if(eventNodes){
for(QNBPluginEventNode *node in eventNodes){
//跳过在delegate之前的监听者
if(!isPassedDelegate && node.pluginObj == delegate){
isPassedDelegate = YES;
continue;
}
if([_blockedDelegates containsObject:node.pluginObj]){
continue;
}
if(!isPassedDelegate){
continue;
}
void (*commonReceiveIMP) (id,SEL,id) = (void (*)(id,SEL,id))node.commonReceiveIMP;
if(commonReceiveIMP){
commonReceiveIMP(node.pluginObj,_commonReceiveSEL,event);
//QNBLogInfo(@"[QNBEventTest] Cache %@:%@",[NSString stringWithFormat:@"didRecievePlayerEvent:"],[node.pluginObj class]);
}
BOOL (*specialEventIMP) (id,SEL,id) = (BOOL (*)(id,SEL,id))node.specialReceiveIMP;
if(specialEventIMP){
isBlocked = specialEventIMP(node.pluginObj,node.specialReceiveSEL,event);
//QNBLogInfo(@"[QNBEventTest] Cache %@:%@",[NSString stringWithFormat:@"didReceive%@:",eventName],[node.pluginObj class]);
}
if(node.brigeObj){
BOOL (*bridgeReceiveIMP) (id,SEL,id) = (BOOL (*)(id,SEL,id))node.bridgeReceiveIMP;
if(bridgeReceiveIMP){
isBlocked = bridgeReceiveIMP(node.brigeObj,node.specialReceiveSEL,event);
//QNBLogInfo(@"[QNBEventTest] brige Cache %@:%@",[NSString stringWithFormat:@"didReceive%@:",eventName],[node.pluginObj class]);
}
}
if(isBlocked){
return YES;
}
}
}
不走cache逻辑的部分
else{
NSMutableArray *eventNodes = [[NSMutableArray alloc] init];
SEL specialEventSEL = NSSelectorFromString([NSString stringWithFormat:@"didReceive%@:",eventName]);
for(NSObject *eventDelegate in _eventDelegates){
QNBPluginEventNode *eventNode = nil;
if([eventDelegate respondsToSelector:_commonReceiveSEL]){
void (*commonEventIMP) (id,SEL,id) = (void (*)(id,SEL,id))[eventDelegate methodForSelector:_commonReceiveSEL];
if(commonEventIMP){
if(![_blockedDelegates containsObject:eventDelegate] && isPassedDelegate && !isBlocked){
commonEventIMP(eventDelegate,_commonReceiveSEL,event);
//QNBLogInfo(@"[QNBEventTest] %@:%@",[NSString stringWithFormat:@"didRecievePlayerEvent:"],[eventDelegate class]);
}
eventNode = [[QNBPluginEventNode alloc] init];
eventNode.pluginObj = eventDelegate;
eventNode.commonReceiveIMP = (IMP)commonEventIMP;
[eventNodes addObject:eventNode];
}
}
if([eventDelegate respondsToSelector:specialEventSEL]){
BOOL (*specialEventIMP) (id,SEL,id) = (BOOL (*)(id,SEL,id))[eventDelegate methodForSelector:specialEventSEL];
if(specialEventIMP){
if(![_blockedDelegates containsObject:eventDelegate] && isPassedDelegate && !isBlocked){
isBlocked = specialEventIMP(eventDelegate,specialEventSEL,event);
}
//QNBLogInfo(@"[QNBEventTest] %@:%@",[NSString stringWithFormat:@"didInterceptPlayerEvent:"],[eventDelegate class]);
if(!eventNode){
eventNode = [[QNBPluginEventNode alloc] init];
eventNode.pluginObj = eventDelegate;
[eventNodes addObject:eventNode];
}
eventNode.specialReceiveSEL = specialEventSEL;
eventNode.specialReceiveIMP = (IMP)specialEventIMP;
}
}
if([eventDelegate isKindOfClass:[QNBPluginViewControllerBridge class]]){
QNBPluginViewControllerBridge *eventBridge = (QNBPluginViewControllerBridge *)eventDelegate;
if(eventBridge.eventTargetViewController){
if([eventBridge.eventTargetViewController respondsToSelector:specialEventSEL]){
BOOL (*brigdeEventIMP) (id,SEL,id) = (BOOL (*)(id,SEL,id))[eventBridge.eventTargetViewController methodForSelector:specialEventSEL];
if(brigdeEventIMP){
if(![_blockedDelegates containsObject:eventDelegate] && isPassedDelegate && !isBlocked){
isBlocked = brigdeEventIMP(eventBridge.eventTargetViewController,specialEventSEL,event);
}
if(!eventNode){
eventNode = [[QNBPluginEventNode alloc] init];
eventNode.pluginObj = eventDelegate;
[eventNodes addObject:eventNode];
}
eventNode.brigeObj = eventBridge.eventTargetViewController;
eventNode.specialReceiveSEL = specialEventSEL;
eventNode.bridgeReceiveIMP = (IMP)brigdeEventIMP;
//QNBLogInfo(@"[QNBEventTest] brige %@:%@",[NSString stringWithFormat:@"didReceive%@:",eventName],eventBridge.eventTargetViewController);
}
}
}
}
//跳过在delegate之前的监听者
if(!isPassedDelegate && eventDelegate == delegate){
isPassedDelegate = YES;
}
}
[_eventNodeDic setObject:eventNodes forKey:eventName];
if(isBlocked){
return YES;
}
}
-
上面的代码非常容易理解,主要两个点就是判断类是否有实现方法,以及调用类的对应实现方法,这里的写法可能不是很常见,所以问题来了,什么是SEL,什么是IMP?
- SEL:Objective-C是动态语言,动态体现在可以在运行的时候修改所执行的方法,可以把一个对象的所有方法看成一张表,SEL就可以看成表中每一条的索引,根据方法名来生成对应的SEL,所以OC中不同的方法名就对应不同的方法
- IMP:IMP是真正的函数指针,这是其定义typedef id (*IMP)(id, SEL,... );它指向一个真正的函数地址
- (IMP)methodForSelector:(SEL)aSelector这个方法可以根据一个SEL,得到该方法的IMP(函数指针)
- 一个简单的实验
- (void)viewDidLoad { [super viewDidLoad]; IMP imp = [self methodForSelector:@selector(sayHi)]; imp(); } - (void)sayHi { NSLog(@"hello world"); } �//控制台打印 2015-09-28 09:51:13.132 MethodForSelector[755:215807] hello world
需要注意的是- (IMP)methodForSelector:(SEL)aSelector参数aSelector即使是私有方法,即在.h文件中没有暴露接口也会调用成功
respondsToSelector判断是个方法是否实现,返回BOOL,用于防止调用没有实现的方法
这样整个流程基本解释了,至于注册的这些实现了响应方法的类,是继承了一个统一父类,重写父类方法就好,这一部分比较简单,就不赘述。