移动 SDK深度链接
iOS
AdStage DeepLink 集成指南(iOS)
目录
概览
AdStage DeepLink SDK 提供以下功能:
- 实时深度链接:通过 URL Scheme、Universal Links 即时处理
- 延迟深度链接:应用安装后首次启动时自动还原
- 动态深度链接创建:通过服务器 API 创建可跟踪的链接
- 归因跟踪:基于 UTM 参数的营销分析
项目设置
CocoaPods 设置
Podfile
platform :ios, '15.0'
target 'YourApp' do
use_frameworks!
# AdStage SDK
pod 'AdapterAdStage', '3.0.11'
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
end
end
end安装:
pod installInfo.plist 设置
1. URL Scheme 设置
在 Info.plist 中添加:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>com.example.myapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>2. Universal Links 设置
在 Info.plist 中添加:
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:go.myapp.com</string>
<string>applinks:go.adstage.net</string>
</array>Xcode 设置:
- 选择 Signing & Capabilities 标签页
- 点击 + Capability
- 添加 Associated Domains
- 添加域名:
applinks:go.myapp.comapplinks:go.adstage.net
3. Apple App Site Association 文件
部署到服务器:https://go.myapp.com/.well-known/apple-app-site-association
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.example.myapp",
"paths": ["*"]
}
]
}
}注意事项:
- 没有文件扩展名(不要加
.json) - Content-Type:
application/json - 必须使用 HTTPS
- 放置于根路径或
.well-known文件夹
4. 必需权限设置
<!-- 网络通信 -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
<!-- 后台模式(可选) -->
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>remote-notification</string>
</array>AppDelegate 设置
Swift - AppDelegate.swift
import UIKit
import AdapterAdStage
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// MARK: - Application Lifecycle
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
// 1. 初始化 AdStage SDK
initializeAdStage()
// 2. 设置深度链接监听器
setupDeepLinkListener()
return true
}
// MARK: - AdStage Initialization
private func initializeAdStage() {
AdStageManager.shared.initialize(
apiKey: "your-api-key-here",
serverUrl: "https://api.adstage.app" // 可选
)
print("✅ AdStage SDK 초기화 완료")
}
private func setupDeepLinkListener() {
// 设置统一的深度链接监听器
DeepLinkManager.shared.setDeepLinkListener { [weak self] deepLinkData in
guard let self = self else { return }
print("""
📱 딥링크 수신:
- Short Path: \(deepLinkData.shortPath)
- Link ID: \(deepLinkData.linkId)
- Source: \(deepLinkData.source.description)
- Parameters: \(deepLinkData.parameters)
""")
// 处理深度链接
self.handleDeepLink(deepLinkData)
}
}
// MARK: - Deep Link Handling
private func handleDeepLink(_ data: DeepLinkData) {
// 按来源分别处理
switch data.source {
case .realtime:
print("🔗 실시간 딥링크 처리")
handleRealtimeDeepLink(data)
case .install:
print("📦 디퍼드 딥링크 처리 (앱 설치 후 첫 실행)")
handleDeferredDeepLink(data)
case .unknown:
print("❓ 알 수 없는 딥링크 소스")
}
}
private func handleRealtimeDeepLink(_ data: DeepLinkData) {
// 基于参数的页面跳转
if let campaign = data.parameters["campaign"] {
navigateToCampaign(campaign)
} else if let promo = data.parameters["promo"] {
navigateToPromotion(promo)
} else {
navigateToHome()
}
}
private func handleDeferredDeepLink(_ data: DeepLinkData) {
// 在引导流程之后处理,或立即处理
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
self?.handleRealtimeDeepLink(data)
}
}
// MARK: - Navigation
private func navigateToCampaign(_ campaign: String) {
print("🎯 캠페인 화면으로 이동: \(campaign)")
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let campaignVC = storyboard.instantiateViewController(
withIdentifier: "CampaignViewController"
) as? CampaignViewController {
campaignVC.campaignId = campaign
if let navController = self.window?.rootViewController as? UINavigationController {
navController.pushViewController(campaignVC, animated: true)
}
}
}
}
private func navigateToPromotion(_ promo: String) {
print("🎁 프로모션 적용: \(promo)")
// 推广处理逻辑
}
private func navigateToHome() {
print("🏠 홈 화면으로 이동")
// 跳转到首页
}
// MARK: - URL Scheme Deep Link (iOS 8+)
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
print("📲 URL Scheme 딥링크 수신: \(url)")
// 交由 AdStage 处理
let handled = DeepLinkManager.shared.handleDeepLink(url)
if handled {
print("✅ AdStage가 딥링크를 처리했습니다")
return true
}
// 处理其他 URL Scheme(例如 OAuth 回调)
if url.scheme == "myapp" {
// 自定义处理
return handleCustomScheme(url)
}
return false
}
// MARK: - Universal Links (iOS 9+)
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// 确认是否为 Universal Link
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
print("🌐 Universal Link 수신: \(url)")
// 交由 AdStage 处理
let handled = DeepLinkManager.shared.handleUniversalLink(userActivity)
if handled {
print("✅ AdStage가 Universal Link를 처리했습니다")
return true
}
// 处理其他 Universal Link
return handleCustomUniversalLink(url)
}
// MARK: - Custom Handlers
private func handleCustomScheme(_ url: URL) -> Bool {
print("🔧 커스텀 URL Scheme 처리: \(url)")
// 处理 OAuth、支付回调等
return false
}
private func handleCustomUniversalLink(_ url: URL) -> Bool {
print("🔧 커스텀 Universal Link 처리: \(url)")
// 处理普通网页链接
return false
}
}SwiftUI - App 结构
import SwiftUI
import AdapterAdStage
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// 处理 URL Scheme
print("📲 URL 수신: \(url)")
_ = DeepLinkManager.shared.handleDeepLink(url)
}
}
}
}
class AppDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil
) -> Bool {
// 初始化 AdStage
AdStageManager.shared.initialize(
apiKey: "your-api-key-here",
serverUrl: "https://api.adstage.app"
)
// 设置深度链接监听器
setupDeepLinkListener()
return true
}
private func setupDeepLinkListener() {
DeepLinkManager.shared.setDeepLinkListener { deepLinkData in
print("📱 딥링크 수신: \(deepLinkData.shortPath)")
// 处理逻辑
}
}
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
// 处理 Universal Link
return DeepLinkManager.shared.handleUniversalLink(userActivity)
}
}深度链接接收处理
1. 实时深度链接处理
import AdapterAdStage
class DeepLinkHandler {
static let shared = DeepLinkHandler()
func setupListener() {
DeepLinkManager.shared.setDeepLinkListener { [weak self] deepLinkData in
guard let self = self else { return }
// 提取深度链接数据
let shortPath = deepLinkData.shortPath
let parameters = deepLinkData.parameters
let source = deepLinkData.source
print("""
✅ 딥링크 수신 완료
- Short Path: \(shortPath)
- Link ID: \(deepLinkData.linkId)
- Source: \(source.description)
- Event Type: \(deepLinkData.eventType)
""")
// 提取 UTM 参数
let utmParams = deepLinkData.getUtmParameters()
print("📊 UTM Parameters: \(utmParams)")
// 提取自定义参数
let customParams = deepLinkData.getCustomParameters()
print("🔧 Custom Parameters: \(customParams)")
// 处理业务逻辑
self.processDeepLink(deepLinkData)
}
}
private func processDeepLink(_ data: DeepLinkData) {
// 按参数分支处理
if let campaign = data.getParameter("campaign") {
handleCampaignDeepLink(campaign, data: data)
} else if let productId = data.getParameter("product_id") {
handleProductDeepLink(productId, data: data)
} else if let promo = data.getParameter("promo") {
handlePromoDeepLink(promo, data: data)
} else {
handleDefaultDeepLink(data)
}
}
private func handleCampaignDeepLink(_ campaign: String, data: DeepLinkData) {
print("🎯 캠페인 딥링크: \(campaign)")
DispatchQueue.main.async {
// 跳转到广告系列页面
NotificationCenter.default.post(
name: .navigateToCampaign,
object: nil,
userInfo: ["campaign": campaign, "data": data]
)
}
}
private func handleProductDeepLink(_ productId: String, data: DeepLinkData) {
print("🛍️ 상품 딥링크: \(productId)")
DispatchQueue.main.async {
NotificationCenter.default.post(
name: .navigateToProduct,
object: nil,
userInfo: ["productId": productId]
)
}
}
private func handlePromoDeepLink(_ promo: String, data: DeepLinkData) {
print("🎁 프로모션 딥링크: \(promo)")
// 自动应用推广码
applyPromoCode(promo)
}
private func handleDefaultDeepLink(_ data: DeepLinkData) {
print("📋 기본 딥링크 처리")
DispatchQueue.main.async {
// 跳转到首页
NotificationCenter.default.post(name: .navigateToHome, object: nil)
}
}
private func applyPromoCode(_ code: String) {
// 推广应用逻辑
print("프로모션 코드 적용: \(code)")
}
}
// 定义 Notification 名称
extension Notification.Name {
static let navigateToCampaign = Notification.Name("navigateToCampaign")
static let navigateToProduct = Notification.Name("navigateToProduct")
static let navigateToHome = Notification.Name("navigateToHome")
}2. 延迟深度链接(自动处理)
// 延迟深度链接会在 AdStageManager.initialize() 时自动处理。
// 无需另行实现!
// 初始化时会自动:
// 1. 收集设备指纹
// 2. 向服务器发起匹配请求
// 3. 若存在已保存的深度链接,则自动调用 onDeepLinkReceived
// 延迟深度链接不提供手动触发 API,在 initialize() 之后自动处理。
// 匹配结果会通过上面注册的 setDeepLinkListener 回调传递。3. 按来源处理
func handleDeepLink(_ data: DeepLinkData) {
switch data.source {
case .realtime:
print("🔗 실시간 딥링크 - 앱 실행 중 수신")
// 可立即切换页面
navigateImmediately(data)
case .install:
print("📦 디퍼드 딥링크 - 앱 설치 후 첫 실행")
// 在引导流程之后处理,或延迟处理
showOnboardingThenNavigate(data)
case .unknown:
print("❓ 알 수 없는 소스")
handleAsDefault(data)
}
}
private func showOnboardingThenNavigate(_ data: DeepLinkData) {
// 引导流程完成后处理
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
self?.navigateImmediately(data)
}
}创建深度链接
1. Builder 模式
import AdapterAdStage
class DeepLinkCreator {
// 创建简单的深度链接
func createSimpleDeepLink() {
AdStageManager.shared.createDeepLinkBuilder()
.setName("여름 프로모션")
.setDescription("2024년 여름 시즌 프로모션")
.setChannel("google-ads")
.setCampaign("summer2024")
.addParameter(key: "promo", value: "SUMMER20")
.create { [weak self] deepLinkInfo, error in
if let error = error {
print("❌ 딥링크 생성 실패: \(error.localizedDescription)")
return
}
guard let info = deepLinkInfo else {
print("❌ 딥링크 정보 없음")
return
}
print("""
✅ 딥링크 생성 완료
- Short URL: \(info.shortUrl)
- Short Path: \(info.shortPath)
- Link ID: \(info.linkId)
""")
// 分享生成的 URL
self?.shareDeepLink(info.shortUrl)
}
}
// 包含全部选项
func createFullDeepLink() {
AdStageManager.shared.createDeepLinkBuilder()
// 基本信息
.setName("겨울 프로모션")
.setDescription("2024-2025 겨울 시즌 프로모션")
.setShortPath("WINTER24") // 用户指定的路径
// 跟踪参数
.setChannel("facebook-ads")
.setSubChannel("instagram")
.setCampaign("winter2024")
.setAdGroup("fashion-lovers")
.setCreative("banner-001")
.setContent("hero-image")
.setKeyword("winter-sale")
// Android 设置
.setAndroidConfig(
packageName: "com.example.myapp",
appScheme: "myapp://promo/winter",
webUrl: "https://example.com/promo/winter"
)
// iOS 设置
.setIosConfig(
appStoreId: "123456789",
appScheme: "myapp://promo/winter",
webUrl: "https://example.com/promo/winter"
)
// 网页设置
.setWebConfig(webUrl: "https://example.com/promo/winter")
// 自定义参数
.addParameter(key: "discount", value: "30")
.addParameter(key: "promoCode", value: "WINTER30")
.addParameter(key: "validUntil", value: "2025-03-31")
.create { deepLinkInfo, error in
if let error = error {
print("❌ 에러: \(error.localizedDescription)")
return
}
guard let info = deepLinkInfo else { return }
print("✅ Short URL: \(info.shortUrl)")
}
}
// 一次性设置多个参数
func createDeepLinkWithMultipleParams() {
let parameters: [String: String] = [
"campaign": "spring2024",
"discount": "15",
"promoCode": "SPRING15",
"source": "email"
]
AdStageManager.shared.createDeepLinkBuilder()
.setName("봄 프로모션")
.setChannel("email-marketing")
.setParameters(parameters)
.create { deepLinkInfo, error in
// 处理
}
}
// 分享功能
private func shareDeepLink(_ url: String) {
DispatchQueue.main.async {
let text = """
🎁 특별 프로모션 초대!
이 링크를 통해 가입하면 5,000원 할인 쿠폰을 드립니다.
\(url)
"""
let activityVC = UIActivityViewController(
activityItems: [text],
applicationActivities: nil
)
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = windowScene.windows.first?.rootViewController {
rootVC.present(activityVC, animated: true)
}
}
}
}2. async/await 方式
class AsyncDeepLinkCreator {
func createDeepLink() async {
do {
let builder = AdStageManager.shared.createDeepLinkBuilder()
.setName("신규 가입 이벤트")
.setChannel("referral")
.setCampaign("invite-friend")
.addParameter(key: "referrer", value: "USER123")
.addParameter(key: "bonus", value: "5000")
let info = try await builder.createAsync()
print("✅ 딥링크 생성 완료: \(info.shortUrl)")
// 更新 UI
await MainActor.run {
self.updateUI(with: info.shortUrl)
}
} catch {
print("❌ 딥링크 생성 실패: \(error.localizedDescription)")
await MainActor.run {
self.showError(error)
}
}
}
@MainActor
private func updateUI(with url: String) {
// 更新 UI
}
@MainActor
private func showError(_ error: Error) {
// 显示错误
}
}3. Objective-C 兼容方式
createDeepLinkBuilder() 与 DeepLinkBuilder 的链式方法、create(completion:)
均已通过 @objc 暴露,因此在 Objective-C 中也可直接使用。
你可以编写一个简单的包装器,让回调只返回 Short URL 字符串。
// 可在 Objective-C 中使用的简单包装器
@objc class ObjCCompatibleDeepLinkCreator: NSObject {
@objc func createSimpleDeepLink(name: String, completion: @escaping (String?, Error?) -> Void) {
AdStageManager.shared.createDeepLinkBuilder()
.setName(name)
.create { info, error in
completion(info?.shortUrl, error)
}
}
@objc func createDeepLinkWithDescription(
name: String,
description: String,
completion: @escaping (String?, Error?) -> Void
) {
AdStageManager.shared.createDeepLinkBuilder()
.setName(name)
.setDescription(description)
.create { info, error in
completion(info?.shortUrl, error)
}
}
}高级功能
1. 设置全局用户属性
使用 UserAttributes 设置用户信息后,之后发送的事件会自动包含这些信息。
setUserAttributes(_:) 接收一个 UserAttributes 对象作为参数,所有字段均为可选值。
// 登录时
func onUserLogin(userProfile: UserProfile) {
let userAttributes = UserAttributes(
gender: userProfile.gender,
country: userProfile.country,
city: userProfile.city,
age: "\(userProfile.age)",
language: Locale.current.languageCode ?? "en"
)
AdStageManager.shared.setUserAttributes(userAttributes)
print("✅ 사용자 정보 설정 완료")
}
// 退出登录时(用空属性重置)
func onUserLogout() {
AdStageManager.shared.setUserAttributes(UserAttributes())
print("🚪 사용자 정보 초기화")
}参考:设备信息(平台、机型、应用版本、OS 版本、IDFV/IDFA、ATT 状态等) 由 SDK 在事件发送时自动收集并包含,因此无需另行设置。
问题排查
1. Universal Link 不生效
检查清单:
- 确认 Associated Domains 设置
- 确认已部署 apple-app-site-association 文件
- 确认使用 HTTPS
- 确认 Team ID 与 Bundle ID 一致
验证方法:
# 1. 确认文件是否可访问
curl https://go.myapp.com/.well-known/apple-app-site-association
# 2. 确认 Apple CDN 缓存
curl https://app-site-association.cdn-apple.com/a/v1/go.myapp.com调试:
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
print("""
Universal Link Debug:
- Activity Type: \(userActivity.activityType)
- Webpage URL: \(userActivity.webpageURL?.absoluteString ?? "nil")
- User Info: \(userActivity.userInfo ?? [:])
""")
return DeepLinkManager.shared.handleUniversalLink(userActivity)
}2. URL Scheme 不生效
确认事项:
func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
print("""
URL Scheme Debug:
- URL: \(url)
- Scheme: \(url.scheme ?? "nil")
- Host: \(url.host ?? "nil")
- Path: \(url.path)
- Query: \(url.query ?? "nil")
- Source App: \(options[.sourceApplication] ?? "nil")
""")
return DeepLinkManager.shared.handleDeepLink(url)
}3. 延迟深度链接不生效
原因:
- 设备指纹收集失败
- 服务器匹配失败
- 网络连接问题
确认:
// 延迟深度链接在 initialize() 之后自动处理(无手动触发 API)。
// 匹配结果会通过 setDeepLinkListener 回调传递,请在监听器中确认日志。
AdStageManager.shared.setDeepLinkListener { data in
print("✅ 디퍼드 딥링크 수신: \(data)")
}4. 应用处于后台时收不到深度链接
SceneDelegate 设置(iOS 13+):
import UIKit
import AdapterAdStage
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let _ = (scene as? UIWindowScene) else { return }
// 处理 URL Context
if let urlContext = connectionOptions.urlContexts.first {
_ = DeepLinkManager.shared.handleDeepLink(urlContext.url)
}
// 处理 User Activity
if let userActivity = connectionOptions.userActivities.first {
_ = DeepLinkManager.shared.handleUniversalLink(userActivity)
}
}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
if let url = URLContexts.first?.url {
_ = DeepLinkManager.shared.handleDeepLink(url)
}
}
func scene(
_ scene: UIScene,
continue userActivity: NSUserActivity
) {
_ = DeepLinkManager.shared.handleUniversalLink(userActivity)
}
}5. 确认构建设置
Build Settings:
- Deployment Target: iOS 15.0 以上
- Swift Language Version: 5.0 以上
- Enable Bitcode: No (Xcode 14+ 已移除)Info.plist 必需项:
<key>LSApplicationQueriesSchemes</key>
<array>
<string>myapp</string>
</array>测试方法
1. URL Scheme 测试
在 Safari 中测试:
myapp://promo/summer
myapp://promo?campaign=summer&discount=20在终端中测试:
xcrun simctl openurl booted "myapp://promo/summer"2. Universal Link 测试
在 Safari 中测试:
https://go.myapp.com/ABCDEF在「备忘录」应用中测试:
- 在「备忘录」应用中输入链接
- 长按该链接
- 选择「Open in [应用名称]」
在终端中测试:
xcrun simctl openurl booted "https://go.myapp.com/ABCDEF"3. 确认日志
// 确认深度链接接收情况
DeepLinkManager.shared.setDeepLinkListener { deepLinkData in
print("""
==========================================
딥링크 수신 테스트
==========================================
Short Path: \(deepLinkData.shortPath)
Link ID: \(deepLinkData.linkId)
Source: \(deepLinkData.source.description)
Parameters:
\(deepLinkData.parameters.map { " - \($0.key): \($0.value)" }.joined(separator: "\n"))
==========================================
""")
}
