Fork me on GitHub

Swift中消失的main函数

OC项目中的main函数

大家是否发现,以前用OC语言创建的项目,一定会有一个main.m文件,里面有一个main函数,在这个函数中唤醒app。如果在app启动时放置断点,可以看到应用程序调用栈,如下图:

OC的main函数

main函数作为程序启动后的第二个函数被调用,然后在main函数中再启动UIApplication,并绑定AppDelegate。

Swift项目中的main函数为何隐藏

创建过Swift项目的童鞋应该会发现,项目中没有一个名为main.swift的文件,为什么app的入口没有了?官方文档的说法是这样的:

In Xcode, Mac templates default to including a “main.swift” file, but for iOS apps the default for new iOS project templates is to add @UIApplicationMain to a regular Swift file. This causes the compiler to synthesize a mainentry point for your iOS app, and eliminates the need for a “main.swift” file.

这段话的意思是,Swift项目中添加了@UIApplicationMain 到swift文件中,使得编译器合成了一个app入口,所以不需要main.swift文件。

细心的童鞋会发现AppDelegate文件中多了个@UIApplicationMain的标志,启动app并放置断点,会发现其实main函数还是存在的。

Swift中@UIApplicationMain标志

可能苹果认为我们并不需要自行配置app入口,所以干脆简化了项目配置,使用更加简单的方式启动应用,但有时候我们可能需要自己配置入口,例如我们要创建一个UIApplication的子类时。

main.swift

如果你希望通过自行配置入口的方式来创建一个UIApplication子类,那么就要创建一个main.swift文件。

首先创建一个swift文件,命名为main。
main.swift中代码如下:

1
2
3
4
5
import Foundation
import UIKit

private let pointer = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
UIApplicationMain(CommandLine.argc, pointer, NSStringFromClass(UIApplication.self), NSStringFromClass(AppDelegate.self))

方法讲解

1
2
3
4
public func UIApplicationMain(_ argc: Int32,
_ argv: UnsafeMutablePointer<UnsafeMutablePointer<Int8>>!,
_ principalClassName: String?,
_ delegateClassName: String?) -> Int32
  • argc:系统或者用户传入的参数
  • argv:系统或用户传入的实际参数
  • principalClassName:确定了主要应用程序类的名称,这个参数可为nil,这样UIKit就会使用默认的程序类UIApplication
  • delegateClassName:程序自定义的代理类名,这个类负责系统和代码之间的交互,一般为AppDelegate,也可自定义子类。

写好main.swift之后,还需要把AppDelegate中的@UIApplicationMain注释掉或者删掉。

Swift注释@UIApplicationMain标志

重新运行项目,app就能正常启动了。

main函数的参数

最后一个参数我们一般会传入应用程序入口类AppDelegate,不过有些时候,特别是需要执行单元测试的场景,我们可能并不希望把整个App都运行起来,那么我们就可以自定义一个单元测试的程序入口类。类似于下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
import Shared

private var appDelegate: AppDelegate.Type

if AppConstants.IsRunningTest {
appDelegate = TestAppDelegate.self
} else {
appDelegate = AppDelegate.self
}

private let pointer = UnsafeMutableRawPointer(CommandLine.unsafeArgv).bindMemory(to: UnsafeMutablePointer<Int8>.self, capacity: Int(CommandLine.argc))
UIApplicationMain(CommandLine.argc, pointer, NSStringFromClass(UIApplication.self), NSStringFromClass(appDelegate))

具体的实现方式,大家可以参考Firefox的iOS版实现。传送门

UIApplicationMain

该部分介绍转自木板钉钉的简书
不管是通过 main.swift 文件还是 @UIApplicationMain 属性,最后都会调用
UIApplicationMain 函数。让我们来看看 UIApplicationMain 函数都做了什么:

  1. UIApplicationMain 创建app中的第一个实例,应用程序实例,
    UIApplication.shared。 UIApplicationMain 函数的第三个参数指定了应用程序实例所属的类,默认该参数是 nil ,则其默认类就是 UIApplication。如果你想subclass一个UIApplication, 那就应该将 UIApplicationMain 函数的第三个参数指定为你的子类名字,例如 NSStringFromClass(MyAppSubclass.self) .

  2. UIApplicationMain 创建app中的第二个实例,app delegate。UIApplicationMain 函数的第四个参数指定了app delegate所属的类,
    NSStringFromClass(AppDelegate.self) 。如果使用 @UIApplicationMain 属性,该属性默认附加在 AppDelegate 类的声明中,其意义与UIApplicationMain 函数一样。

  3. 如果 Info.plist 文件指定了一个main storyboard, UIApplicationMain 函数就载入storyboard并找到其中的initial view controller(或者说是storyboard的入口点),并实例化该view controller,这是创建的第三个实例。对于Single View app模版,这个实例就是 ViewController 类的实例,该类定义在 ViewController.swift 中。

  4. 如果存在main.storyboard文件,UIApplicationMain 函数现在就该创建应用程序的window了,这是app中的第四个实例,一个UIWindow类的实例(或者在AppDelegate中,可以替换为一个UIWindow子类的实例)。创建window实例后,将其指定为AppDelegate的 window 属性,同时,将initial view controller实例的指定为window实例的 rootViewController属性。

  5. UIApplicationMain 现在开始处理AppDelegate实例并调用它的一些方法,如 application(_:didFinishLaunchingWithOptions:), 在该方法中,我们可以加入自己的代码进行一些初始化的设定,但不要进行一些比较耗时的工作,因为在这个时候,我们的app界面还没有显示出来。

  6. 如果存在main.storyboard文件, UIApplicationMain 函数开始调用UIWindow的实例方法 makeKeyAndVisible ,这样app界面就显示出来了。

  7. 在window显示的过程中,将获取root view controller的main view, 如果view controller的view是通过storyboard或xib文件获取的,那么该nib文件会被加载。nib文件中的实例化并初始化,并称为初始界面的对象,view及其subview将被放置在window中。view controller的 viewDidLoad 方法被调用,在这里可以写一些自己的代码。

然后应用程序已经启动完成并开始运行,UIApplicationMain 函数仍然运行而且永不return,它继续监视用户行为,管理eventloop等。

------------- 本文结束感谢您的阅读 -------------