yx_tracking_ios/YXTrackingSDK/YXTracking.swift

462 lines
18 KiB
Swift
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// YXTracking.swift
// YXTrackingSDK
//
// Created by 1234 on 2026/1/27.
//
import UIKit
import Network
/// SDK
/// 1SDK
/// 2/20600
/// 3
/// 4
/// 5
public class YXTracking: NSObject {
static let shared = YXTracking()
private var host: String = "" //
private var cacheCount: Int = 20 //
private var pushInterval: Int = 600 //
private var enableLog: Bool = false //
private var eventRequestResult: EventRequestResult = EventRequestResult() //
private var systemAllDimInfoResponseResult: SystemAllDimInfoResponseResult = SystemAllDimInfoResponseResult() //
private var isPushingEventList: Bool = false //
/// SDK
/// - Parameters:
/// - host:
/// - systemCode:
/// - cacheCount:
/// - pushInterval:
/// - enableLog:
/// - Returns:
public class func initWithSystemCode(host: String, systemCode: String, cacheCount: Int?, pushInterval: Int?, enableLog: Bool?) {
//
if host.isEmpty {
YXTracking.shared.error(message: "SDK config failed, host is empty!");
return;
}
//
if systemCode.isEmpty {
YXTracking.shared.error(message: "SDK config failed, system code is empty!");
return;
}
//
YXTracking.shared.host = host
if cacheCount != nil {
YXTracking.shared.cacheCount = cacheCount!
}
if pushInterval != nil {
YXTracking.shared.cacheCount = pushInterval!
}
if enableLog != nil {
YXTracking.shared.enableLog = enableLog!
}
//
YXTracking.shared.log(message: "SDK config success!");
//
YXTracking.shared.addNetworkReachabilityObserver()
//
YXTracking.shared.addEnterBackgroundNotificationObserver()
YXTracking.shared.addWillTerminateNotificationObserver()
//
YXTracking.shared.eventRequestResult.system_code = systemCode
YXTracking.shared.eventRequestResult.clientType = 2
//
YXTracking.shared.getDeviceInfo()
//
YXTracking.shared.getSystemAllDimInfo()
}
///
/// - Parameters:
/// - eventType:
/// - page: controller
/// - buttonId:
/// - url: action
/// - customTags:
/// - pushNow
/// - Returns:
public class func addEvent(eventType: String, page: String, buttonId: String, url: String, customTags: Dictionary<String, String>?, pushNow: Bool) {
//
YXTracking.shared.eventRequestResult.eventType = eventType
//
let eventParams = EventParams(page: page, buttonId: buttonId, url: url)
YXTracking.shared.eventRequestResult.eventParams = eventParams
//
YXTracking.shared.eventRequestResult.customTags = customTags
//
YXTracking.shared.getTimestamp()
YXTracking.shared.log(message: "SDK add event:\n\(YXTracking.shared.eventRequestResult)");
//
if let systemEventTypes = YXTracking.shared.systemAllDimInfoResponseResult.systemEventTypes,
systemEventTypes.count > 0 {
guard !eventType.isEmpty else {
YXTracking.shared.error(message: "SDK push event error, event type is empty!");
return
}
if !systemEventTypes.contains(where: { $0.event_code == eventType }) {
YXTracking.shared.error(message: "SDK push event error, event type not in systemEventTypes!");
return;
}
} else {
YXTracking.shared.error(message: "SDK push event error, systemEventTypes is empty!");
YXTrackingSQLiteManager.addTrackingData(data: YXTracking.shared.eventRequestResult)
return
}
//
YXTracking.shared.log(message: "SDK add event success\nEvent Request Parameter:\n\(YXTracking.shared.eventRequestResult)");
//
if pushNow {
YXTracking.shared.pushEvent(event: YXTracking.shared.eventRequestResult)
} else {
YXTrackingSQLiteManager.addTrackingData(data: YXTracking.shared.eventRequestResult)
YXTracking.shared.checkEventCount()
}
}
///
/// - Parameters:
/// - userInfo:
/// - Returns:
public class func setUserInfo(userId: Int64, userName: String, account: String) {
let userInfo = UserInfo(userId: userId, userName: userName, account: account)
YXTracking.shared.eventRequestResult.userInfo = userInfo
YXTracking.shared.log(message: "SDK config user info success\nUser info:\n\(userInfo)");
}
///
public class func clearUserInfo() {
YXTracking.shared.eventRequestResult.userInfo = nil
YXTracking.shared.log(message: "SDK clear user info success!");
}
///
func getDeviceInfo() {
let device = UIDevice.current
let os = "\(device.systemName) \(device.systemVersion)"
let localizedModel = device.localizedModel
let screenWidthPoints = UIScreen.main.bounds.width
let screenHeightPoints = UIScreen.main.bounds.height
let scale = UIScreen.main.scale
let screenWidthPixels = screenWidthPoints * scale
let screenHeightPixels = screenHeightPoints * scale
let screenResolution = "\(Int(screenWidthPixels))x\(Int(screenHeightPixels))"
let deviceInfo = DeviceInfo(os: os, model: localizedModel, screenResolution: screenResolution)
YXTracking.shared.eventRequestResult.deviceInfo = deviceInfo
YXTracking.shared.debug(message: "SDK config device info success\nDevice info:\n\(deviceInfo)");
}
///
func getTimestamp() {
let currentDate = Date()
let unixTimestamp = currentDate.timeIntervalSince1970
let milliseconds = Int64(unixTimestamp * 1000)
let customFormatter = ISO8601DateFormatter()
customFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
customFormatter.timeZone = TimeZone.current
let isoStringWithMilliseconds = customFormatter.string(from: currentDate)
YXTracking.shared.eventRequestResult.clientTimestamp = milliseconds
YXTracking.shared.eventRequestResult.timestamp = isoStringWithMilliseconds
}
///
func checkEventCount() {
let count = YXTrackingSQLiteManager.getTrackingDataCount()
if count >= cacheCount {
YXTracking.pushEventList()
}
}
///
func startThreadPool() {
let queue = DispatchQueue(label: "com.yx.tracking", qos: .userInitiated)
queue.async { [weak self] in
guard let self = self else { return }
while true {
YXTracking.shared.log(message: "SDK push event list on thread pool!")
YXTracking.pushEventList()
Thread.sleep(forTimeInterval: TimeInterval(pushInterval))
}
}
}
///
func getSystemAllDimInfo() {
let systemCode = YXTracking.shared.eventRequestResult.system_code ?? ""
guard !systemCode.isEmpty else { return }
let urlString = "\(host)/api/ExternalEventlogs/GetSystemAllDimInfo?systemCode=\(systemCode)"
guard let url = URL(string: urlString) else {
return
}
let task = URLSession.shared.dataTask(with: url) { [self] data, response, error in
if let error = error {
YXTracking.shared.error(message: error.localizedDescription)
return
}
guard let data = data else {
return
}
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let dictionary = json as? [String: Any] {
if let code = dictionary["code"] as? Int, code == 200 {
if let result = dictionary["data"] as? Dictionary<String, Any>,
let resultData = try? JSONSerialization.data(withJSONObject: result) {
do {
let resultModel = try JSONDecoder().decode(SystemAllDimInfoResponseResult.self, from: resultData)
systemAllDimInfoResponseResult = resultModel
YXTracking.shared.log(message: "SDK init success\nSystem All Dim Info:\n\(systemAllDimInfoResponseResult)");
//
YXTracking.pushEventList()
// 线
YXTracking.shared.startThreadPool()
} catch {
YXTracking.shared.error(message: "SDK init fail!");
}
} else {
YXTracking.shared.error(message: "无法创建Data对象!")
}
} else {
YXTracking.shared.error(message: "SDK init failerror code: \(dictionary["code"] ?? "未知")")
}
}
}
task.resume()
}
///
/// - Parameters:
/// - event:
/// - Returns:
func pushEvent(event: EventRequestResult) {
let urlString = "\(host)/api/ExternalEventlogs/AddEventLog"
guard let url = URL(string: urlString) else {
return
}
// URL
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// eventJSON
let jsonEncoder = JSONEncoder()
do {
YXTracking.shared.log(message: "SDK push event:\n\(event)");
let jsonData = try jsonEncoder.encode(event)
request.httpBody = jsonData
} catch {
YXTracking.shared.error(message: "SDK push event error encoding\n\(error)");
return
}
//
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
YXTracking.shared.error(message: "SDK push event error sending\n\(error)");
return
}
guard let data = data else {
return
}
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let dictionary = json as? [String: Any] {
if let code = dictionary["code"] as? Int, code == 200 {
YXTracking.shared.success(message: "SDK push event success Response:\n\(String(data: data, encoding: .utf8) ?? "")");
} else {
YXTrackingSQLiteManager.addTrackingData(data: YXTracking.shared.eventRequestResult)
YXTracking.shared.error(message: "SDK push event fail Response:\n\(String(data: data, encoding: .utf8) ?? "")");
}
}
}
task.resume()
}
///
public class func pushEventList() {
/// pushEventList
if YXTracking.shared.isPushingEventList {
return
}
YXTracking.shared.isPushingEventList = true
///
let count = YXTrackingSQLiteManager.getTrackingDataCount()
guard count > 0 else { return }
/// URL
let urlString = "\(YXTracking.shared.host)/api/ExternalEventlogs/AddEventListLog"
guard let url = URL(string: urlString) else { return }
/// URL
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
///
let eventList = YXTrackingSQLiteManager.readTrackingData()
var events: [EventRequestResult] = []
for item in eventList {
events.append(item)
}
///
guard !events.isEmpty else { return }
/// event listJSON
let jsonEncoder = JSONEncoder()
do {
let jsonData = try jsonEncoder.encode(events)
request.httpBody = jsonData
} catch {
YXTracking.shared.error(message: "SDK push event list error encoding\n\(error)");
YXTracking.shared.isPushingEventList = false
return
}
///
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
YXTracking.shared.error(message: "SDK push event list error sending\n\(error)");
YXTracking.shared.isPushingEventList = false
return
}
guard let data = data else {
YXTracking.shared.isPushingEventList = false
return
}
let json = try? JSONSerialization.jsonObject(with: data, options: [])
if let dictionary = json as? [String: Any] {
if let code = dictionary["code"] as? Int, code == 200 {
YXTrackingSQLiteManager.deleteTrackingData()
YXTracking.shared.success(message: "SDK push event list success Response:\n\(String(data: data, encoding: .utf8) ?? "")")
} else {
YXTracking.shared.error(message: "SDK push event list fail Response:\n\(String(data: data, encoding: .utf8) ?? "")")
}
}
YXTracking.shared.isPushingEventList = false
}
task.resume()
}
///
func addNetworkReachabilityObserver() {
let monitor = NWPathMonitor()
let queue = DispatchQueue(label: "NetworkMonitor")
monitor.pathUpdateHandler = { path in
let systemEventTypes = YXTracking.shared.systemAllDimInfoResponseResult.systemEventTypes ?? []
guard systemEventTypes.isEmpty else { return }
if path.status == .satisfied {
YXTracking.shared.log(message: "SDK network connected!");
YXTracking.shared.getSystemAllDimInfo()
} else {
YXTracking.shared.log(message: "SDK network not connected!");
}
}
monitor.start(queue: queue)
}
///
func addEnterBackgroundNotificationObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(onPushEventList), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
///
func addWillTerminateNotificationObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(onPushEventList), name: UIApplication.willTerminateNotification, object: nil)
}
///
@objc func onPushEventList() {
YXTracking.pushEventList()
}
}
///
extension YXTracking {
///
func log(message: String) {
guard YXTracking.shared.enableLog else { return }
print(" [YXTracking LOG] \(message)")
}
func debug(message: String) {
guard YXTracking.shared.enableLog else { return }
print("🐛 [YXTracking DEBUG] \(message)")
}
func warning(message: String) {
guard YXTracking.shared.enableLog else { return }
print("⚠️ [YXTracking WARNING] \(message)")
}
func success(message: String) {
guard YXTracking.shared.enableLog else { return }
print("✅ [YXTracking SUCCESS] \(message)")
}
func error(message: String) {
guard YXTracking.shared.enableLog else { return }
print("❌ [YXTracking ERROR] \(message)")
}
func fatal(message: String) {
guard YXTracking.shared.enableLog else { return }
print("💀 [YXTracking FATAL] \(message)")
}
}