I'm implementing a RequestRetrier with Alamofire to refresh the accessToken of a given user.
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {
lock.lock() ; defer { lock.unlock() }
if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
requestsToRetry.append(completion)
if !isRefreshing {
refreshToken(completion: { [weak self] succeded, accessToken in
guard let strongSelf = self else { return }
strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
strongSelf.requestsToRetry.forEach{ $0(succeded, 0.0) }
strongSelf.requestsToRetry.removeAll()
})
}
} else {
completion(false, 0.0)
}
}
When strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() } is called it doesn't continue the execution so I have an infinite loop. I tried checking the result of strongSelf.lock.try() and returns false.
This is happening when I sign in with a wrong password so the server returns a 401.
This is my refreshToken code
guard !isRefreshing else { return }
// ... Get user ... //
if let user = user {
isRefreshing = true
signIn(user: userDTO)
.subscribe(onNext: { [weak self] userSession in
guard let strongSelf = self else { return }
// ... Save accessToken ... //
completion(true, userSession.accessToken)
strongSelf.isRefreshing = false
}, onError: { [weak self] error in
guard let strongSelf = self else { return }
// ... Log out user ... //
completion(false, nil)
strongSelf.isRefreshing = false
})
.disposed(by: bag)
} else {
completion(false, nil)
}
As seen in the Github issue https://github.com/Alamofire/Alamofire/issues/2544 I could fix this by changing:
private let lock = NSLock()
to
private let lock = NSRecursiveLock()
The difference between them is that the recursive lock can by unlocked if the same thread is trying to lock.
Related
I've been trying multiple ways to run a function in a for in loop and when all have returned to run another function but for some reason it appears the final function is running before one or more of the others have returned a result.
This is my latest attempt: (both functions work it is just the order which is the issue)
var counter: Int = 0
for owner in arrOwnerList {
self.removeDevice(device: self.device, account: owner as! String) { (isSuccess) -> Void in
print(isSuccess)
if isSuccess {
}
}
}
if self.arrOwnerList.count == self.counter {
self.removeDeviceFromServer(device: self.device)
self.sendEmail(to:"gordon#myemail.co.uk", subject:self.device+" has been removed", text:self.device+" has been removed from the server, please check the sim for bar and termination")
}
}
func removeDevice(device: String, account: String, completion: #escaping (Bool) -> Void) {
let dictHeader : [String:String] = ["username":username,"password":password]
let dictArray = [device]
self.counter += 1
WebHelper.requestPUTAPIRemoveDevice(baseURL+"rootaccount/removedevices/"+account+"?server=MUIR", header: dictHeader, dictArray: dictArray, controllerView: self, success: { (response) in
print(response)
if response.count == 0 {
self.Label1.alpha = 1
print("response count == 0")
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.ServerError, on: self)
}
}
else {
}
}) { (error) in
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: error?.localizedDescription ?? Messages.ServerError, on: self)
}
}
let isSuccess = true
self.Label1.alpha = 1
completion(isSuccess)
}
func removeDeviceFromServer(device: String) {
let dictHeader : [String:String] = ["username":username,"password":password]
WebHelper.requestDELETEAPI(baseURL+"defaultdevice/"+device+"?server=MUIR", header: dictHeader, controllerView: self, success: { (response) in
if response.count == 0 {
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.ServerError, on: self)
}
}
else {
if response.count != 0 {
self.Label2.alpha = 1
DispatchQueue.main.async {
self.Label2.alpha = 1
}
}
else{
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: Messages.NoDataFound, on: self)
}
}
}
}) { (error) in
DispatchQueue.main.async {
GlobalConstant.showAlertMessage(withOkButtonAndTitle: GlobalConstant.AppName, andMessage: error?.localizedDescription ?? Messages.ServerError, on: self)
}
}
}
DispatchGroup will solve the problems. (https://developer.apple.com/documentation/dispatch/dispatchgroup)
let dispatchGroup = DispatchGroup()
for owner in arrOwnerList {
dispatchGroup.enter()
self.removeDevice(device: self.device, account: owner as! String) { (isSuccess) -> Void in
defer {
dispatchGroup.leave()
}
print(isSuccess)
if isSuccess {
}
}
}
// This block execute when the loop is completed.
dispatchGroup.notify(queue: .main) { [weak self] in
guard let self = self else { return }
self.removeDeviceFromServer(device: self.device)
self.sendEmail(to:"gordon#myemail.co.uk", subject:self.device+" has been removed", text:self.device+" has been removed from the server, please check the sim for bar and termination")
}
I am working on a similar feature to 'liking/unliking a post'.
I have an MVVM architecture as;
struct MyStructModel {
var isLiked: Bool? = false
}
class MyStructView {
var isLiked: Bool
init(myStructModel: MyStructModel) {
self.isLiked = myStructModel.isLiked ?? false
}
}
I successfully get the value of whether the post is liked or not here;
func isPostLiked(documentID: String, completion: #escaping (Bool) -> Void) {
guard let authID = auth.id else { return }
let query = reference(to: .users).document(authID).collection("liked").document(documentID)
query.getDocument { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let data = snapshot?.data() else { return }
if let value = data["isLiked"] as? Bool {
completion(value)
} else {
completion(false)
}
}
}
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { (snapshot, error) in
if error != nil {
print(error as Any)
return
}
guard let snapshotDocuments = snapshot?.documents else { return }
for document in snapshotDocuments {
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
// isLiked is nil here...
self.isPostLiked(documentID: post.documentID!) { (isLiked) in
post.isLiked = isLiked
print("MODEL SAYS: \(post.isLiked!)")
// isLiked is correct value here...
}
posts.append(post)
}
completion(posts)
}
}
}
However, when it gets to my cell the value is still nil.
Adding Cell Code:
var post: MyStructView? {
didSet {
guard let post = post else { return }
print(post.isLiked!)
}
}
Your isLiked property is likely nil in your cells because the retrieveReviews function doesn't wait for the isPostLiked function to complete before completing itself.
You could easily solve this issue by using DispatchGroups. This would allow you to make sure all of your Posts have their isLiked value properly set before being inserted in the array, and then simply use the DispatchGroup's notify block to return all the loaded posts via the completion handler:
func retrieveReviews(completion: #escaping([MyStructModel]) -> ()) {
var posts = [MyStructModel]()
let query = reference(to: .posts).order(by: "createdAt", descending: true)
query.getDocuments { [weak self] (snapshot, error) in
guard let self = self else { return }
if error != nil {
return
}
guard let documents = snapshot?.documents else { return }
let dispatchGroup = DispatchGroup()
for document in documents {
dispatchGroup.enter()
if var post = try? JSONDecoder().decodeQuery(MyStructModel.self, fromJSONObject: document.decode()) {
self.isPostLiked(documentID: post.documentID!) { isLiked in
post.isLiked = isLiked
posts.append(post)
dispatchGroup.leave()
}
}
}
dispatchGroup.notify(queue: .main) {
completion(posts)
}
}
}
I have this function:
func makeRepoRequest() -> Single<[String: Any]> {
return Single<[String: Any]>.create {[weak self] observer in
guard let something = self?.temp else {
let disposeBag = DisposeBag()
self?.getRepo("364").subscribe(onSuccess: { content in
observer(.success(content))
}, onError: { error in
observer(.error(error))
}).disposed(by: disposeBag)
return Disposables.create()
}
observer(.success(something))
return Disposables.create()
}
}
is subscribe to this function:
func getRepo(_ repo: String) -> Single<[String: Any]> {
return Single<[String: Any]>.create { single in
print(repo)
let url = "https://api.github.com/repositories?since=\(repo)"
print(url)
let task = URLSession.shared.dataTask(with: URL(string:url)!) { data, _, error in
if let error = error {
single(.error(error))
return
}
guard let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []),
let result = json as? [String: Any] else {
let error = NSError(domain: "Decoding", code: 0, userInfo: nil)
single(.error(error))
return
}
single(.success(result))
}
task.resume()
return Disposables.create()
}
}
but for some reason the subscription it never gets a call back. Any of you knows why the subscription never gets a call back?
I'll really appreciate your help.
Your makeRepoRequest() is defined incorrectly. The disposable you create inside the closure should be the one that you return. There shouldn't be any disposeBag in there, also you need to unwrap self and make sure something is emitted if self doesn't exist, also if you are going to keep a cache in temp you really should assign to it...
func makeRepoRequest() -> Single<[String: Any]> {
return Single<[String: Any]>.create { [weak self] observer in
guard let this = self else {
observer(.error(MyError.missingSelf))
return Disposables.create()
}
guard !this.temp.isEmpty else {
return this.getRepo("364").subscribe(onSuccess: { content in
this.temp = content
observer(.success(content))
}, onError: { error in
observer(.error(error))
})
}
observer(.success(this.temp))
return Disposables.create()
}
}
However, since you are just emitting content with no changes, you don't even need to use .create(_:). So something like this:
func makeRepoRequest() -> Single<[String: Any]> {
if !temp.isEmpty {
return getRepo("364")
.do(onSuccess: { [weak self] in self?.temp = $0 })
}
else {
return Single.just(temp)
}
}
Lastly, you aren't properly canceling your network request in your getRepo(_:) method. It should end with return Disposables.create { task.cancel() }
I suggest you read up more on Disposables.
I'm working on updating an app from Swift 3 to Swift 5. This requires updating ReactiveCocoa. There is some code that was using a timer within a SignalProducer that no longer works when updating. Here is the before and after:
Swift 3 Code
func start(beats: [Beat]) -> SignalProducer<PlayedBeat, Error> {
var index: Int = 0
let oneSecondTimer = timer(interval: DispatchTimeInterval.seconds(1), on: QueueScheduler(), leeway: DispatchTimeInterval.milliseconds(100))
.on(starting: {
// initialize
index = -1
}, value: { date in
index += 1
})
.flatMap(.latest) { [weak self] (_: Date) -> SignalProducer<PlayedBeat, Error> in
guard let strongSelf = self else {
return SignalProducer<PlayedBeat, Error>(error: Error.unknown("self reference missing"))
}
if index < beats.count {
let data = beats[index].toDeviceFormat()
strongSelf.device.writeRate(data: data, delegate: strongSelf)
return SignalProducer<PlayedBeat, Error>(value: "boom")
} else {
return SignalProducer<PlayedBeat, Error>(error: Error.noMoreData).delay(1.0, on: QueueScheduler.main)
}
}
let startSignal = SignalProducer<PlayedBeat, Error> { [weak self] (innerSink, disposable) in
guard let strongSelf = self else {
innerSink.send(error: Error.unknown("self reference missing"))
return
}
strongSelf.beatSink = innerSink
let beatDisposable = oneSecondTimer.observe(innerSink, during: strongSelf.reactive.lifetime)
disposable += {
let data = DatedBeat.stopPlayingDeviceFormat()
strongSelf.device.writeRate(data: data, delegate: strongSelf)
}
disposable.add(beatDisposable)
}
return startSignal
.on(
started: { _ in
print("Start sending beats")
},
failed: { (error) in
print("error \(error)")
},
completed: { _ in
print("completed stopped beating ")
})
}
Swift 5 Code
func start(beats: [DatedBeat]) -> SignalProducer<PlayedBeat, Error> {
var index: Int = 0
let oneSecondTimer = SignalProducer.timer(interval: DispatchTimeInterval.seconds(1), on: QueueScheduler.main, leeway: DispatchTimeInterval.milliseconds(100))
.on(starting: {
// initialize
index = -1
}, value: { date in
index += 1
})
.flatMap(.latest) { [weak self] (_: Date) -> SignalProducer<PlayedBeat, Error> in
guard let strongSelf = self else {
return SignalProducer<PlayedBeat, Error>(error: Error.unknown("self reference missing"))
}
if index < beats.count {
let data = beats[index].toDeviceFormat()
strongSelf.device.writeRate(data: data, delegate: strongSelf)
return SignalProducer<PlayedBeat, Error>(value: "boom")
} else {
return SignalProducer<PlayedBeat, Error>(error: Error.noMoreData)
.delay(1.0, on: QueueScheduler.main)
}
}
let startSignal = SignalProducer<PlayedBeat, Error> { [weak self] (innerSink, lifetime) in
guard let strongSelf = self else {
innerSink.send(error: Error.unknown("self reference missing"))
return
}
strongSelf.beatSink = innerSink
if lifetime.hasEnded {
let data = DatedBeat.stopPlayingDeviceFormat()
strongSelf.device.writeRate(data: data, delegate: strongSelf)
innerSink.sendInterrupted()
return
}
_ = oneSecondTimer.observe(on: innerSink as! Scheduler)
}
return startSignal.on(
started: {
print("Start sending beats")
},
failed: { (error) in
print("error \(error)")
},
completed: {
print("completed stopped beating ")
})
}
Currently the line _ = oneSecondTimer.observe(on: innerSink as! Scheduler) throws an error when trying to case innerSink to Scheduler. If I remove that line, then the timer is never started.
If anyone has thoughts on the proper way to start a timer in ReactiveCocoa, that would be super helpful. Thanks!
I'm retrieving data from Firebase Google. I'm checking the data i received is expire or not.
func checkBought(movieName : String) -> Bool{
var yesOrNo = false
boughtRef.observeEventType(.Value, withBlock: { (snap) in
if snap.value![movieName]! != nil {
if self.timestamp > snap.value![movieName]! as! Double {
//expire
print("expire")
yesOrNo = false
} else {
//not expire
print("not expire")
yesOrNo = true
}
} else {
//not bought yet
print("No movie")
yesOrNo = false
}
})
return yesOrNo
}
Now, the return statement is returning before the firebase code is executed and change the value of yesOrNo.
The classic:
You cannot return anything from a method which contains an asynchronous task
You need a completion block, simply
func checkBought(movieName : String, completion:(Bool) -> Void) {
boughtRef.observeEventType(.Value, withBlock: { (snap) in
if snap.value![movieName]! != nil {
if self.timestamp > snap.value![movieName]! as! Double {
//expire
print("expire")
completion(false)
} else {
//not expire
print("not expire")
completion(true)
}
} else {
//not bought yet
print("No movie")
completion(false)
}
})
}
Or easier
func checkBought(movieName : String, completion:(Bool) -> Void) {
boughtRef.observeEventType(.Value, withBlock: { (snap) in
if let movieStamp = snap.value![movieName] as? Double where self.timestamp <= movieStamp {
//not expire
print("not expire")
completion(true)
} else {
// expire or not bought yet
print("expire or no movie")
completion(false)
}
})
}
And call it with
checkBought("Foo") { flag in
print(flag)
}