WWDC20 第二弹 - 苹果小程序?App Clip

2,146 阅读7分钟
App Clip可以说是一众Session里最引人注目的了,Apple生态又增加了新的产品形态。无论承认与否,当裁判也开始下场踢球,App Clip将带来的改变绝不可小觑。

什么是App Clips?


从Apple Session的说明,我们可以这么理解:用户通过 NFC 标签、二维码、Message 信息、Map 、Safari、Siri 建议等途径唤起 App Clip应用程序,在未安装主App的情况下,以类似网页的接触形式和原生级别的体验,使用主App中的某些服务和功能

App Clip实际上就是一段格式遵从 Universal Link 格式的URL标识,Apple针对做了系统级处理。只不过App Clip URL与应用一样,必须在App Store Connect注册。

PS:详细内容感兴趣的可以查看WWDC20 Session 10146: Configure and link your app clips

使用场景

Apple对于App Clip的定位是希望能够进一步触及到用户生活的方方面面,包括一些低频但必要的场景。

在上面的一些场景中,往往用户并没有特别服务于当前场景下的App,如果有一个程序足够轻量,免安装,且快速且高效地让用户能够触达当前场景下的服务,连通人与线下是不是更简单?这就是App Clips的设计初衷,让人与线下服务更触手可及。

以Apple提供的订购沙冰的例子说明:通过NFC唤醒商家App Clip,点击打开并开始购买。

如何唤醒?

苹果对App Clip的使用场景非常明确,系统对调起方式做了严格的过滤,支持的发起入口有如下几种:

  1. NFC
  2. QR Codes(也就是二维码,专门的生成工具会在年底开放)
  3. Maps
  4. Siri 建议
  5. Safari链接
  6. Messages

特点

  1. 由于有10M的大小限制,对于现在的网络状态而言(国内移动网络,懂得都懂),基本可以快速的打开,因此苹果在App Clip的提示上都使用了“Open”而不是“Install”,来降低用户的接受程度。
  2. 为了进一步提高App Clip到主App的转化,在使用App Clip的过程中,Apple也会明显提示主App的存在,方便用户直接去下载。
  3. 30天不使用,系统自动删除App Clip及数据,来确保用户的存储空间能够合理分配。
  4. 支持地理位置,相机,麦克风和蓝牙等权限,限制访问 Health、Fitness、通讯录、信息、照片、文件等个人数据。
  5. 为了避免弹窗授权的糟糕体验,设计了免申请的通知、定位权限。当然还是有限制,免申请的通知只在 8 个小时内有效,地理位置只能获取一次。
  6. 支持Sign in with Apple,也支持ASWebAuthenticationSession来第三方登录,以及使用Apple Pay。
  7. 不会与App一起出现在用户的Setting App里,有单独的App Clips的分组。
  8. 一旦安装了主App,对应的App Clip将会被删除,再点击链接将会直接进入主App。

实现方式


依赖主App

虽然对于用户来说不需要下载主App,但开发者必须使App Clip跟随主App一同提交审核,App Clip并不能够独立开发并提审。(这与Apple设计之初的理念是一致的,目的是为了快速体验功能,而不是替代App)

独立的Target

在开发上,也是完全Native的实现,类似于一个新增的Extension target,例如Keyboard Extension、iMessage Extension等。

支持UIKit与SwiftUI

有些人认为只能够使用SwiftUI开发(包括我的leader),实际上是错误的,Apple只是说SwiftUI开发会更快(毕竟未来主打),可以直接看测试工程:

App Clip不仅支持SwiftUI,也支持UIKit,包括很多人还在坚守的Objective-C,完全都没有问题,并不存在上手难度。

数据共享

由于Target依赖于主App,所以Target间的资源共享都是完全OK的,只需要在资源归属上勾选上App Clip就可以了;同时,与Extension一致,App Clip可以通过App Groups来与主App共享数据。

引导转化

Apple建议开发者可以在App Clip的视图中嵌入SKOverlay,当用户在App Clip中完成相关任务后展示SKOverlay,这样可以较好的引导用户,比如可以将其放置在用户的付款确认界面之后。

SKOverlay也是新特性,这里就不展开说了,详细请参考WWDC20 Session: What's New with In-App Purchase

支持一对多

同时,主工程支持多个App Clip Target,目前并不清楚Apple对于数量的限制是多少,但是估计能够满足大部分App主要功能的拆分,以某团为例,可以存在多个App Clip:单车、外卖、酒店住宿、打车等等。

支持不同参数

只需要提供不同参数,就可以针对不同场景不同需求来提供不同的 App Clip 体验,例如官方提供的统一连锁下不同咖啡馆举例:

如何处理Universal Link和App Clip URL?

官方Demo提供的解决方案是通过编译宏APPCLIP来做分支处理,这样能够最大程度共用代码:

import SwiftUI
#if APPCLIP
import AppClip
import CoreLocation
#endif

@main
struct FrutaApp: App {
    @StateObject private var model = FrutaModel()

    #if !APPCLIP
    @StateObject private var store = Store()
    #endif

    @SceneBuilder var body: some Scene {
        WindowGroup {
            #if APPCLIP
            NavigationView {
                SmoothieMenu()
            }
            .environmentObject(model)
            .onContinueUserActivity(NSUserActivityTypeBrowsingWeb, perform: handleUserActivity)
            #else
            ContentView()
                .environmentObject(model)
                .environmentObject(store)
            #endif
        }
    }

    #if APPCLIP
    func handleUserActivity(_ userActivity: NSUserActivity) {
        guard let incomingURL = userActivity.webpageURL,
              let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true),
              let queryItems = components.queryItems else {
            return
        }
        if let smoothieID = queryItems.first(where: { $0.name == "smoothie" })?.value {
            model.selectSmoothie(id: smoothieID)
        }
        guard let payload = userActivity.appClipActivationPayload,
              let latitudeValue = queryItems.first(where: { $0.name == "latitude" })?.value,
              let longitudeValue = queryItems.first(where: { $0.name == "longitude" })?.value,
              let latitude = Double(latitudeValue), let longitude = Double(longitudeValue) else {
            return
        }
        let region = CLCircularRegion(center: CLLocationCoordinate2D(latitude: latitude,
                            longitude: longitude), radius: 100, identifier: "smoothie_location")
        payload.confirmAcquired(in: region) { inRegion, error in
            if let error = error {
                print(error.localizedDescription)
                return
            }
            DispatchQueue.main.async {
                model.applePayAllowed = inRegion
            }
        }
    }
    #endif
}

其中handleUserActivity就是在处理App Clip链接。

当然,直接将主App和App Clip的代码分开也是可以的,但是这可能对于两者区别较大的产品更适合,但同时也违反了Apple的初衷,希望App Clip仅仅是主App的一部分,这可能会导致审核遇阻。

与小程序的异同


相信看到这儿,大家应该对App Clip有一定了解,很多自媒体会拿微信小程序比较,认为App Clip是苹果小程序,如果仅从两者设计之初的理念来比较,确实比较相似,但是从其他方面来讲,两者差异较大:

  1. 入口不同:App Clip支持多种打开方式,微信小程序只支持从微信进入,但是后者支持主动搜索。
  2. 性能不同:App Clip依赖于系统,微信小程序依赖于微信,原生性能会明显优于小程序。
  3. 体验路径不同:微信小程序需要打开微信,下拉小程序列表,找到并打开目标小程序,App Clip一步到位,实施降维打击。
  4. 定位不同:App Clip明确是主App功能的一部分,不能做主App无关的内容,但是微信小程序没有该限制,并且支持独立发布。

也正是由于定位不同,微信小程序完全可以与App Clip共分天下,微信小程序已经形成生态圈,国内很多开发商专注于小程序的开发,都没有App,自然也就用不上App Clip,虽然App Clip必然会抢占小程序的市场,但是目前看,还是无法从根本上动摇小程序的地位。

一些有待探索的问题


虽然Apple对App Clip进行了相当详尽的说明,仍然有一些问题并没有找到答案:

  1. 如果App Clip不仅提供快捷功能,再通过WKWebview(Web Api可以在App Clip使用)来展现更多内容,Apple是否有对策?
  2. Apple明确限制了包体大小,但是对于内存并没有说明,例如Keyboard的内存管理十分严格,只有几十M,如果App Clip也采取同样限制,开发者还需要注意内存占用的问题。

相关Session

原创不易,文章有任何错误,欢迎批(feng)评(kuang)指(diao)教(wo),顺手点个赞👍,不甚感激!