Integrate Umeng analytics and APM
This commit is contained in:
3
Podfile
3
Podfile
@ -5,6 +5,9 @@ target 'suixinkan' do
|
|||||||
pod 'AMapLocation-NO-IDFA'
|
pod 'AMapLocation-NO-IDFA'
|
||||||
pod 'AMap3DMap-NO-IDFA'
|
pod 'AMap3DMap-NO-IDFA'
|
||||||
pod 'AMapSearch-NO-IDFA'
|
pod 'AMapSearch-NO-IDFA'
|
||||||
|
pod 'UMCommon'
|
||||||
|
pod 'UMDevice'
|
||||||
|
pod 'UMAPM'
|
||||||
end
|
end
|
||||||
|
|
||||||
target 'suixinkanTests' do
|
target 'suixinkanTests' do
|
||||||
|
|||||||
16
Podfile.lock
16
Podfile.lock
@ -6,11 +6,19 @@ PODS:
|
|||||||
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
||||||
- AMapSearch-NO-IDFA (9.8.0):
|
- AMapSearch-NO-IDFA (9.8.0):
|
||||||
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
- AMapFoundation-NO-IDFA (>= 1.9.0)
|
||||||
|
- UMAPM (2.0.7):
|
||||||
|
- UMCommon
|
||||||
|
- UMCommon (7.5.11):
|
||||||
|
- UMDevice
|
||||||
|
- UMDevice (3.6.0)
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- AMap3DMap-NO-IDFA
|
- AMap3DMap-NO-IDFA
|
||||||
- AMapLocation-NO-IDFA
|
- AMapLocation-NO-IDFA
|
||||||
- AMapSearch-NO-IDFA
|
- AMapSearch-NO-IDFA
|
||||||
|
- UMAPM
|
||||||
|
- UMCommon
|
||||||
|
- UMDevice
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
@ -18,13 +26,19 @@ SPEC REPOS:
|
|||||||
- AMapFoundation-NO-IDFA
|
- AMapFoundation-NO-IDFA
|
||||||
- AMapLocation-NO-IDFA
|
- AMapLocation-NO-IDFA
|
||||||
- AMapSearch-NO-IDFA
|
- AMapSearch-NO-IDFA
|
||||||
|
- UMAPM
|
||||||
|
- UMCommon
|
||||||
|
- UMDevice
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
AMap3DMap-NO-IDFA: f53ee0cb33db83c1a08856b5995e53a0c2096158
|
AMap3DMap-NO-IDFA: f53ee0cb33db83c1a08856b5995e53a0c2096158
|
||||||
AMapFoundation-NO-IDFA: a2e3c895398d7ee757278e1a0a8f9359da4b146e
|
AMapFoundation-NO-IDFA: a2e3c895398d7ee757278e1a0a8f9359da4b146e
|
||||||
AMapLocation-NO-IDFA: 7cd8fc837ea41edfbf4d937cd20572e277b77d18
|
AMapLocation-NO-IDFA: 7cd8fc837ea41edfbf4d937cd20572e277b77d18
|
||||||
AMapSearch-NO-IDFA: c0afd2a69a076d4228becda4401dbe4a279a03ef
|
AMapSearch-NO-IDFA: c0afd2a69a076d4228becda4401dbe4a279a03ef
|
||||||
|
UMAPM: ba9fbebe8dd2048b251c332ab0afc86e1ba5ea63
|
||||||
|
UMCommon: d652b3b372a801b36db203f5b546ab3e15676898
|
||||||
|
UMDevice: 20b7b3c37a36b2c5c7ca8b5a54386ff9ed2b3b19
|
||||||
|
|
||||||
PODFILE CHECKSUM: bf22a001ae02e51eb203f1eae5556a33b831ada7
|
PODFILE CHECKSUM: b2f49c71df3a1aca1cf08d192e467a8be772283c
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@ -364,6 +364,10 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
|
OTHER_LDFLAGS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"-ObjC",
|
||||||
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanzhixiang.suixinkan;
|
PRODUCT_BUNDLE_IDENTIFIER = com.yuanzhixiang.suixinkan;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
@ -403,6 +407,10 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
|
OTHER_LDFLAGS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"-ObjC",
|
||||||
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.yuanzhixiang.suixinkan;
|
PRODUCT_BUNDLE_IDENTIFIER = com.yuanzhixiang.suixinkan;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||||
|
|||||||
@ -13,6 +13,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
UmengBootstrap.configureIfNeeded()
|
||||||
if AppStore.shared.privacyAgreementAccepted, !AppStore.shared.token.isEmpty {
|
if AppStore.shared.privacyAgreementAccepted, !AppStore.shared.token.isEmpty {
|
||||||
AMapBootstrap.configureIfNeeded()
|
AMapBootstrap.configureIfNeeded()
|
||||||
}
|
}
|
||||||
@ -35,4 +36,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
suixinkan/Config/UmengBootstrap.swift
Normal file
51
suixinkan/Config/UmengBootstrap.swift
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// UmengBootstrap.swift
|
||||||
|
// suixinkan
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// 友盟 SDK 隐私合规初始化入口,必须在用户同意隐私政策后调用。
|
||||||
|
enum UmengBootstrap {
|
||||||
|
|
||||||
|
/// 当前进程内是否已经完成友盟 SDK 初始化。
|
||||||
|
private(set) static var isConfigured = false
|
||||||
|
|
||||||
|
private static let lock = NSLock()
|
||||||
|
private static var configureHandler: (_ appKey: String, _ channel: String) -> Void = { appKey, channel in
|
||||||
|
UMConfigure.setLogEnabled(false)
|
||||||
|
UMConfigure.initWithAppkey(appKey, channel: channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 在用户已同意 App 隐私政策后配置友盟 SDK,幂等。
|
||||||
|
@discardableResult
|
||||||
|
static func configureIfNeeded(appStore: AppStore = .shared) -> Bool {
|
||||||
|
guard appStore.privacyAgreementAccepted else { return false }
|
||||||
|
|
||||||
|
lock.lock()
|
||||||
|
defer { lock.unlock() }
|
||||||
|
guard !isConfigured else { return true }
|
||||||
|
|
||||||
|
configureHandler(UmengConfig.appKey, UmengConfig.channel)
|
||||||
|
isConfigured = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 测试专用:替换真实 SDK 初始化实现。
|
||||||
|
static func setConfigureHandlerForTesting(_ handler: @escaping (_ appKey: String, _ channel: String) -> Void) {
|
||||||
|
lock.lock()
|
||||||
|
defer { lock.unlock() }
|
||||||
|
configureHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 测试专用:重置初始化状态与默认初始化实现。
|
||||||
|
static func resetForTesting() {
|
||||||
|
lock.lock()
|
||||||
|
defer { lock.unlock() }
|
||||||
|
isConfigured = false
|
||||||
|
configureHandler = { appKey, channel in
|
||||||
|
UMConfigure.setLogEnabled(false)
|
||||||
|
UMConfigure.initWithAppkey(appKey, channel: channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
suixinkan/Config/UmengConfig.swift
Normal file
15
suixinkan/Config/UmengConfig.swift
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
//
|
||||||
|
// UmengConfig.swift
|
||||||
|
// suixinkan
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// 友盟统计与性能监控 SDK 配置。
|
||||||
|
enum UmengConfig {
|
||||||
|
/// 友盟后台申请的 iOS AppKey。
|
||||||
|
nonisolated static let appKey = "6a470b81cbfa6959516c34d0"
|
||||||
|
|
||||||
|
/// 友盟渠道标识,默认使用官方 App Store 渠道。
|
||||||
|
nonisolated static let channel = "App Store"
|
||||||
|
}
|
||||||
@ -25,6 +25,7 @@ enum AuthSessionHelper {
|
|||||||
|
|
||||||
if privacyAgreementAccepted {
|
if privacyAgreementAccepted {
|
||||||
AMapBootstrap.configureIfNeeded()
|
AMapBootstrap.configureIfNeeded()
|
||||||
|
UmengBootstrap.configureIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCenter.default.post(name: NotificationName.userDidLogin, object: nil)
|
NotificationCenter.default.post(name: NotificationName.userDidLogin, object: nil)
|
||||||
|
|||||||
@ -7,3 +7,10 @@
|
|||||||
#import <AMapLocationKit/AMapLocationKit.h>
|
#import <AMapLocationKit/AMapLocationKit.h>
|
||||||
#import <MAMapKit/MAMapKit.h>
|
#import <MAMapKit/MAMapKit.h>
|
||||||
#import <AMapSearchKit/AMapSearchKit.h>
|
#import <AMapSearchKit/AMapSearchKit.h>
|
||||||
|
#import <UMCommon/UMCommon.h>
|
||||||
|
|
||||||
|
#if __has_include(<UMAPM/UMAPM.h>)
|
||||||
|
#import <UMAPM/UMAPM.h>
|
||||||
|
#elif __has_include(<UMAPM/UMCrashConfigure.h>)
|
||||||
|
#import <UMAPM/UMCrashConfigure.h>
|
||||||
|
#endif
|
||||||
|
|||||||
54
suixinkanTests/UmengBootstrapTests.swift
Normal file
54
suixinkanTests/UmengBootstrapTests.swift
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// UmengBootstrapTests.swift
|
||||||
|
// suixinkanTests
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import suixinkan
|
||||||
|
|
||||||
|
/// 友盟 SDK 初始化 gate 测试。
|
||||||
|
final class UmengBootstrapTests: XCTestCase {
|
||||||
|
|
||||||
|
private var defaults: UserDefaults!
|
||||||
|
private var appStore: AppStore!
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
UmengBootstrap.resetForTesting()
|
||||||
|
defaults = UserDefaults(suiteName: "UmengBootstrapTests")!
|
||||||
|
defaults.removePersistentDomain(forName: "UmengBootstrapTests")
|
||||||
|
appStore = AppStore(defaults: defaults)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDown() {
|
||||||
|
UmengBootstrap.resetForTesting()
|
||||||
|
super.tearDown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigureReturnsFalseWhenPrivacyNotAccepted() {
|
||||||
|
var callCount = 0
|
||||||
|
UmengBootstrap.setConfigureHandlerForTesting { _, _ in
|
||||||
|
callCount += 1
|
||||||
|
}
|
||||||
|
appStore.privacyAgreementAccepted = false
|
||||||
|
|
||||||
|
XCTAssertFalse(UmengBootstrap.configureIfNeeded(appStore: appStore))
|
||||||
|
XCTAssertFalse(UmengBootstrap.isConfigured)
|
||||||
|
XCTAssertEqual(callCount, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testConfigureIsIdempotentWhenPrivacyAccepted() {
|
||||||
|
var calls: [(appKey: String, channel: String)] = []
|
||||||
|
UmengBootstrap.setConfigureHandlerForTesting { appKey, channel in
|
||||||
|
calls.append((appKey, channel))
|
||||||
|
}
|
||||||
|
appStore.privacyAgreementAccepted = true
|
||||||
|
|
||||||
|
XCTAssertTrue(UmengBootstrap.configureIfNeeded(appStore: appStore))
|
||||||
|
XCTAssertTrue(UmengBootstrap.isConfigured)
|
||||||
|
XCTAssertTrue(UmengBootstrap.configureIfNeeded(appStore: appStore))
|
||||||
|
XCTAssertEqual(calls.count, 1)
|
||||||
|
XCTAssertEqual(calls.first?.appKey, UmengConfig.appKey)
|
||||||
|
XCTAssertEqual(calls.first?.channel, UmengConfig.channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user