10、App启动过程分析与UIApplication自定义举例

10、App启动过程分析与UIApplication自定义举例

OC工程里面

在OC工程中中有一个main.m的文件,App启动时首先初始化所有的类,然后调用main.m中的main函数,可以把App的启动过程简单的分为两个阶段:(1)从类的初始化到main函数的执行(2)从main函数的执行到application:didFinishLaunchingWithOptions的执行。
mian函数的实现如下:

int main(int argc,char* argv[]){
	@autorelasepool {
		return UIApplicationMain(argc,argv,nil,NSStringFromClass([AppDelegate class]))
	}
}

调用UIApplicationMain函数,并返回。
通常来讲,main函数返回就代表着程序的退出,但实际上该函数并不会返回,也就是说App不会退出,主要原因在于RunLoop的存在,其内部是一个do-while的循环,保证了程序一直运行。

  • UIApplicationMain函数的第1个参数–argc,是int类型,代表程序启动时的参数个数,默认是1。
  • 第2个参数–argv,是char*[]类型,代表各个参数的值,默认为程序的名字。
  • 第3个参数–printClassName,是NSString*类型,该参数就必须传一个UIApplication或者其子类的类名对应的字符串,用于实例化一个UIApplication或者其子类对象,大多数情况下传入nil,传入nil就是UIApplication类的名字对应的字符串作为参数,相当于传入了NSStringFromClass([UIApplication class]),但是实际上是一个很有用的参数,如果要针对UIApplication来完成一些事情的话,那么可以自定义一个UIApplication的子类。
    例如有一些页面想要处理一些琐碎的事情,想在某个类中集中处理,就可以考虑自定义一个UIApplication的子类,通过设置这个参数,在该子类中统一做这些事情,而不是将这些事情分散化到各个页面的对应的VC中处理。

App日志的统计类似这样的一个需求:如果要记录App的行为日志,但是又不想要每个页面对应的VC都处理这种琐碎的事情,而是更希望这些VC做一些它们自己负责的交互与业务逻辑的事情,那么这种需求就可以用刚刚提到的思路来解决。
自定义一个继承于UIApplication的类—TestApplication,在main方法中将其指定为第3个参数。

int main(int argc,char* argv[]){
	@autorelasepool {
		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:(nullable id) target from:(nullable id) sender forEvent:(nullable UIEvent*)event {
	//在这里处理一些统一的逻辑,例如:记录行为日志
	return [super sendAction:action to:target from sender forEvent:event];
}

sendAction:to:from:forEvent:这个方法可以很好的解决现有的需求,例如要统计登录页面中点击登录按钮的这个行为,可以通过target参数判断页面,通过sender判断触发事件的按钮,通过action判断触发的事件名称,有了这些信息就可以判断出一个行为是否是登录页面的点击登录按钮行为,从而将该行为记录下来,或者上报给服务器。
这种需求也可以利用runtime和OC的消息转发机制可以将该需求更加灵活的实现。

@implementataion TestApplication

-(BOOL) sendAction:(SEL)action to:(nullable id) target from (nullable id)sender forEvent:(nullable UIEvent*)event {
	if ([target isKindOfClass:[LoginViewController class]]&&[sender isKindOfClass:[UIButton class]]&&[NSStringFromSelector(action) isEqualToString:@"handle login"]){
	//记录登录日志行为或者上报服务器
}

return [super sendAction:action to:target from sender forEvent:event];
}
@end
  • 第四个参数–delegateClassName,也是NSString*类型,指定一个继承于UIResponder并遵循UIApplicationDelegate协议的类的实例,这个类由Xcode摸板准备,即AppDelegate类。UIApplicationMain函数会以第三个参数闯进一个UIApplication或者子类的实例,以第四个参数创建一个它的代理的实例(通常是AppDelegate的实例),然后指定代理类的实例为UIApplication或者其子类实例的delegate属性。

当代码走到UIApplicationMain函数时,就会走到AppDelegate类里面。AppDelegate类声明周期中很重要的一个函数:application:didFinishLaunchingWithOptions:会被调用。

  • 在Xcode5之前,sb还没有成为新建项目的默认配置,通常会在该方法里面做两件事情:
    (1)初始化appDelegate实例的window。
    (2)指定一个VC的实例作为window的rootViewController,并将该window设置成makeKeyAndVisible。
-(BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*) launchOptions {
	self.windwo = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen] bounds];
	RootViewController *rootController = [[RootViewController alloc] init];
	self.window.rootViewController = rootController;
	[self.window makeKeyAndVisible];

	return YES;
}
  • 在iOS8.0以及之前的iOS版本中,可以实例化一个VC,但是并不需要指定为rootViewController,而是把VC的View添加到window上,如下:
-(BOOL)application:(UIApplication*)application didFinishingWithOptions:(NSDictionary*) launchOptions {
		self.windwo = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen] bounds];
	RootViewController *rootController = [[RootViewController alloc] init];
	[self.window addSubview:rootController.view];
	[self.window makeKeyAndVisible];

	return YES;
}
  • iOS8.0之后就不能在通过addSubView了。

  • Xcode的Single View Application模板引入Main.storyboard之后,在info.plist里面就多了一个Main storyboard file base name字段,用于指定sb文件名,在程序启动时该sb文件会加载。在模板创建的工程中该字段指定为Main。
    请添加图片描述

如果不指定该值,那么就需要在application:didFinishLaunchingWithOptions:方法中手动配置如下内容。

-(BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*) launchOptions {
	self.windwo = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen] bounds];
	RootViewController *rootController = [[RootViewController alloc] init];
	self.window.rootViewController = rootController;
	[self.window makeKeyAndVisible];

	return YES;
}

如果指定了该字段,虽然application:didFinishLaunchingWithOptions:里面什么也没写,但是系统会自动创建一个window,然后根据Main storyboard file base name传入的sb文件名实例化一个VC,作为window的rootViewController。系统会找到sb文件并且设置为Is Initial View Controller的VC,并将其实例化。如果Is Initial View Controller被取消,那么运行成功会发现屏幕是黑色的,这个黑色就是window的颜色。

Swift工程里面

Swift工程里面并没有一个main.swift文件,但是AppDelegate里面多了一个@UIApplicationMain。@UIApplicationMain会让编译器自动合成一个App的入口,相当于原来的main函数由编译器生成。
如果我们想要修改main函数,首先要注释掉@UIApplicationMain,然后建立一个main.swift的文件,删除里面所有的代码,然后添加如下代码:

import UIKit 
import Foundation

UIApplicationMain(CommandLine.argc,UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to:UnsafeMutablePointer<Int8>.self,capacity:In t(CommandLine.argc)),nil,NSStringFromClass(AppDelegate.self))
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值