在swift singleton中调用异步函数的正确方法
我想为我的应用程序构建一个分析类,并且我正在使用单例。在swift singleton中调用异步函数的正确方法
如果我运行这个,tagEvent函数立即运行而不是首先运行openSession(),所以sessionId返回nil。 如何通过适当的初始化创建一个像这样的类,并像单例实例一样使用它。
Analytics.swift
final class Analytics {
public static let instance = Analytics()
private var baseUrl = "http://localhost"
public var deviceId: String?
private init(){
self.deviceId = SomeFunctionGetsDeviceID()
}
func openSession(){
// make an API call to create a session and save sessionId to UserDefaults
if let url = URL(string: self.baseUrl + "/session"){
let params:[String:Any] = ["deviceId": "\(self.deviceId!)"]
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField:"Content-Type")
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: params, options: [])
AnalyticsSessionManager.sharedManager.request(request as URLRequestConvertible).validate().responseObject(completionHandler: { (response: DataResponse<SessionOpenResponse>) in
if response.result.value != nil {
UserDefaults.standard.set(response.result.value?.sessionId, forKey: "sessionId")
}
})
}
}
func closeSession(){
// make an API call to close a session and delete sessionId from UserDefaults
...
}
func tagEvent(eventName: String, attributes: [String : String]? = nil) {
if let url = URL(string: self.baseUrl + "/event"),
let sessionId = UserDefaults.standard.string(forKey: "sessionId"){
...
// make an API call to create an event with that sessionId
}
}
}
AppDelegate.swift
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Analytics.instance.openSession()
Analytics.instance.tagEvent(eventName: "App Launch", attributes:
["userID":"1234"])
}
我最好的猜测是的openSession功能是异步做功和tagEvent电话打进来之前,异步代码有完成。有几种解决方法:
1)添加同步,以便tagEvent代码将等待openSession调用完成(如果正在进行中)。如果不是在进步,也许它会自动调用openSession时,等待完成,然后在功能
2)执行代码的openSession添加完成处理程序和机箱内部可以调用tagEvent如:
func openSession(completionHandler: @escapaing (Bool) ->()){
// make an API call to create a session and save sessionId to UserDefaults
...
UserDefaults.standard.set(someSessionID, forKey: "sessionId")
// when done with async work
completionHandler(true)
}
然后在您的应用程序代理:
Analytics.instance.openSession() { (success)
Analytics.instance.tagEvent(eventName: "App Launch", attributes:["userID":"1234"])
}
3)*这是我会修*我不会让类以外的地方需要的openSession呼叫的方式。我想一个标志添加到Analytics(分析)类:
private var initialized = false
在的openSession功能,设置这一切都做
initialized = true
后在tagEvent功能:
func tagEvent(eventName: String, attributes: [String : String]? = nil) {
// Check for initialization
if (!initialized) {
openSession() { (success) in
// perform your tagEvent code
})
} else {
if let url = URL(string: self.baseUrl + "/event"),
let sessionId = UserDefaults.standard.string(forKey: "sessionId"){
...
// make an API call to create an event with that sessionId
}
}
}
@rmaddy是绝对正确的。这是一个异步问题,而不是单例问题。 – Shawn
感谢您的回答。但是我想在AppDelegate中调用openSession一次,然后在应用程序范围内调用tagEvent。在你的情况下,每当我想标记事件时,它都不会打开一个会话? – invincible
检查我添加的第三个选项。你可以简化你的代码,但希望这表达了总体思路。 – Shawn
您可以实现一个“内部”类,为您的Analytics类提供静态参考,如下所示(注意:删除final
关键字):
class Analytics {
// properties ...
class var sharedInstance: Analytics {
struct Static {
static let instance: Analytics = Analytics()
}
return Static.instance
}
// other methods (init(), openSession(), closeSession(), tagEvent())
}
接下来调用方法如下:
Analytics.sharedInstance.openSession()
Analytics.sharedInstance.closeSession()
Analytics.sharedInstance.tagEvent(...)
感谢您的回答,但结果相同,tagEvent函数运行时无需等待正在进行异步API调用的openSession函数来创建会话 – invincible
'openSession'肯定是之前'tagEvent'调用。最可能的问题是'openSession'会立即返回,因为它会进行异步调用。 – rmaddy
“进行API调用”实际上是什么样子? –
我编辑了一个API调用部分@rmaddy – invincible