作者二亮子用写IB省出的时间出了这本书,这本书确实是一本好书,本人也会在通读全书后对书中IB知识点进行总结归纳学习 此篇文章是此书中在扩展部分非IB系列总结出来,所以单独列出来
文章目录
- OC代码分析
- 第一个参数
argc
- 第二个参数
argv
- 第三个参数
nil(principalClassName)
无侵入埋点实现方案重点在此参数- 第四个参数
delegateClassName
- Swift代码分析
乍一看标题 UIApplicationMain函数怎么会和无侵入埋点扯到一块呢,下边先来慢慢分析 UIApplicationMain函数
一 OC代码分析
在OC工程中有一个main.m的文件, App启动时候首先要初始化所有的类(注意这一步是初始化类,而不是实例),然后调用main.m中的main函数
可以把APP的启动过程简单的分成两个阶段:
1 从类的初始化到Main函数的执行
2 从Main函数的执行到application:didFinishLaunchingWithOptions
的执行。main函数的实现很简单如下
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
调用UIApplicationMain
函数并返回,这里需要注意一点,通常来讲,Main函数返回就代表着程序的退出,但实际上该函数并不会返回,也就是说APP不会退出,主要原因就在于RunLoop的存在,其内部是一个do-while循环,保证了程序一直运行。这是一个普遍的解决方案,并不是iOS特有的,在Android中有looper做类似的事情
1 第一个参数argc
argc 是int 类型,代表程序启动时的参数个数,默认是1
2 第二个参数argv
是char *[]
类型,代表各个参数的值,默认程序启动的名字
3 第三个参数 nil 无侵入埋点实现方案重点在此参数
principalClassName
是NSString
类型,参数必须传一个UIApplication
或其子类的类名对应的字符串,用于实例化一个UIApplication及其子类对象,大多数情况传nil, 传nil就是用UIApplication
类的名字对应的字符串作为参数, 相当于穿了一个 NSStringFromClass([UIApplication class])
,但实际上这是一个很有用的参数, 如果要针对UIApplication来 完成一些事情的话, 那么可以自定义一个UIApplication
的子类, 例如有一些各个页面都需要处理的繁琐事情, 想在某各类中集中处理, 就可以考虑自定义一个 UIApplication
的子类,通过设置这个参数,在该子类中统一做这些事情, 而不是将这些事情分散到各个页面对应的VC处理。
App日志的统计类似于这样的一种需求: 如果要记录App的行为日志, 但又不想每个页面对应的VC都处理这种琐碎的事情, 而是更希望这些VC做一些他们负责的交互与业务逻辑的事情,而是更希望这些VC做这些他们自己负责的交互与业务逻辑的事情, 那么这种需求就可以用到刚刚提到的 思路来解决。自定义一个继承于UIApplication
的类-----TestApplication ,在Main方法中将其定义为第三个参数 如下:
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv,NSStringFromClass([TestApplication class]), NSStringFromClass([AppDelegate class]));
}
}
假如UIApplication类中有一个和Event相关的API,所以可以利用这些API,完成记录行为日志的任务, 如下:
- (void)sendEvent:(UIEvent *)event{
// 在这里处理一些统一的逻辑
[super sendEvent:event];
}
- (BOOL)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event{
// 在这里处理一些统一的逻辑, 例如 记录行为日志
return [super sendAction:action to:target from:sender forEvent:event];
}
sendAction:to:from:forEvent
这个方法可以很好的解决现有的需求, 例如要统计登录页面中点击登录按钮的这个行为, 可以通过Target参数判断页面, 通过sender判断出发事件的按钮,通过action判断触发的事件名称,有了这些信息,就可以判断出一个行为是否是登录页面的点击登录按钮行为从而将行为记录下来, 或上报给服务器, 当然这种需求也有很多其他的解决办法,例如利用runtime和OC的消息转发机制,可以将该需求实现的更灵活,但自定义UIApplication的方法却是最为简单方便的, 如下
- (BOOL)sendAction:(SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event{
if ([target isKindOfClass:[LLLoginViewController class]] && [sender isKindOfClass:[UIButton class]] && [NSStringFromSelector(action) isEqualToString:@"loginActionClick"]) {
// 记录登录行为日志 或上报服务器
}
return [super sendAction:action to:target from:sender forEvent:event];
}
4 第四个参数delegateClassName
也是NSString类型, 指定一个继承于UIResponder并遵循UIApplicationDelagate
协议的类的实例,这个类有xcode 的模板准备,即AppDelegate
类, UIApplicationMain
函数会以第三个参数创建一个 UIApplication
或者其子类的实例,会以第四个参数创建一个他的代理类的实例(通常是AppDelegate
类的实例)我们编写代码往往从appDelegate类开始,也可以通过[UIApplication sharedApplication]
获得application单例对象。当代码走到UIApplicationMain
函数时,就会走到AppDelegate
类里,剩下的大家基本就知道了。
二 Swift代码分析
接下来来看一下基于Swift的工程, 我们会发现,工程中并没有main.swift.但是AppDelegate里多了一个@UIApplicationMain
,@UIApplicationMain
会让编译器自动合成一个APP的入口, 相当于原来的main函数由编译器自动生成。可能是由于在实际的iOS开发中很少修改Main.m中的代码,所以苹果公司觉得这个文件是多余的, 干脆就由编译器自动完成这些事情吧,但是如果确实有需求要修改main函数怎么办, 例如记录行为日志 很简单,首先注释掉@UIApplicationMain
,此时会发现编译会报错,这是建立一个main.swift的文件,删除里边的代码,然后添加如下代码就可以正常的编译运行了:
import UIKit
import Foundation
UIApplicationMain(
CommandLine.argc,
UnsafeMutableRawPointer(CommandLine.unsafeArgv)
.bindMemory(
to: UnsafeMutablePointer<Int8>.self,
capacity: Int(CommandLine.argc)),
nil,
NSStringFromClass(AppDelegate.self)
)
要自定义UIApplication
子类,可以将上面代码中传nil得参数替换成自定义的UIApplication
子类的类名,例如NSStringFromClass(TestApplication.self)
,然后再这个文件中重写那个方法 接下来就和OC的一样了 ,可以参考OC语言进行配置了