UIApplicationMain 大家并不陌生,因为在通过 XCode 建立 iOS 的 Ojective-C 工程时肯定会看到。新建的 main.m 文件长这样:
int main(int argc, char * argv[]) {
NSString* appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
大部分人都不会在意这个函数,其实它非常有用。
UIApplicationMain 的参数分 4 个部分,argc,argv,principalClassName 和 delegateClassName 。
argc 和 argv 不需要介绍,是 C 语言 main 函数的基础参数。后两个参数都是 NSString 类型的参数。也是着重要介绍的参数。
delegateClassName 参数比较好理解,即设置 App 的公用回调函数,例如如下函数就是常用的应用完成启动时的回调:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
新建 XCode 工程时已经替开发者新建了 AppDelegate 类,但是因为后来苹果将应用回调的大部分功能转移到了 SceneDelegate 类中,所以 AppDelegate 的作用越来越小了。
注:之所以 AppDelegate 被替代,原因是 iOS 13 之后,苹果引入了多场景的概念,不同的场景对应不同的回调,而传统的 AppDelegate 不适应这种回调模式。
principalClassName 是最重要的参数,它需要开发者提供 UIApplication 或者它的子类,如果传入 nil,则默认使用 UIApplication 。
如果不用 UIApplication 作为默认的 principalClassName,而是传入它的子类,一般是为了解决产品在应用层面的管理问题。例如设计了一个 UIApplication 的子类叫 MyApplication
,那么,原先的 main.m 代码就需要按如下方式重写:
int main(int argc, char * argv[]) {
NSString* appDelegateClassName;
NSString* applicationClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
applicationClassName = NSStringFromClass([MyApplication class]);
}
return UIApplicationMain(argc, argv, applicationClassName, appDelegateClassName);
}
除此之外还有另一个办法来告诉项目工程自己需要用别的类替代,其方法是在 Info.plist 文件中引入 Principal class 作为 Key 的字符串键值,其值填入类名,例如本例的 MyApplication,那么,当 UIApplicationMain 所传入的 principalClassName 参数为 nil 时,会自动在 Info.plist 寻找 Principal class 的值来作为 applicationClassName。
实现 UIApplication 子类可以从应用层面全局监控所有的 UIEvent 事件。UIEvent 事件包括用户的输入,传感器等信息,最常处理的是用户输入。
例如通过以下方式继承实现 sendEvent 后,可以在产品内观察记录所有用户的手指在屏幕上移动的事件。并记录下触摸的位置。
- (void)sendEvent: (UIEvent*)event {
if (event.type == UIEventTypeTouches) {
NSSet* touches = [event allTouches];
UITouch* touch = touches.objectEnumerator.nextObject;
if (touch) {
if (touch.phase == UITouchPhaseMoved) {
UIView* view = touch.view;
CGPoint point = [touch locationInView: view];
NSLog(@"detect move event (%lf, %lf)", point.x, point.y);
}
}
}
[super sendEvent: event];
}
因为 sendEvent 是管理着 UIEvent 的最顶层下发,这就意味着在继承实现 sendEvent 时不调用 [super sendEvent: event]
的话,UIEvent 就不会往下传递,这样就能起到拦截用户输入使它不生效的效果。
例如,以下代码可以起到“屏蔽所有用户触摸事件”的效果。
- (void)sendEvent: (UIEvent*)event {
if (event.type == UIEventTypeTouches) {
NSSet* touches = [event allTouches];
UITouch* touch = touches.objectEnumerator.nextObject;
if (touch) {
// 屏蔽所有用户触摸事件
return;
}
}
[super sendEvent: event];
}
另一个常用的方法是 sendAction:
- (BOOL)sendAction: (SEL)action to:(id)target from:(id)sender forEvent:(UIEvent *)event {
NSLog(@"send action");
return [super sendAction: action to: target from: sender forEvent: event];
}
Action 是 Event 管理下的封装,也就是说是先有事件,才可能出现Action 。
给 UIButton 通过 addTarget 添加的动作就是 Action,通过继承实现 sendAction 可以检测到整个应用内的所有 Action 事件。
这里要注意,不能通过给 sendAction 返回 NO 来屏蔽应用内的点击响应,除非在继承实现时不调用 super 的 sendAction 。
[UIApplication.sharedApplication openURL: url
options: @{}
completionHandler: nil];
以上这段代码大家并不陌生了,应用跳转,打开内置浏览器等都会调用这个函数。
通过继承实现 openURL 可以拦截所有 openURL 调用,这样做最大的好处是可以进行 URL 选择过滤,并且可以实现根据需要弹窗提醒用户即将进行跳转。
拦截的策略和之前 sendEvent,sendAction 一样,不需要过多赘述。
移动开发者联盟加入指引