浅谈 iOS 应用启动过程

因为种种缘由,掘金等第三方平台博客再也不保证可以同步更新,欢迎移步 GitHub:github.com/kingcos/Per…。谢谢!ios

Create an iOS single view application manually in Swift.git

Date Notes Swift Xcode
2017-05-26 CS193p UIApplication 3.1 8.3.2
2017-03-28 首次提交 3.0 8.2.1

Preface

首先要感谢没故事的卓同窗大大送的泊学会员,在泊学学了几节课,了解了不少不一样角度的 iOS 开发知识。这篇文章就启发自其 iOS 101 中的一个纯手工的 Single View Application 一文。但本文将更加深刻的叙述了启动过程,且实现均为 Swift 3.0。github

本文对应的 Demo 能够在:github.com/kingcos/Sin… 查看、下载。swift

Manually or Storyboard

main.m in Objective-C Single View Application网络

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
复制代码
  • 自从 Storyboard 诞生以来,关于纯代码、Xib、以及 Storyboard 的选择就在 iOS 开发圈中炸开了锅。这里不会探讨各类姿式的优劣,只是可能不少和我同样的初学者,从一开始就被 Storyboard 先入为主。加上方便灵活的拖控件,天然而然就可能没有机会去思考一个 iOS 应用是如何启动起来的。加上 Swift 的诞生,使得整个项目的初始结构获得了更大的简化(少了 main.m 以及不少 .h 头文件)。
  • 为了便于研究 iOS 应用的启动过程,咱们删除 Xcode 自动建立的 Main.storyboard,并把 Main Interface 清空。

Main Interface

AppDelegate.swift

  • AppDelegate.swift 中是 AppDelegate 类。
  • AppDelegate 将会建立 App 内容绘制的窗口,并提供应用内响应状态改变(state transitions)的地方。
  • AppDelegate 将会建立 App 的入口和 Run Loop(运行循环),并将输入事件发送到 App(由 @UIApplicationMain 完成)。

Run Loop: An event processing loop that you use to schedule work and coordinate the receipt of incoming events in your app. (From Start Developing iOS Apps (Swift)) Run Loop 是一个事件处理循环,能够用来在应用中安排任务并定位收到的即将到来的事件。app

AppDelegate.swiftide

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        window = UIWindow()
        window?.backgroundColor = UIColor.red
        window?.rootViewController = UIViewController()
        window?.makeKeyAndVisible()

        return true
    }
}
复制代码
  • 由于咱们删除了 Main.storyboard,咱们须要以上代码初始化 UIWindow(根 UIView),并使得其可见,关于 UIWindow 能够参考文末的连接。
  • AppDelegate 中的方法将应用程序对象和代理联系起来,当应用在不一样状态会自动调用相应的代理方法,咱们能够自定义相应的实现,抑或留空或删除即便用默认的实现。
  • application(_:​did​Finish​Launching​With​Options:​):该方法在应用启动进程几乎完成且将要运行之际调用,所以在其中初始化 window,设置根控制器,并使得其可见。

@UIApplicationMain

main.swift函数

import UIKit

UIApplicationMain(
    CommandLine.argc,
    UnsafeMutableRawPointer(CommandLine.unsafeArgv)
        .bindMemory(
            to: UnsafeMutablePointer<Int8>.self,
            capacity: Int(CommandLine.argc)),
    nil,
    NSStringFromClass(AppDelegate.self)
)
复制代码
  • 在 AppDelegate.swift 文件中 AppDelegate 类声明之上的一行即是 @UIApplicationMain。
  • 咱们能够尝试将该行注释,连接器将直接报错「entry point (_main) undefined.」,即入口 main 未定义,所以能够得知 @UIApplicationMain 标签将会根据其下方的 AppDelegate 建立一个 UIApplicationMain 入口并启动程序。
  • 手动实现 @UIApplicationMain:
    • 若是不使用 @UIApplicationMain 标签,须要本身创建一个 main.swift 文件,但咱们不须要本身建立方法,Xcode 能够直接将该文件中的代码看成 main() 函数。
    • 在 main.swift 中,咱们添加以上的代码。

Code written at global scope is used as the entry point for the program, so you don’t need a main() function. (From The Swift Programming Language (Swift 3.0.1)) 全局范围书写的代码将被看成程序入口,因此不须要 main() 函数。oop

UIApplication​Main()

  • 在 main.swift 中,调用了 int UIApplicationMain(int argc, char * _Nonnull argv[], NSString *principalClassName, NSString *delegateClassName); 方法。
  • 该方法在建立了应用程序对象、应用程序代理、以及设置了事件循环。
  • UIApplication​Main() 的前两个参数是命令行参数。
  • principalClassName: 该参数为 UIApplication 类名或其子类名的字符串,nil 是默认为 UIApplication。
  • delegateClassName: 该参数为要初始化的应用程序代理(AppDelegate)类,也可指定为 nil 但须要从应用程序的主 nib 文件加载代理对象。
  • 虽然该函数有返回值,但从不返回。

UIApplication

MyApp.swift字体

class MyApp: UIApplication {
    override func sendEvent(_ event: UIEvent) {
        print("\(#function) - Event: \(event)")

        super.sendEvent(event)
    }
}
复制代码
  • 由上文得知,main.swift 中 UIApplication​Main()的第三个参数能够为 UIApplication 类名或其子类名的字符串。
  • 新建一个 MyApp.swift 在其中定义一个 UIApplication 子类,咱们即可以在这里作一些针对应用全局的事情,好比重写 sendEvent(:) 方法即可以监听到整个应用发送事件。

Update for CS193p

let myApp = UIApplication.shared
复制代码
  • UIApplication 在 App 中是单例的。
  • UIApplication 管理全部全局行为。
  • UIApplication 不须要子类化。
// 在其余 App 中打开 URL
open func open(_ url: URL, options: [String : Any] = [:], completionHandler completion: ((Bool) -> Swift.Void)? = nil)

@available(iOS 3.0, *)
open func canOpenURL(_ url: URL) -> Bool

// 注册接收推送通知
@available(iOS 8.0, *)
open func registerForRemoteNotifications()

@available(iOS 3.0, *)
open func unregisterForRemoteNotifications()
// 本地或推送的通知由 UNNotification 处理

// 设置后台取回间隔
@available(iOS 7.0, *)
open func setMinimumBackgroundFetchInterval(_ minimumBackgroundFetchInterval: TimeInterval)
// 一般将其设置为:
UIApplicationBackgroundFetchIntervalMinimum

// 后台时请求更长时间
@available(iOS 4.0, *)
open func beginBackgroundTask(expirationHandler handler: (() -> Swift.Void)? = nil) -> UIBackgroundTaskIdentifier
// 不要忘记结束时调用 endBackgroundTask(UIBackgroundTaskIdentifier)

// 状态来网络使用 Spinner 显示开关
var isNetworkActivityIndicatorVisible: Bool

var backgroundTimeRemaining: TimeInterval { get } // 直到暂停
var preferredContentSizeCategory: UIContentSizeCategory { get } // 大字体或小字体
var applicationState: UIApplicationState { get } // 前台,后台,已激活
复制代码

Reference

也欢迎您关注个人微博 @萌面大道V & 简书

相关文章
相关标签/搜索