Compare commits
2 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
8163f171d2 | |
|
|
0ad881ca66 |
70
CHANGELOG.md
70
CHANGELOG.md
|
|
@ -1,3 +1,73 @@
|
||||||
|
## v0.4.3
|
||||||
|
- fix
|
||||||
|
- 事件参数值字符串中包含“‘”单引号sql报错
|
||||||
|
- feature
|
||||||
|
- 增加事件参数
|
||||||
|
- deviceInfo.appsflyerId
|
||||||
|
- 增加设置appsflyerId接口
|
||||||
|
- setAppFlyersId(_ appFlyersId: String?) -> Void
|
||||||
|
|
||||||
|
## v0.4.2
|
||||||
|
- fix
|
||||||
|
- 临时回滚 0.4.0,排查solitare collection ATP DAU下降问题。
|
||||||
|
|
||||||
|
## v0.4.1
|
||||||
|
- fix
|
||||||
|
- x.0的数值类型转换成整型问题
|
||||||
|
|
||||||
|
## v0.4.0
|
||||||
|
- feature
|
||||||
|
- 支持 AppExtension 上报打点
|
||||||
|
- 如在AppExtension 中使用,需在Podfile 中添加以下代码:
|
||||||
|
```swift
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
if target.name == 'GuruAnalyticsLib'
|
||||||
|
config.build_settings['APPLICATION_EXTENSION_API_ONLY'] = 'NO'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## v0.3.9
|
||||||
|
- feature
|
||||||
|
- 增加事件参数
|
||||||
|
- deviceInfo.guruAnalyticsVersion
|
||||||
|
- deviceInfo.gurusdkVersion
|
||||||
|
- 增加设置中台版本接口
|
||||||
|
- 初始化方法增加guruSDKVersion参数 initializeLib(..., guruSDKVersion: String)
|
||||||
|
- setGuruSDKVersion(_ version: String) -> Void
|
||||||
|
|
||||||
|
## v0.3.8.1
|
||||||
|
- fix
|
||||||
|
- x.0的数值类型转换成整型问题
|
||||||
|
|
||||||
|
## v0.3.8
|
||||||
|
- fix
|
||||||
|
- 隐私文件增加divice id声明
|
||||||
|
|
||||||
|
## v0.3.7
|
||||||
|
- feature
|
||||||
|
- 增加事件
|
||||||
|
- guru_sdk_init_start
|
||||||
|
- guru_sdk_init_complete
|
||||||
|
- session_start
|
||||||
|
- guru_engagement
|
||||||
|
- 增加事件参数
|
||||||
|
- session_number
|
||||||
|
- session_id
|
||||||
|
- deviceInfo.sdkVersion
|
||||||
|
- info.vendorId
|
||||||
|
|
||||||
|
## v0.3.6.1
|
||||||
|
- fix:
|
||||||
|
- 更新privacy manifest文件,移除tracking domains下的空条目。
|
||||||
|
|
||||||
|
## v0.3.6
|
||||||
|
- fix:
|
||||||
|
- 增加第三方依赖库版本约束
|
||||||
|
|
||||||
## v0.3.5
|
## v0.3.5
|
||||||
- 接口更新:
|
- 接口更新:
|
||||||
- 日志打包方法eventsLogsArchive废弃,使用eventsLogsDirectory获取文件夹URL
|
- 日志打包方法eventsLogsArchive废弃,使用eventsLogsDirectory获取文件夹URL
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 51;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
607FACCC1AFB9204008FA782 /* Sources */,
|
607FACCC1AFB9204008FA782 /* Sources */,
|
||||||
607FACCD1AFB9204008FA782 /* Frameworks */,
|
607FACCD1AFB9204008FA782 /* Frameworks */,
|
||||||
607FACCE1AFB9204008FA782 /* Resources */,
|
607FACCE1AFB9204008FA782 /* Resources */,
|
||||||
86E7176C5034831947DC0310 /* [CP] Embed Pods Frameworks */,
|
65438729BAC744B5EACA2945 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
|
@ -210,7 +210,7 @@
|
||||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
86E7176C5034831947DC0310 /* [CP] Embed Pods Frameworks */ = {
|
65438729BAC744B5EACA2945 /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
|
@ -368,8 +368,10 @@
|
||||||
baseConfigurationReference = D30C5441D6D5CBD750E76657 /* Pods-GuruAnalytics_Example.debug.xcconfig */;
|
baseConfigurationReference = D30C5441D6D5CBD750E76657 /* Pods-GuruAnalytics_Example.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
DEVELOPMENT_TEAM = 43U6TB4QK3;
|
DEVELOPMENT_TEAM = 69MW7VVKA9;
|
||||||
INFOPLIST_FILE = GuruAnalytics/Info.plist;
|
INFOPLIST_FILE = GuruAnalytics/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = GuruAnalytics;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|
@ -388,8 +390,10 @@
|
||||||
baseConfigurationReference = F992AC1E7C4013032773C33F /* Pods-GuruAnalytics_Example.release.xcconfig */;
|
baseConfigurationReference = F992AC1E7C4013032773C33F /* Pods-GuruAnalytics_Example.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
DEVELOPMENT_TEAM = 43U6TB4QK3;
|
DEVELOPMENT_TEAM = 69MW7VVKA9;
|
||||||
INFOPLIST_FILE = GuruAnalytics/Info.plist;
|
INFOPLIST_FILE = GuruAnalytics/Info.plist;
|
||||||
|
INFOPLIST_KEY_CFBundleDisplayName = GuruAnalytics;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 15.6;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
batchLimit: 25,
|
batchLimit: 25,
|
||||||
initializeTimeout: 5,
|
initializeTimeout: 5,
|
||||||
saasXAPPID: "test_app_id",
|
saasXAPPID: "test_app_id",
|
||||||
saasXDEVICEINFO: "appIdentifier=test.app.example;appVersion=1.0;deviceType=apple;deviceCountry=CN")
|
saasXDEVICEINFO: "appIdentifier=test.app.example;appVersion=1.0;deviceType=apple;deviceCountry=CN",
|
||||||
|
guruSDKVersion: "1.0.0")
|
||||||
GuruAnalytics.setUserID("100004")
|
GuruAnalytics.setUserID("100004")
|
||||||
GuruAnalytics.setAdjustId("xsdfal021sxasdfl")
|
GuruAnalytics.setAdjustId("xsdfal021sxasdfl")
|
||||||
GuruAnalytics.setDeviceId(UUID().uuidString)
|
GuruAnalytics.setDeviceId(UUID().uuidString)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,31 @@
|
||||||
import UIKit
|
import Foundation
|
||||||
|
|
||||||
var greeting = "Hello, playground"
|
var greeting = "Hello, playground"
|
||||||
|
|
||||||
let sss = ["1", "2", "3"].map({ "'\($0)'"
|
let sss = ["1", "2", "3"].map({ "'\($0)'"
|
||||||
}).joined(separator: ",")
|
}).joined(separator: ",")
|
||||||
print("value in (\(sss))")
|
print("value in (\(sss))")
|
||||||
|
|
||||||
|
let number = NSNumber(7.0)
|
||||||
|
let number2 = NSNumber(7)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
print("number is double: \(number is Double)")
|
||||||
|
print("number is int: \(number is Int)")
|
||||||
|
print("number value: \(number)")
|
||||||
|
print("number type: \(number.objCType)")
|
||||||
|
extension NSNumber {
|
||||||
|
var type: CFNumberType {
|
||||||
|
return CFNumberGetType(self as CFNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print("number type: \(number.type)")
|
||||||
|
print("number2 type: \(number2.type)")
|
||||||
|
|
||||||
|
let number3 = 7.0
|
||||||
|
|
||||||
|
print("number3 is Double: \(number3 is Double)")
|
||||||
|
print("number3 is Int: \(number3 is Int)")
|
||||||
|
print("number3 is NSNumber: \(number3 is NSNumber)")
|
||||||
|
|
|
||||||
7
Example/GuruAnalytics/MyPlayground.playground/playground.xcworkspace/contents.xcworkspacedata
generated
Normal file
7
Example/GuruAnalytics/MyPlayground.playground/playground.xcworkspace/contents.xcworkspacedata
generated
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
|
|
@ -23,15 +23,31 @@ class ViewController: UIViewController {
|
||||||
|
|
||||||
@IBAction func setFirebaseId(_ sender: Any) {
|
@IBAction func setFirebaseId(_ sender: Any) {
|
||||||
GuruAnalytics.setFirebaseId("2312:3XSFA0211231")
|
GuruAnalytics.setFirebaseId("2312:3XSFA0211231")
|
||||||
|
GuruAnalytics.setAppFlyersId("app_flyers_id:133323")
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func create(_ sender: Any) {
|
@IBAction func create(_ sender: Any) {
|
||||||
GuruAnalytics.setScreen("home")
|
GuruAnalytics.setScreen("home")
|
||||||
GuruAnalytics.logEvent("crate_clk_" + String(Int(Date().timeIntervalSince1970)),
|
GuruAnalytics.logEvent("crate_clk_" + String(Int(Date().timeIntervalSince1970)),
|
||||||
parameters: ["category": "category_\(Int.random(in: 0...100000))",
|
parameters: ["category": "category_\(Int.random(in: 0...100000))",
|
||||||
"int_v_test": 2147483647, "double_v_test": 200.1,
|
"int_v_test": 2147483647, "double_v_test_1": 200.1,
|
||||||
|
"double_v_test_2": NSNumber(7.00),
|
||||||
|
"double_v_test_3": 7.00,
|
||||||
"string_v_test": "400",
|
"string_v_test": "400",
|
||||||
"long_v_test": Int64(1)])
|
"long_v_test": Int64(1),
|
||||||
|
"long_v_test2": Int64.max,
|
||||||
|
"long_v_test3": Int64.min,
|
||||||
|
"value": 0.0])
|
||||||
|
GuruAnalytics.logEvent("spend_virtual_currency",
|
||||||
|
parameters: ["theme_name": "theme_bg_0029",
|
||||||
|
"theme_category": "theme",
|
||||||
|
"virtural_currency_name": "coins",
|
||||||
|
"value": 100,
|
||||||
|
"balance": 33810,
|
||||||
|
"scene": "theme",
|
||||||
|
"level_name": "Instance of I'm 'SettingIntDat'",
|
||||||
|
])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func deleteItem(_ sender: Any) {
|
@IBAction func deleteItem(_ sender: Any) {
|
||||||
|
|
@ -39,7 +55,7 @@ class ViewController: UIViewController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func getLogs(_ sender: UIButton) {
|
@IBAction func getLogs(_ sender: UIButton) {
|
||||||
GuruAnalytics.eventsLogsArchive({ [weak self] url in
|
GuruAnalytics.eventsLogsDirectory({ [weak self] url in
|
||||||
guard let `self` = self, let url = url else { return }
|
guard let `self` = self, let url = url else { return }
|
||||||
|
|
||||||
if MFMailComposeViewController.canSendMail() {
|
if MFMailComposeViewController.canSendMail() {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ platform :ios, '11.0'
|
||||||
|
|
||||||
target 'GuruAnalytics_Example' do
|
target 'GuruAnalytics_Example' do
|
||||||
pod 'GuruAnalyticsLib', :path => '../'
|
pod 'GuruAnalyticsLib', :path => '../'
|
||||||
# pod 'GuruAnalyticsLib', '0.3.4'
|
# pod 'GuruAnalyticsLib', '0.4.2'
|
||||||
end
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,21 @@
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>NSPrivacyCollectedDataTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyCollectedDataType</key>
|
||||||
|
<string>NSPrivacyCollectedDataTypeDeviceID</string>
|
||||||
|
<key>NSPrivacyCollectedDataTypeLinked</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSPrivacyCollectedDataTypeTracking</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSPrivacyCollectedDataTypePurposes</key>
|
||||||
|
<array>
|
||||||
|
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
<key>NSPrivacyAccessedAPITypes</key>
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
|
@ -34,8 +49,6 @@
|
||||||
<key>NSPrivacyTracking</key>
|
<key>NSPrivacyTracking</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>NSPrivacyTrackingDomains</key>
|
<key>NSPrivacyTrackingDomains</key>
|
||||||
<array>
|
<array/>
|
||||||
<string></string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ public class GuruAnalytics: NSObject {
|
||||||
initializeTimeout: Double = 5,
|
initializeTimeout: Double = 5,
|
||||||
saasXAPPID: String,
|
saasXAPPID: String,
|
||||||
saasXDEVICEINFO: String,
|
saasXDEVICEINFO: String,
|
||||||
loggerDebug: Bool = true) {
|
loggerDebug: Bool = true,
|
||||||
|
guruSDKVersion: String) {
|
||||||
Self.uploadPeriodInSecond = uploadPeriodInSecond
|
Self.uploadPeriodInSecond = uploadPeriodInSecond
|
||||||
Self.batchLimit = batchLimit
|
Self.batchLimit = batchLimit
|
||||||
Self.eventExpiredSeconds = eventExpiredSeconds
|
Self.eventExpiredSeconds = eventExpiredSeconds
|
||||||
|
|
@ -42,6 +43,7 @@ public class GuruAnalytics: NSObject {
|
||||||
Self.saasXAPPID = saasXAPPID
|
Self.saasXAPPID = saasXAPPID
|
||||||
Self.saasXDEVICEINFO = saasXDEVICEINFO
|
Self.saasXDEVICEINFO = saasXDEVICEINFO
|
||||||
Self.loggerDebug = loggerDebug
|
Self.loggerDebug = loggerDebug
|
||||||
|
Constants.guruSDKVersion = guruSDKVersion
|
||||||
_ = Manager.shared
|
_ = Manager.shared
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,6 +83,12 @@ public class GuruAnalytics: NSObject {
|
||||||
setUserProperty(firebaseId, forName: .firebaseId)
|
setUserProperty(firebaseId, forName: .firebaseId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 设置appsflyerId
|
||||||
|
@objc
|
||||||
|
public class func setAppFlyersId(_ appFlyersId: String?) {
|
||||||
|
setUserProperty(appFlyersId, forName: .appsflyerId)
|
||||||
|
}
|
||||||
|
|
||||||
/// screen name
|
/// screen name
|
||||||
@objc
|
@objc
|
||||||
public class func setScreen(_ name: String) {
|
public class func setScreen(_ name: String) {
|
||||||
|
|
@ -155,4 +163,9 @@ public class GuruAnalytics: NSObject {
|
||||||
enableUpload = isOn
|
enableUpload = isOn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 设置中台库版本
|
||||||
|
@objc
|
||||||
|
public class func setGuruSDKVersion(_ version: String) -> Void {
|
||||||
|
Constants.guruSDKVersion = version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -124,8 +124,16 @@ extension Entity {
|
||||||
|
|
||||||
static func normalizeParameters(_ parameters: [String : Any]) -> [String : EventValue] {
|
static func normalizeParameters(_ parameters: [String : Any]) -> [String : EventValue] {
|
||||||
var params = [String : EventValue]()
|
var params = [String : EventValue]()
|
||||||
|
var allParams = parameters;
|
||||||
|
|
||||||
|
GuruAnalytics.BuiltinParametersKeys.allCases.forEach { paramKey in
|
||||||
|
if let value = allParams.removeValue(forKey: paramKey.rawValue) {
|
||||||
|
params[paramKey.rawValue] = normalizeValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var count = 0
|
var count = 0
|
||||||
parameters.sorted(by: { $0.key < $1.key }).forEach({ key, value in
|
allParams.sorted(by: { $0.key < $1.key }).forEach({ key, value in
|
||||||
|
|
||||||
guard count < maxParametersCount else {
|
guard count < maxParametersCount else {
|
||||||
cdPrint("too many parameters")
|
cdPrint("too many parameters")
|
||||||
|
|
@ -140,17 +148,7 @@ extension Entity {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if let value = value as? String {
|
params[normalizedKey] = normalizeValue(value)
|
||||||
params[normalizedKey] = Entity.EventValue(stringValue: String(value.prefix(maxParameterStringValueLength)))
|
|
||||||
} else if let value = value as? Int {
|
|
||||||
params[normalizedKey] = Entity.EventValue(longValue: Int64(value))
|
|
||||||
} else if let value = value as? Int64 {
|
|
||||||
params[normalizedKey] = Entity.EventValue(longValue: value)
|
|
||||||
} else if let value = value as? Double {
|
|
||||||
params[normalizedKey] = Entity.EventValue(doubleValue: value)
|
|
||||||
} else {
|
|
||||||
params[normalizedKey] = Entity.EventValue(stringValue: String("\(value)".prefix(maxParameterStringValueLength)))
|
|
||||||
}
|
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
})
|
})
|
||||||
|
|
@ -158,6 +156,36 @@ extension Entity {
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func normalizeValue(_ value: Any) -> EventValue {
|
||||||
|
|
||||||
|
var preprocessedValue = value
|
||||||
|
|
||||||
|
if let val = preprocessedValue as? NSNumber {
|
||||||
|
preprocessedValue = val.numricValue
|
||||||
|
}
|
||||||
|
|
||||||
|
let eventValue: EventValue
|
||||||
|
if let value = preprocessedValue as? String {
|
||||||
|
eventValue = Entity.EventValue(stringValue: normalizeStringValue(String(value.prefix(maxParameterStringValueLength))))
|
||||||
|
} else if let value = preprocessedValue as? Int {
|
||||||
|
eventValue = Entity.EventValue(longValue: Int64(value))
|
||||||
|
} else if let value = preprocessedValue as? Int64 {
|
||||||
|
eventValue = Entity.EventValue(longValue: value)
|
||||||
|
} else if let value = preprocessedValue as? Double {
|
||||||
|
eventValue = Entity.EventValue(doubleValue: value)
|
||||||
|
} else {
|
||||||
|
eventValue = Entity.EventValue(stringValue: normalizeStringValue(String("\(value)".prefix(maxParameterStringValueLength))))
|
||||||
|
}
|
||||||
|
return eventValue
|
||||||
|
}
|
||||||
|
|
||||||
|
static func normalizeStringValue(_ value: String) -> String {
|
||||||
|
let normalizedString = value.replacingOccurrences(of: "'", with: "''")
|
||||||
|
cdPrint("original string value: \(value)")
|
||||||
|
cdPrint("normalized string value: \(normalizedString)")
|
||||||
|
return normalizedString
|
||||||
|
}
|
||||||
|
|
||||||
static func normalizeKey(_ key: String) -> String? {
|
static func normalizeKey(_ key: String) -> String? {
|
||||||
var mutableKey = key
|
var mutableKey = key
|
||||||
|
|
||||||
|
|
@ -191,6 +219,11 @@ extension Entity {
|
||||||
let adId: String?
|
let adId: String?
|
||||||
///用户的pseudo_id
|
///用户的pseudo_id
|
||||||
let firebaseId: String?
|
let firebaseId: String?
|
||||||
|
///appsFlyerId
|
||||||
|
let appsflyerId: String?
|
||||||
|
|
||||||
|
///IDFV
|
||||||
|
let vendorId: String? = UIDevice().identifierForVendor?.uuidString
|
||||||
|
|
||||||
enum CodingKeys: String, CodingKey {
|
enum CodingKeys: String, CodingKey {
|
||||||
case deviceId
|
case deviceId
|
||||||
|
|
@ -198,6 +231,8 @@ extension Entity {
|
||||||
case adjustId
|
case adjustId
|
||||||
case adId
|
case adId
|
||||||
case firebaseId
|
case firebaseId
|
||||||
|
case vendorId
|
||||||
|
case appsflyerId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -226,3 +261,23 @@ extension Entity {
|
||||||
let data: Int64
|
let data: Int64
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Entity {
|
||||||
|
struct Session {
|
||||||
|
let id: UUID = UUID()
|
||||||
|
|
||||||
|
var sessionId: Int {
|
||||||
|
return id.uuidString.hash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SessionNumber: Codable {
|
||||||
|
var number: Int
|
||||||
|
let createdAtMs: Int64
|
||||||
|
|
||||||
|
static func createNumber() -> SessionNumber {
|
||||||
|
return SessionNumber(number: 0, createdAtMs: Date().msSince1970)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,40 @@ WHERE \(Entity.EventRecord.CodingKeys.timestamp.rawValue) < \(earlierThan)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchOutdatedEventRecords(earlierThan: Int64) -> Single<[Entity.EventRecord]> {
|
||||||
|
return mapTransactionToSingle { (db) in
|
||||||
|
let querySQL: String =
|
||||||
|
"""
|
||||||
|
SELECT * FROM \(TableName.event.rawValue)
|
||||||
|
WHERE \(Entity.EventRecord.CodingKeys.timestamp.rawValue) < \(earlierThan)
|
||||||
|
"""
|
||||||
|
cdPrint(#function + "query sql: \(querySQL)")
|
||||||
|
let results = try db.executeQuery(querySQL, values: nil) //[ASC | DESC]
|
||||||
|
var t: [Entity.EventRecord] = []
|
||||||
|
while results.next() {
|
||||||
|
guard let recordId = results.string(forColumnIndex: 0),
|
||||||
|
let eventName = results.string(forColumnIndex: 1),
|
||||||
|
let eventJson = results.string(forColumnIndex: 2) else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let priority: Int = results.columnIsNull(Entity.EventRecord.CodingKeys.priority.rawValue) ?
|
||||||
|
Entity.EventRecord.Priority.DEFAULT.rawValue : Int(results.int(forColumn: Entity.EventRecord.CodingKeys.priority.rawValue))
|
||||||
|
|
||||||
|
let ts: Int = results.columnIsNull(Entity.EventRecord.CodingKeys.transitionStatus.rawValue) ?
|
||||||
|
Entity.EventRecord.TransitionStatus.idle.rawValue : Int(results.int(forColumn: Entity.EventRecord.CodingKeys.transitionStatus.rawValue))
|
||||||
|
|
||||||
|
let record = Entity.EventRecord(recordId: recordId, eventName: eventName, eventJson: eventJson,
|
||||||
|
timestamp: results.longLongInt(forColumn: Entity.EventRecord.CodingKeys.timestamp.rawValue),
|
||||||
|
priority: priority, transitionStatus: ts)
|
||||||
|
t.append(record)
|
||||||
|
}
|
||||||
|
|
||||||
|
results.close()
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func resetTransitionStatus(for recordIds: [String]) -> Single<Void> {
|
func resetTransitionStatus(for recordIds: [String]) -> Single<Void> {
|
||||||
guard !recordIds.isEmpty else {
|
guard !recordIds.isEmpty else {
|
||||||
return .just(())
|
return .just(())
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ internal class Manager {
|
||||||
// MARK: - private members
|
// MARK: - private members
|
||||||
|
|
||||||
private typealias PropertyName = GuruAnalytics.PropertyName
|
private typealias PropertyName = GuruAnalytics.PropertyName
|
||||||
|
private typealias BuiltinParametersKeys = GuruAnalytics.BuiltinParametersKeys
|
||||||
|
|
||||||
private let bag = DisposeBag()
|
private let bag = DisposeBag()
|
||||||
|
|
||||||
|
|
@ -170,8 +171,23 @@ internal class Manager {
|
||||||
private typealias InternalEventReporter = ((_ eventCode: Int, _ info: String) -> Void)
|
private typealias InternalEventReporter = ((_ eventCode: Int, _ info: String) -> Void)
|
||||||
private var internalEventReporter: InternalEventReporter?
|
private var internalEventReporter: InternalEventReporter?
|
||||||
|
|
||||||
|
private lazy var session = Entity.Session()
|
||||||
|
private lazy var sessionNumber: Entity.SessionNumber = {
|
||||||
|
var sessionNumber = UserDefaults.sessionNumber
|
||||||
|
let dayGaps = Calendar.current.dateComponents([.day], from: Date(timeIntervalSince1970: Double(sessionNumber.createdAtMs) / 1000), to: Date()).day ?? 0
|
||||||
|
if (dayGaps > 0) {
|
||||||
|
sessionNumber = Entity.SessionNumber.createNumber()
|
||||||
|
}
|
||||||
|
sessionNumber.number += 1
|
||||||
|
UserDefaults.sessionNumber = sessionNumber
|
||||||
|
return sessionNumber
|
||||||
|
}()
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
|
|
||||||
|
//
|
||||||
|
logSDKInitStart()
|
||||||
|
|
||||||
// first open
|
// first open
|
||||||
logFirstOpenIfNeeded()
|
logFirstOpenIfNeeded()
|
||||||
|
|
||||||
|
|
@ -188,6 +204,12 @@ internal class Manager {
|
||||||
logFirstFgEvent()
|
logFirstFgEvent()
|
||||||
|
|
||||||
ntwkMgr.networkErrorReporter = self
|
ntwkMgr.networkErrorReporter = self
|
||||||
|
|
||||||
|
//
|
||||||
|
logSDKInitComplete()
|
||||||
|
|
||||||
|
//
|
||||||
|
logSessionStart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,7 +251,7 @@ internal extension Manager {
|
||||||
priority: Entity.EventRecord.Priority) -> Single<Entity.EventRecord> {
|
priority: Entity.EventRecord.Priority) -> Single<Entity.EventRecord> {
|
||||||
|
|
||||||
return userProperty.take(1).observe(on: rxWorkScheduler).asSingle().flatMap { p in
|
return userProperty.take(1).observe(on: rxWorkScheduler).asSingle().flatMap { p in
|
||||||
.create { subscriber in
|
.create { [weak self] subscriber in
|
||||||
do {
|
do {
|
||||||
debugPrint("userProperty thread queueName: \(Thread.current.queueName) count: \(p.count)")
|
debugPrint("userProperty thread queueName: \(Thread.current.queueName) count: \(p.count)")
|
||||||
var userProperty = p
|
var userProperty = p
|
||||||
|
|
@ -240,12 +262,16 @@ internal extension Manager {
|
||||||
eventParam[PropertyName.screen.rawValue] = screen
|
eventParam[PropertyName.screen.rawValue] = screen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eventParam[BuiltinParametersKeys.sessionId.rawValue] = self?.session.sessionId
|
||||||
|
eventParam[BuiltinParametersKeys.sessionNo.rawValue] = self?.sessionNumber.number
|
||||||
|
|
||||||
let userInfo = Entity.UserInfo(
|
let userInfo = Entity.UserInfo(
|
||||||
uid: userProperty.removeValue(forKey: PropertyName.uid.rawValue),
|
uid: userProperty.removeValue(forKey: PropertyName.uid.rawValue),
|
||||||
deviceId: userProperty.removeValue(forKey: PropertyName.deviceId.rawValue),
|
deviceId: userProperty.removeValue(forKey: PropertyName.deviceId.rawValue),
|
||||||
adjustId: userProperty.removeValue(forKey: PropertyName.adjustId.rawValue),
|
adjustId: userProperty.removeValue(forKey: PropertyName.adjustId.rawValue),
|
||||||
adId: userProperty.removeValue(forKey: PropertyName.adId.rawValue),
|
adId: userProperty.removeValue(forKey: PropertyName.adId.rawValue),
|
||||||
firebaseId: userProperty.removeValue(forKey: PropertyName.firebaseId.rawValue)
|
firebaseId: userProperty.removeValue(forKey: PropertyName.firebaseId.rawValue),
|
||||||
|
appsflyerId: userProperty.removeValue(forKey: PropertyName.appsflyerId.rawValue)
|
||||||
)
|
)
|
||||||
|
|
||||||
let event = try Entity.Event(timestamp: timestamp,
|
let event = try Entity.Event(timestamp: timestamp,
|
||||||
|
|
@ -361,19 +387,33 @@ private extension Manager {
|
||||||
|
|
||||||
/// 1. 删除过期的数据
|
/// 1. 删除过期的数据
|
||||||
serverNowMsSingle
|
serverNowMsSingle
|
||||||
.flatMap({ [weak self] serverNowMs -> Single<Void> in
|
.flatMap({ [weak self] serverNowMs -> Single<[Entity.EventRecord]> in
|
||||||
guard let `self` = self else { return .just(()) }
|
guard let `self` = self else {
|
||||||
let earlierThan: Int64 = serverNowMs - self.eventExpiredIntervel.int64Ms
|
return .error(NSError(domain: "com.guru.analytics.manager",
|
||||||
return self.db.removeOutdatedEventRecords(earlierThan: earlierThan)
|
code: 0,
|
||||||
})
|
userInfo: [NSLocalizedDescriptionKey : "Manager released"]
|
||||||
.catch({ error in
|
))
|
||||||
cdPrint("remove outdated records error: \(error)")
|
}
|
||||||
return .just(())
|
let earlierThan: Int64 = serverNowMs - self.eventExpiredIntervel.int64Ms
|
||||||
})
|
return self.db.fetchOutdatedEventRecords(earlierThan: earlierThan)
|
||||||
.subscribe(onSuccess: { [weak self] _ in
|
})
|
||||||
self?.outdatedEventsCleared.onNext(true)
|
.flatMap({ [weak self] records in
|
||||||
})
|
return self?.db.deleteEventRecords(records.map { $0.recordId })
|
||||||
.disposed(by: bag)
|
.map { _ in records.count } ??
|
||||||
|
.error(NSError(domain: "com.guru.analytics.manager",
|
||||||
|
code: 0,
|
||||||
|
userInfo: [NSLocalizedDescriptionKey : "Manager released"]
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.catch({ error in
|
||||||
|
cdPrint("remove outdated records error: \(error)")
|
||||||
|
return .just(0)
|
||||||
|
})
|
||||||
|
.subscribe(onSuccess: { [weak self] deletedCount in
|
||||||
|
UserDefaults.deletedEventsCount += deletedCount
|
||||||
|
self?.outdatedEventsCleared.onNext(true)
|
||||||
|
})
|
||||||
|
.disposed(by: bag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func logFirstOpenIfNeeded() {
|
func logFirstOpenIfNeeded() {
|
||||||
|
|
@ -417,6 +457,7 @@ private extension Manager {
|
||||||
.flatMap { self.db.addEventRecords($0) }
|
.flatMap { self.db.addEventRecords($0) }
|
||||||
.do(onSuccess: { _ in
|
.do(onSuccess: { _ in
|
||||||
self.accumulateLoggedEventsCount(1)
|
self.accumulateLoggedEventsCount(1)
|
||||||
|
UserDefaults.totalEventsCount += 1
|
||||||
self.eventsLogger.verbose("log event success")
|
self.eventsLogger.verbose("log event success")
|
||||||
}, onError: { error in
|
}, onError: { error in
|
||||||
self.eventsLogger.error("log event error: \(error)")
|
self.eventsLogger.error("log event error: \(error)")
|
||||||
|
|
@ -424,6 +465,28 @@ private extension Manager {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func logSDKInitStart() {
|
||||||
|
_logEvent(GuruAnalytics.sdkInitStartEvent.name, parameters: [
|
||||||
|
GuruAnalytics.sdkInitStartEvent.paramKeyType.totalEvents.rawValue : UserDefaults.totalEventsCount,
|
||||||
|
GuruAnalytics.sdkInitStartEvent.paramKeyType.deletedEvents.rawValue : UserDefaults.deletedEventsCount,
|
||||||
|
GuruAnalytics.sdkInitStartEvent.paramKeyType.uploadedEvents.rawValue : UserDefaults.uploadedEventsCount,
|
||||||
|
], priority: .HIGH)
|
||||||
|
.subscribe()
|
||||||
|
.disposed(by: bag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logSDKInitComplete() {
|
||||||
|
_logEvent(GuruAnalytics.sdkInitCompleteEvent.name, parameters: [
|
||||||
|
GuruAnalytics.sdkInitCompleteEvent.paramKeyType.duration.rawValue : Date().msSince1970 - startAt.msSince1970,
|
||||||
|
], priority: .HIGH)
|
||||||
|
.subscribe()
|
||||||
|
.disposed(by: bag)
|
||||||
|
}
|
||||||
|
func logSessionStart() {
|
||||||
|
_logEvent(GuruAnalytics.sessionStartEvent.name, parameters: nil, priority: .HIGH)
|
||||||
|
.subscribe()
|
||||||
|
.disposed(by: bag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 轮询上传相关
|
// MARK: - 轮询上传相关
|
||||||
|
|
@ -536,6 +599,7 @@ private extension Manager {
|
||||||
}
|
}
|
||||||
.flatMap { recordIDs -> Single<Void> in
|
.flatMap { recordIDs -> Single<Void> in
|
||||||
self.accumulateUploadedEventsCount(recordIDs.count)
|
self.accumulateUploadedEventsCount(recordIDs.count)
|
||||||
|
UserDefaults.uploadedEventsCount += recordIDs.count
|
||||||
/// step4: 删除数据库中对应记录
|
/// step4: 删除数据库中对应记录
|
||||||
return self.db.deleteEventRecords(recordIDs)
|
return self.db.deleteEventRecords(recordIDs)
|
||||||
.catch { error in
|
.catch { error in
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,48 @@ internal enum UserDefaults {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var totalEventsCount: Int {
|
||||||
|
get {
|
||||||
|
return defaults?.value(forKey: totalEventsCountKey) as? Int ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
set {
|
||||||
|
defaults?.set(newValue, forKey: totalEventsCountKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var deletedEventsCount: Int {
|
||||||
|
get {
|
||||||
|
return defaults?.value(forKey: deletedEventsCountKey) as? Int ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
set {
|
||||||
|
defaults?.set(newValue, forKey: deletedEventsCountKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var uploadedEventsCount: Int {
|
||||||
|
get {
|
||||||
|
return defaults?.value(forKey: uploadedEventsCountKey) as? Int ?? 0
|
||||||
|
}
|
||||||
|
|
||||||
|
set {
|
||||||
|
defaults?.set(newValue, forKey: uploadedEventsCountKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static var sessionNumber: Entity.SessionNumber {
|
||||||
|
get {
|
||||||
|
let jsonString = defaults?.value(forKey: sessionNumberKey) as? String ?? ""
|
||||||
|
let sessionNumber = JSONDecoder().decodeObject(Entity.SessionNumber.self, from: jsonString)
|
||||||
|
?? Entity.SessionNumber.createNumber()
|
||||||
|
return sessionNumber
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
let jsonString = newValue.asString
|
||||||
|
defaults?.setValue(jsonString, forKey: sessionNumberKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UserDefaults {
|
extension UserDefaults {
|
||||||
|
|
@ -62,4 +104,21 @@ extension UserDefaults {
|
||||||
static var fgDurationKey: String {
|
static var fgDurationKey: String {
|
||||||
return "fg.duration.ms"
|
return "fg.duration.ms"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static var totalEventsCountKey: String {
|
||||||
|
return "events.recorded.total.count"
|
||||||
|
}
|
||||||
|
|
||||||
|
static var deletedEventsCountKey: String {
|
||||||
|
return "events.deleted.count"
|
||||||
|
}
|
||||||
|
|
||||||
|
static var uploadedEventsCountKey: String {
|
||||||
|
return "events.uploaded.count"
|
||||||
|
}
|
||||||
|
|
||||||
|
static var sessionNumberKey: String {
|
||||||
|
return "session.number"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,12 @@ internal extension GuruAnalytics {
|
||||||
case firebaseId
|
case firebaseId
|
||||||
case screen = "screen_name"
|
case screen = "screen_name"
|
||||||
case firstOpenTime = "first_open_time"
|
case firstOpenTime = "first_open_time"
|
||||||
|
case appsflyerId
|
||||||
}
|
}
|
||||||
|
|
||||||
///built-in events
|
///built-in events
|
||||||
static let fgEvent: EventProto = {
|
static let fgEvent: EventProto = {
|
||||||
var e = EventProto(paramKeyType: FgEventParametersKeys.self, name: "fg")
|
var e = EventProto(paramKeyType: FgEventParametersKeys.self, name: "guru_engagement")
|
||||||
return e
|
return e
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -31,6 +32,21 @@ internal extension GuruAnalytics {
|
||||||
return e
|
return e
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
static let sdkInitStartEvent: EventProto = {
|
||||||
|
var e = EventProto(paramKeyType: SDKEventParametersKeys.self, name: "guru_sdk_init_start")
|
||||||
|
return e
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let sdkInitCompleteEvent: EventProto = {
|
||||||
|
var e = EventProto(paramKeyType: SDKEventParametersKeys.self, name: "guru_sdk_init_complete")
|
||||||
|
return e
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let sessionStartEvent: EventProto = {
|
||||||
|
var e = EventProto(paramKeyType: DefaultEventParametersKeys.self, name: "session_start")
|
||||||
|
return e
|
||||||
|
}()
|
||||||
|
|
||||||
class func setUserProperty(_ value: String?, forName name: PropertyName) {
|
class func setUserProperty(_ value: String?, forName name: PropertyName) {
|
||||||
setUserProperty(value, forName: name.rawValue)
|
setUserProperty(value, forName: name.rawValue)
|
||||||
}
|
}
|
||||||
|
|
@ -48,4 +64,23 @@ internal extension GuruAnalytics {
|
||||||
case duration
|
case duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum SDKEventParametersKeys: String {
|
||||||
|
case totalEvents = "total_events"
|
||||||
|
case deletedEvents = "deleted_events"
|
||||||
|
case uploadedEvents = "uploaded_events"
|
||||||
|
case duration
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DefaultEventParametersKeys {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal extension GuruAnalytics {
|
||||||
|
|
||||||
|
///built-in event parameters
|
||||||
|
enum BuiltinParametersKeys: String, CaseIterable {
|
||||||
|
case screenName = "screen_name"
|
||||||
|
case sessionNo = "session_number"
|
||||||
|
case sessionId = "session_id"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,17 @@ internal struct Constants {
|
||||||
return shortVersion
|
return shortVersion
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
private static let guruAnalyticsSDKVersion: String = {
|
||||||
|
guard let infoDict = Bundle(for: Manager.self).infoDictionary,
|
||||||
|
let currentVersion = infoDict["CFBundleShortVersionString"] as? String else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return currentVersion
|
||||||
|
}()
|
||||||
|
|
||||||
|
/// 中台库版本,由外部更新
|
||||||
|
static var guruSDKVersion: String = "";
|
||||||
|
|
||||||
private static let preferredLocale: Locale = {
|
private static let preferredLocale: Locale = {
|
||||||
guard let preferredIdentifier = Locale.preferredLanguages.first else {
|
guard let preferredIdentifier = Locale.preferredLanguages.first else {
|
||||||
return Locale.current
|
return Locale.current
|
||||||
|
|
@ -83,7 +94,9 @@ internal struct Constants {
|
||||||
"screenH": Int(screenSize.h),
|
"screenH": Int(screenSize.h),
|
||||||
"screenW": Int(screenSize.w),
|
"screenW": Int(screenSize.w),
|
||||||
"osVersion": systemVersion,
|
"osVersion": systemVersion,
|
||||||
"language" : languageCode
|
"language" : languageCode,
|
||||||
|
"guruAnalyticsVersion" : guruAnalyticsSDKVersion,
|
||||||
|
"gurusdkVersion" : guruSDKVersion,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +106,50 @@ internal struct Constants {
|
||||||
/// - returns: raw `String` of device type, e.g. iPhone5,1
|
/// - returns: raw `String` of device type, e.g. iPhone5,1
|
||||||
///
|
///
|
||||||
private static func hardwareString() -> String {
|
private static func hardwareString() -> String {
|
||||||
|
var name: [Int32] = [CTL_HW, HW_MACHINE]
|
||||||
|
var size: size_t = 0
|
||||||
|
|
||||||
|
// 🛡️ 安全检查1:获取缓冲区大小
|
||||||
|
guard sysctl(&name, 2, nil, &size, nil, 0) == 0,
|
||||||
|
size > 0 && size < 256 else {
|
||||||
|
return "iPhone14,1" // 安全默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🛡️ 安全检查2:创建缓冲区
|
||||||
|
let bufferSize = Int(size) + 1
|
||||||
|
var hw_machine = [CChar](repeating: 0, count: bufferSize)
|
||||||
|
var actualSize = size
|
||||||
|
|
||||||
|
// 🛡️ 安全检查3:获取数据
|
||||||
|
guard sysctl(&name, 2, &hw_machine, &actualSize, nil, 0) == 0 else {
|
||||||
|
return "iPhone14,1" // 安全默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🛡️ 安全检查4:确保null终止(防止越界)
|
||||||
|
let safeIndex = min(Int(actualSize), bufferSize - 1)
|
||||||
|
hw_machine[safeIndex] = 0
|
||||||
|
|
||||||
|
var hardware: String = String(cString: hw_machine)
|
||||||
|
|
||||||
|
// 🛡️ 安全检查5:验证结果
|
||||||
|
if hardware.isEmpty {
|
||||||
|
return "iPhone14,1" // 安全默认值
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for simulator
|
||||||
|
if hardware == "x86_64" || hardware == "i386" || hardware == "arm64" {
|
||||||
|
if let deviceID = ProcessInfo.processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] {
|
||||||
|
hardware = deviceID
|
||||||
|
} else {
|
||||||
|
hardware = "Simulator"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hardware
|
||||||
|
}
|
||||||
|
|
||||||
|
//这里不可用,有崩溃!!!用上边的方法!!
|
||||||
|
private static func hardwareStringError() -> String {
|
||||||
var name: [Int32] = [CTL_HW, HW_MACHINE]
|
var name: [Int32] = [CTL_HW, HW_MACHINE]
|
||||||
var size: Int = 2
|
var size: Int = 2
|
||||||
sysctl(&name, 2, nil, &size, nil, 0)
|
sysctl(&name, 2, nil, &size, nil, 0)
|
||||||
|
|
@ -107,10 +164,12 @@ internal struct Constants {
|
||||||
hardware = deviceID
|
hardware = deviceID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hardware
|
return hardware
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// This method returns the Platform enum depending upon harware string
|
/// This method returns the Platform enum depending upon harware string
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
//
|
||||||
|
// Untitled.swift
|
||||||
|
// Pods
|
||||||
|
//
|
||||||
|
// Created by mayue on 2025/1/14.
|
||||||
|
//
|
||||||
|
|
||||||
|
extension NSNumber {
|
||||||
|
var valueType: CFNumberType {
|
||||||
|
return CFNumberGetType(self as CFNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
var numricValue: Any {
|
||||||
|
switch valueType {
|
||||||
|
case .sInt8Type,
|
||||||
|
.sInt16Type,
|
||||||
|
.sInt32Type,
|
||||||
|
.charType,
|
||||||
|
.shortType,
|
||||||
|
.intType,
|
||||||
|
.longType,
|
||||||
|
.cfIndexType,
|
||||||
|
.nsIntegerType:
|
||||||
|
return intValue;
|
||||||
|
|
||||||
|
case
|
||||||
|
.sInt64Type,
|
||||||
|
.longLongType:
|
||||||
|
return int64Value;
|
||||||
|
|
||||||
|
case .float32Type,
|
||||||
|
.float64Type,
|
||||||
|
.floatType,
|
||||||
|
.doubleType,
|
||||||
|
.cgFloatType,
|
||||||
|
.maxType:
|
||||||
|
return doubleValue;
|
||||||
|
|
||||||
|
@unknown default:
|
||||||
|
return doubleValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,4 +25,38 @@ internal extension JSONDecoder {
|
||||||
}
|
}
|
||||||
return try decode(type, from: unwrappedData)
|
return try decode(type, from: unwrappedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func decodeObject<T>(_ type: T.Type, from data: Data) -> T? where T: Decodable {
|
||||||
|
|
||||||
|
guard data.count > 0 else { return nil }
|
||||||
|
|
||||||
|
var object: T? = nil
|
||||||
|
|
||||||
|
do {
|
||||||
|
object = try decode(type, from: data)
|
||||||
|
} catch {
|
||||||
|
cdPrint("JSONDecoder decode error: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return object
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeObject<T>(_ type: T.Type, from jsonString: String) -> T? where T: Decodable {
|
||||||
|
guard let jsonData = jsonString.data(using: .utf8) else { return nil }
|
||||||
|
return decodeObject(type, from: jsonData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeObject<T>(_ type: T.Type, from dictionary: [String : Any]) -> T? where T: Decodable {
|
||||||
|
|
||||||
|
var data: Data?
|
||||||
|
|
||||||
|
do {
|
||||||
|
data = try JSONSerialization.data(withJSONObject: dictionary, options: .prettyPrinted)
|
||||||
|
} catch let error {
|
||||||
|
cdPrint(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let jsonData = data else { return nil }
|
||||||
|
return decodeObject(type, from: jsonData)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
Pod::Spec.new do |s|
|
Pod::Spec.new do |s|
|
||||||
s.name = 'GuruAnalyticsLib'
|
s.name = 'GuruAnalyticsLib'
|
||||||
s.version = '0.3.5'
|
s.version = '0.4.4'
|
||||||
s.summary = 'A short description of GuruAnalytics.'
|
s.summary = 'A short description of GuruAnalytics.'
|
||||||
|
|
||||||
# This description is used to generate tags and improve search results.
|
# This description is used to generate tags and improve search results.
|
||||||
|
|
@ -38,12 +38,12 @@ TODO: Add long description of the pod here.
|
||||||
# s.public_header_files = 'Pod/Classes/**/*.h'
|
# s.public_header_files = 'Pod/Classes/**/*.h'
|
||||||
# s.frameworks = 'UIKit', 'MapKit'
|
# s.frameworks = 'UIKit', 'MapKit'
|
||||||
# s.dependency 'AFNetworking', '~> 2.3'
|
# s.dependency 'AFNetworking', '~> 2.3'
|
||||||
s.dependency 'RxCocoa', '~> 6'
|
s.dependency 'RxCocoa', '~> 6.7.0'
|
||||||
s.dependency 'Alamofire', '~> 5.0'
|
s.dependency 'Alamofire', '~> 5.9'
|
||||||
s.dependency 'FMDB'
|
s.dependency 'FMDB', '~> 2.0'
|
||||||
s.dependency 'GzipSwift'
|
s.dependency 'GzipSwift', '~> 5.0'
|
||||||
s.dependency 'CryptoSwift'
|
s.dependency 'CryptoSwift', '~> 1.0'
|
||||||
s.dependency 'SwiftyBeaver'
|
s.dependency 'SwiftyBeaver', '~> 1.0'
|
||||||
|
|
||||||
s.subspec 'Privacy' do |ss|
|
s.subspec 'Privacy' do |ss|
|
||||||
ss.resource_bundles = {
|
ss.resource_bundles = {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue