SwiftUI2 應(yīng)用程序生命周期

長期以來,iOS 開發(fā)者一直使用AppDelegates 作為他們應(yīng)用程序的主要入口點(diǎn)。隨著 SwiftUI2 在 WWDC 2020 上的推出,Apple 引入了一個(gè)新的應(yīng)用程序生命周期,它(幾乎)完全取消了AppDelegate,為類似 DSL 的方法讓路。

在本文中,我將討論為何引入此更改,以及如何在新應(yīng)用或現(xiàn)有應(yīng)用中利用新生命周期。

指定應(yīng)用程序入口點(diǎn)

我們需要回答的第一個(gè)問題是,我們?nèi)绾胃嬖V編譯器我們應(yīng)用程序的入口點(diǎn)?SE-0281指定了基于類型的程序入口點(diǎn)的工作方式:

Swift 編譯器將使用@main屬性注釋的類型標(biāo)注為程序的入口點(diǎn)。用@main標(biāo)記的類型有一個(gè)隱式要求:聲明一個(gè)靜態(tài)main()方法。

創(chuàng)建新的 SwiftUI 應(yīng)用程序時(shí),應(yīng)用程序的main class 如下所示:

import SwiftUI
@main
struct SwiftUIAppLifeCycleApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

那么SE-0281中提到的靜態(tài)函數(shù)main()在哪里呢?

好吧,事實(shí)證明,框架提供者可以(并且應(yīng)該)為用戶的方便提供默認(rèn)實(shí)現(xiàn)。查看上面的代碼片段,您會注意到它SwiftUIAppLifeCycleApp遵循App協(xié)議。Apple 提供了如下所示的協(xié)議擴(kuò)展:

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension App {
    /// Initializes and runs the app.
    ///
    /// If you precede your ``SwiftUI/App`` conformer's declaration with the
    /// [@main](https://docs.swift.org/swift-book/ReferenceManual/Attributes.html#ID626)
    /// attribute, the system calls the conformer's `main()` method to launch
    /// the app. SwiftUI provides a
    /// default implementation of the method that manages the launch process in
    /// a platform-appropriate way.
    public static func main()
}

我們有了它——這個(gè)協(xié)議擴(kuò)展提供了一個(gè)默認(rèn)的實(shí)現(xiàn)來處理應(yīng)用程序的啟動。

由于 SwiftUI 框架不是開源的,我們無法看到 Apple 如何實(shí)現(xiàn)這一點(diǎn),但Swift Argument Parser是開源的,并且也使用這種方法。查看源代碼ParsableCommand以了解他們?nèi)绾问褂脜f(xié)議擴(kuò)展來提供main作為程序入口點(diǎn)的靜態(tài)函數(shù)的默認(rèn)實(shí)現(xiàn):

extension ParsableCommand {
...
  public static func main(_ arguments: [String]?) {
    do {
      var command = try parseAsRoot(arguments)
      try command.run()
    } catch {
      exit(withError: error)
    }
  }
  public static func main() {
    self.main(nil)
  }
}

如果所有這些聽起來有點(diǎn)復(fù)雜,那么好消息是您在創(chuàng)建新的 SwiftUI 應(yīng)用程序時(shí)實(shí)際上不必?fù)?dān)心它:只需確保在創(chuàng)建應(yīng)用程序時(shí)在Life Cycle下拉列表中選擇SwiftUI App,就可以了:

創(chuàng)建一個(gè)新的 SwiftUI 項(xiàng)目

讓我們來看看一些常見的場景。

初始化資源,SDK 或框架

大多數(shù)應(yīng)用程序在應(yīng)用程序啟動時(shí)需要執(zhí)行幾個(gè)步驟:獲取一些配置值、連接到數(shù)據(jù)庫或初始化框架或第三方SDK。

通常,您會在ApplicationDelegatesapplication(_:didFinishLaunchingWithOptions:)方法中執(zhí)行此操作。由于我們不再有application delegate,我們需要找到其他方法來初始化我們的應(yīng)用程序。根據(jù)您的具體要求,這里有一些策略:

  • 在您的main class上實(shí)現(xiàn)初始化程序(請參閱文檔
  • 設(shè)置存儲屬性的初始值(參見文檔
  • 使用閉包設(shè)置默認(rèn)屬性值(請參閱文檔
@main
struct ColorsApp: App {
  init() {
    print("Colors application is starting up. App initialiser.")
  }
  
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
}

如果這些都不能滿足您的需求,可能你真的需要AppDelegate。最后會介紹如何引入AppDelegate的相關(guān)方法。

應(yīng)用程序的生命周期

有時(shí)能夠知道您的應(yīng)用程序處于哪種狀態(tài)很有用。例如,您可能希望在應(yīng)用程序變?yōu)榛顒訝顟B(tài)active時(shí)立即獲取新數(shù)據(jù),或者在應(yīng)用程序變?yōu)榉腔顒訝顟B(tài)inactive并轉(zhuǎn)換到后臺background時(shí)刷新所有緩存。

通常情況下,你會在你的ApplicationDelegate中實(shí)現(xiàn)applicationDidBecomeActiveapplicationWillResignActiveapplicationDidEnterBackground

從 iOS 14.0 開始,Apple 提供了一個(gè)新的 API,允許以更優(yōu)雅和可維護(hù)的方式跟蹤應(yīng)用程序的狀態(tài):ScenePhase. 您的項(xiàng)目可以有多個(gè)場景,但您可能只有一個(gè)場景,用WindowGroup.

SwiftUI 跟蹤環(huán)境中場景的狀態(tài),您可以通過使用@Environment屬性包裝器獲取當(dāng)前值,然后使用onChange(of:)修飾符來偵聽任何更改,從而使代碼可以訪問當(dāng)前值:

@main
struct SwiftUIAppLifeCycleApp: App {
  @Environment(\.scenePhase) var scenePhase
  
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .onChange(of: scenePhase) { newScenePhase in
      switch newScenePhase {
      case .active:
        print("App is active")
      case .inactive:
        print("App is inactive")
      case .background:
        print("App is in background")
      @unknown default:
        print("Oh - interesting: I received an unexpected new value.")
      }
    }
  }
}

值得注意的是,您也可以從應(yīng)用程序的其他位置讀取phase。在應(yīng)用程序的頂層讀取phase時(shí)(如代碼片段中所示),您將獲得應(yīng)用程序中所有phase的匯總。.inactive的值表示您的應(yīng)用程序中沒有任何場景處于活動狀態(tài)。讀取視圖上的phase時(shí),您將收到包含該視圖的phase的值。請記住,此時(shí)您的應(yīng)用程序可能包含具有其他phase值的其他場景。有關(guān)scenephase的更多詳細(xì)信息,請閱讀 Apple 的文檔

deep links

以前,在處理deep links時(shí),您必須實(shí)現(xiàn)application(_:open:options:),并將傳入的URL路由到最合適的處理程序。

使用新的應(yīng)用程序生命周期模型,這變得容易多了。您可以通過將onOpenURL修飾符附加到應(yīng)用程序的最頂層場景來處理傳入的 URL :

@main
struct SwiftUIAppLifeCycleApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
        .onOpenURL { url in
          print("Received URL: \(url)")
        }
    }
  }
}

真正酷的是:您可以在整個(gè)應(yīng)用程序中安裝多個(gè)URL處理程序 - 使deep links更容易,因?yàn)槟梢栽谧詈线m的地方處理傳入鏈接。

如果可能,您應(yīng)該使用universal links (或Firebase 動態(tài)鏈接,它使用iOS 應(yīng)用程序的通用鏈接),因?yàn)樗鼈兪褂藐P(guān)聯(lián)的域名在您擁有的網(wǎng)站和您的應(yīng)用程序之間創(chuàng)建連接 - 這將允許您安全地共享數(shù)據(jù)。

但是,您仍然可以使用自定義URL schemes來鏈接到您的應(yīng)用程序中的內(nèi)容。

無論哪種方式,在您的應(yīng)用程序中觸發(fā)深層鏈接的一種簡單方法是在您的開發(fā)機(jī)器上使用以下命令:

xcrun simctl openurl booted <your url>
demo.gif

Continuing user activities

如果應(yīng)用程序使用NSUserActivity,以與Siri, Handoff, or Spotlight整合,你需要處理用戶活動的延續(xù)。

同樣,新的應(yīng)用程序生命周期模型通過提供兩個(gè)修飾符使您可以更輕松地進(jìn)行此操作,這些修飾符允許您advertise活動并在以后繼續(xù)該活動。

下面是一個(gè)片段,展示了如何宣傳活動,例如,在詳細(xì)信息視圖中:

struct ColorDetailsView: View {
  var color: String
  
  var body: some View {
    Image(color)
      // ...
      .userActivity("showColor" ) { activity in
        activity.title = color
        activity.isEligibleForSearch = true
        activity.isEligibleForPrediction = true
        // ...
      }
  }
}

為了繼續(xù)這個(gè)活動,你可以在你的頂級導(dǎo)航視圖中注冊一個(gè)onContinueUserActivity閉包,像這樣:

import SwiftUI
struct ContentView: View {
  var colors = ["Red", "Green", "Yellow", "Blue", "Pink", "Purple"]
  
  @State var selectedColor: String? = nil
  
  var body: some View {
    NavigationView {
      ScrollView {
        LazyVGrid(columns: columns) {
          ForEach(colors, id: \.self) { color in
            NavigationLink(destination: ColorDetailsView(color: color),
                           tag: color,
                           selection: $selectedColor) {
              Image(color)
            }
          }
        }
        .onContinueUserActivity("showColor") { userActivity in
          if let color = userActivity.userInfo?["colorName"] as? String {
            selectedColor = color
          }
        }
      }
    }
  }
}

以上都不適合我怎么辦!

并非所有AppDelegate的回調(diào)都受新應(yīng)用程序生命周期的支持(目前)。如果以上都不能滿足您的需求,那么您可能真的需要一個(gè)AppDelegate

您可能需要AppDelegate的另一個(gè)原因是,您是否使用任何第三方 SDK,這些SDK利用method swizzling將自身注入應(yīng)用程序生命周期。Firebase是一個(gè)眾所周知的案例

為了幫助您,Swift 提供了一種將遵循AppDelegate協(xié)議的代理與您的App連接的方法:@UIApplicationDelegateAdaptor. 以下是如何使用它:

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    print("Colors application is starting up. ApplicationDelegate didFinishLaunchingWithOptions.")
    return true
  }
}
@main
struct ColorsApp: App {
  @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
  
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

如果您復(fù)制現(xiàn)有AppDelegate實(shí)現(xiàn),請不要?jiǎng)h除@main- 否則,編譯器會抱怨多個(gè)應(yīng)用程序入口點(diǎn)。

結(jié)論

有了這一切,讓我們來討論一下 Apple 做出這一改變的原因。我認(rèn)為有以下幾個(gè)原因:

SE-0281明確指出,設(shè)計(jì)目標(biāo)之一是提供一種更通用和輕量級的機(jī)制,用于將程序的入口點(diǎn)委托給指定類型。

Apple 為處理應(yīng)用程序生命周期而選擇的基于DSL的方法與在 SwiftUI 中構(gòu)建 UI 的聲明式方法非常吻合。使用相同的概念使事情更容易理解,并有助于新開發(fā)人員的上手。

任何聲明式方法的主要好處是:框架/平臺提供者不會將實(shí)現(xiàn)特定功能的負(fù)擔(dān)推給開發(fā)人員,而是負(fù)責(zé)解決這個(gè)問題。如果需要進(jìn)行任何更改,在不破壞許多開發(fā)人員的應(yīng)用程序的情況下發(fā)布這些更改會容易得多 - 理想情況下,開發(fā)人員不必更改他們的實(shí)現(xiàn),因?yàn)榭蚣軙槟幚硪磺小?/p>

總體而言,新的應(yīng)用程序生命周期模型使您的應(yīng)用程序啟動實(shí)施變得更容易、更簡單。你的代碼會更干凈、更容易維護(hù)——如果你問我,我認(rèn)為這是一件好事。

Github示例代碼

翻譯自The Ultimate Guide to the SwiftUI 2 Application Life Cycle

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,677評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內(nèi)容