由于種種原因,簡(jiǎn)書等第三方平臺(tái)博客不再保證能夠同步更新,歡迎移步 GitHub:https://github.com/kingcos/Perspective/。謝謝!
Create an iOS single view application manually in Swift.
| Date | Notes | Swift | Xcode |
|---|---|---|---|
| 2017-05-26 | CS193p UIApplication | 3.1 | 8.3.2 |
| 2017-03-28 | 首次提交 | 3.0 | 8.2.1 |
Preface
首先要感謝沒(méi)故事的卓同學(xué)大大送的泊學(xué)會(huì)員,在泊學(xué)學(xué)了幾節(jié)課,了解了很多不同角度的 iOS 開發(fā)知識(shí)。這篇文章就啟發(fā)自其 iOS 101 中的一個(gè)純手工的 Single View Application 一文。但本文將更加深入的敘述了啟動(dòng)過(guò)程,且實(shí)現(xiàn)均為 Swift 3.0。
本文對(duì)應(yīng)的 Demo 可以在:https://github.com/kingcos/SingleViewAppManually-Demo 查看、下載。
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 誕生以來(lái),關(guān)于純代碼、Xib、以及 Storyboard 的選擇就在 iOS 開發(fā)圈中炸開了鍋。這里不會(huì)探討各種姿勢(shì)的優(yōu)劣,只是可能很多和我一樣的初學(xué)者,從一開始就被 Storyboard 先入為主。加上方便靈活的拖控件,自然而然就可能沒(méi)有機(jī)會(huì)去思考一個(gè) iOS 應(yīng)用是如何啟動(dòng)起來(lái)的。加上 Swift 的誕生,使得整個(gè)項(xiàng)目的初始結(jié)構(gòu)得到了更大的簡(jiǎn)化(少了 main.m 以及很多 .h 頭文件)。
- 為了便于研究 iOS 應(yīng)用的啟動(dòng)過(guò)程,我們刪除 Xcode 自動(dòng)創(chuàng)建的 Main.storyboard,并把 Main Interface 清空。

AppDelegate.swift
- AppDelegate.swift 中是 AppDelegate 類。
- AppDelegate 將會(huì)創(chuàng)建 App 內(nèi)容繪制的窗口,并提供應(yīng)用內(nèi)響應(yīng)狀態(tài)改變(state transitions)的地方。
- AppDelegate 將會(huì)創(chuàng)建 App 的入口和 Run Loop(運(yùn)行循環(huán)),并將輸入事件發(fā)送到 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 是一個(gè)事件處理循環(huán),可以用來(lái)在應(yīng)用中安排任務(wù)并定位收到的即將到來(lái)的事件。
AppDelegate.swift
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
}
}
- 因?yàn)槲覀儎h除了 Main.storyboard,我們需要以上代碼初始化 UIWindow(根 UIView),并使得其可見(jiàn),關(guān)于 UIWindow 可以參考文末的鏈接。
- AppDelegate 中的方法將應(yīng)用程序?qū)ο蠛痛砺?lián)系起來(lái),當(dāng)應(yīng)用在不同狀態(tài)會(huì)自動(dòng)調(diào)用相應(yīng)的代理方法,我們可以自定義相應(yīng)的實(shí)現(xiàn),抑或留空或刪除即使用默認(rèn)的實(shí)現(xiàn)。
-
application(_:?did?Finish?Launching?With?Options:?):該方法在應(yīng)用啟動(dòng)進(jìn)程幾乎完成且將要運(yùn)行之際調(diào)用,因此在其中初始化 window,設(shè)置根控制器,并使得其可見(jiàn)。
@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。
- 我們可以嘗試將該行注釋,鏈接器將直接報(bào)錯(cuò)「entry point (_main) undefined.」,即入口 main 未定義,因此可以得知 @UIApplicationMain 標(biāo)簽將會(huì)根據(jù)其下方的 AppDelegate 創(chuàng)建一個(gè) UIApplicationMain 入口并啟動(dòng)程序。
- 手動(dòng)實(shí)現(xiàn) @UIApplicationMain:
- 如果不使用 @UIApplicationMain 標(biāo)簽,需要自己建立一個(gè) main.swift 文件,但我們不需要自己創(chuàng)建方法,Xcode 可以直接將該文件中的代碼當(dāng)作 main() 函數(shù)。
- 在 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))
全局范圍書寫的代碼將被當(dāng)作程序入口,所以不需要 main() 函數(shù)。
UIApplication?Main()
- 在 main.swift 中,調(diào)用了
int UIApplicationMain(int argc, char * _Nonnull argv[], NSString *principalClassName, NSString *delegateClassName);方法。 - 該方法在創(chuàng)建了應(yīng)用程序?qū)ο?、?yīng)用程序代理、以及設(shè)置了事件循環(huán)。
- UIApplication?Main() 的前兩個(gè)參數(shù)是命令行參數(shù)。
- principalClassName: 該參數(shù)為 UIApplication 類名或其子類名的字符串,nil 是默認(rèn)為 UIApplication。
- delegateClassName: 該參數(shù)為要初始化的應(yīng)用程序代理(AppDelegate)類,也可指定為 nil 但需要從應(yīng)用程序的主 nib 文件加載代理對(duì)象。
- 雖然該函數(shù)有返回值,但從不返回。
UIApplication
MyApp.swift
class MyApp: UIApplication {
override func sendEvent(_ event: UIEvent) {
print("\(#function) - Event: \(event)")
super.sendEvent(event)
}
}
- 由上文得知,main.swift 中 UIApplication?Main()的第三個(gè)參數(shù)可以為 UIApplication 類名或其子類名的字符串。
- 新建一個(gè) MyApp.swift 在其中定義一個(gè) UIApplication 子類,我們便可以在這里做一些針對(duì)應(yīng)用全局的事情,比如重寫
sendEvent(:)方法便可以監(jiān)聽到整個(gè)應(yīng)用發(fā)送事件。
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
// 注冊(cè)接收推送通知
@available(iOS 8.0, *)
open func registerForRemoteNotifications()
@available(iOS 3.0, *)
open func unregisterForRemoteNotifications()
// 本地或推送的通知由 UNNotification 處理
// 設(shè)置后臺(tái)取回間隔
@available(iOS 7.0, *)
open func setMinimumBackgroundFetchInterval(_ minimumBackgroundFetchInterval: TimeInterval)
// 通常將其設(shè)置為:
UIApplicationBackgroundFetchIntervalMinimum
// 后臺(tái)時(shí)請(qǐng)求更長(zhǎng)時(shí)間
@available(iOS 4.0, *)
open func beginBackgroundTask(expirationHandler handler: (() -> Swift.Void)? = nil) -> UIBackgroundTaskIdentifier
// 不要忘記結(jié)束時(shí)調(diào)用 endBackgroundTask(UIBackgroundTaskIdentifier)
// 狀態(tài)來(lái)網(wǎng)絡(luò)使用 Spinner 顯示開關(guān)
var isNetworkActivityIndicatorVisible: Bool
var backgroundTimeRemaining: TimeInterval { get } // 直到暫停
var preferredContentSizeCategory: UIContentSizeCategory { get } // 大字體或小字體
var applicationState: UIApplicationState { get } // 前臺(tái),后臺(tái),已激活
Reference
- 一個(gè)純手工的 Single View Application
- Start Developing iOS Apps (Swift)
- @UIAPPLICATIONMAIN
- How to call UIApplicationMain in main.swift in Swift 3
- CS193p 查漏補(bǔ)缺(二)Lecture 04 - UIWindow
- CS193p 查漏補(bǔ)缺(九)Lecture 16