由于我们已经在项目中使用了 SwiftUI,这使得泊学 App 只能在 iOS 13 及以后的版本中运行。因此,项目中为兼容更早版本 iOS 的代码就都没必要了。于是在继续之前,我们先清理下这部分代码。它们主要影响的是在各种交互条件下,对通知权限的申请。

整理 App 的启动流程

我们先通过一张图看下整理后的 App 主要启动流程:

其中这一节要修改的,就是图中橙色的部分。

Clear Keychain

当 App 第一次启动时,我们要清除之前保存在 Keychain 中的用户信息。这是因为当用户在登录状态下删除 App 并重新安装时,由于 iOS 并不会清除 Keychain 中的数据,导致了下次启动时,会误用之前保存的用户信息发起请求,这显然不是我们期望的。

那么,该如何进行清理呢?这要从 Keychain 中都保存了什么内容说起。当前,我们一共保存了三类数据。一类是App 启动时,向服务器申请的用于验证返回值的公钥,这部分代码,在 AppDelegate 里:

_ = attempt() {
  KeychainBasedPublicKeyInfoStore
    .default
    .refresh()
    .done { print($0) }
}

其中,KeychainBasedPublicKeyInfoStore 会在 Keychain 中保存当前使用的公钥版本(io.boxue.key.public.version)以及公钥(io.boxue.key.public.pem)。这两个信息是和用户个人数据无关的,并且每次 App 启动时会自动更新。因此,它们可以一直留在 Keychain 里。

第二类,是用户成功登录后,由服务器下发的标识用户的 RemoteUserSession,它在 Keychain 中通过 io.boxue.key.remote.usersession 键保存,这个数据应该是要删掉的;

第三类,则是记录用户账号和个人信息的 UserProfile,它在 Keychain 中通过 io.boxue.key.user.profile 键保存,这个数据也应该要删掉;

在泊学 App 里,RemoteUserSessionUserProfile 统一用 UserSession 这个 model 表示,而对这个 model 的操作,则由 UserSessionRepository 这个协议完成,因此,在这里,我们添加了一个清除用户数据的方法:

public protocol UserSessionRepository {
  func clearUserSession()
}

然后,在 BoxueUserSessionRepository 的实现里,分别调用删除这两个数据的 delete 方法:

public func clearUserSession() {
  _ = userProfileStore.delete()
  _ = remoteUserSessionStore.delete()
}

最后,在 LaunchViewModelinit 方法里,判断如果是第一次启动,就清空记录用户信息的 Keychain 数据:

public init(userSessionRepository: UserSessionRepository,
            guideResponder: GuideResponder,
            browseResponder: BrowseResponder) {
  /// ...

  if isFirstLaunch {
    userSessionRepository.clearUserSession()
  }
}

Provisional notification 权限

第二处要修改的,是当用户在欢迎界面点击“随便看看”的时候,不再需要判断当前系统是否支持 provisional 通知了,直接申请这种默认的通知权限即可。于是,在 WelcomeViewModelrequestNotification 方法里,我们可以直接生成请求权限的事件:

func requestNotification() {
  // If people choosed "Browse now", they might just wanna look around.
  // So do not interfere them by a popup. The provisional permission can make
  // our notifications in the notification center by default.
  requestProvisionalNotificationSubject.onNext(())
  browseResponder.browse()
}

将 SwiftUI 组件移动到 Boxue_iOS

最后一处要修改的,是 Boxue_iOS 中的 SwiftUI 组件。之前为了让这些组件使用 Xcode 的 UI 预览功能,我们把它放到了 Boxue_iOS target,并且在 Target Membership 中勾选了 Boxue:

但按照最初我们对这个工程结构的划分,可重用组件应该放到 BoxueUIKit 这个框架里。于是,我们把所有属于 UI 组件部分的 SwiftUI 代码都移动到了 BoxueUIKit / Reusable / SwiftUI group,并且去掉了这些文件的 Boxue Target Membership:

最后,在 Boxue_iOS 中保留的,只是和 App 具体页面内容相关的 SwiftUI 文件。例如上图中,保留下来的,都是在首页上要显示的滚动视图。这些视图也将通过访问 BoxueUIKit 这个框架对外提供的接口来实现相关的功能。

这样,和 SwiftUI 的部分就和整个项目最初的功能划分保持一致了。唯一的“缺陷”,就是我们不再能够使用 SwiftUI 的预览功能。不过这个问题就实际的开发体验而言,真的并没有想象中严重。一来,当前版本 Xcode 的预览功能并不好用,甚至当有些结果和想象不一样的时候,你还要怀疑预览是不是有 bug,与其这样,倒不如先不去用它;二来,当你真的熟悉了 SwiftUI 之后,在头脑中的自动布局,要比通过 Xcode 渲染出来,快多了 :)

What's next?

处理了这些不再使用的代码和配置之后,从下一节开始,我们就要着手准备内容浏览的首页了。这个过程比我们想象的复杂一些,因为它包含了一些在接下来构建起它界面时都会用到的机制,例如:

  • 界面的部分功能要区分用户是否登录;
  • 接收到服务器的返回结果之后,要在本地进行缓存,并实现本地缓存显示优先,通过异步方式自动更新界面的效果。这样,当网络不好,或处于飞行模式时,只要曾经读取过的内容,对应的界面就可以正常浏览。对于一个以内容为主的 App 来说,这样做用户体验会更好;

因此,接下来,我们要做的,就是获取网络数据,自动编码解码到 model,自动缓存到 Core Data,在得到数据后自动更新到界面,以及重新加载界面时自动识别缓存,这一系列的功能。这个机制,将为泊学 App 的绝大多数界面提供服务。

所有订阅均支持 12 期免息分期

¥ 59

按月订阅

一个月,观看并下载所有视频内容。初来泊学,这可能是个最好的开始。

开始订阅

¥ 512

按年订阅

一年的时间,让我们一起疯狂地狩猎知识吧。比按月订阅优惠 28%

开始订阅

¥ 1280

泊学终身会员

永久观看和下载所有泊学网站视频,并赠送 100 元商店优惠券。

我要加入
如需帮助,欢迎通过以下方式联系我们