淺談 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 & 簡書

相關文章
相關標籤/搜索