Multi Thread API calls with Socket Swift IOS - ios

I'm using sockets to communicate with server API's. I have to call multiple API calls at the same time but when response comes my method loosing completion handler reference. for example if i hit 4 Api's at the same time, Sockets reply back with 4 responses as expected but my method returning back only one callback which is last API call.
So my question is how can i divide these calls into different threads so i got exact callbacks?
My Socket Call which is return back with completion Handler
if self.socketManager.isConnected {
self.socketManager.perform(request: request) { (result) in
completion?(result)
self.cache(request: request, result: result)
}
}
My Socket call
// MARK: - Methods
func perform(request: Request, completion: #escaping ResultAnyCompletion) {
do {
print("requst##### \(request.correlationId ?? "")")
let identityStatus = checkForIdentityStatus(request: request)
let paramData = try request.createBody(excludeIdentity: identityStatus)
webSocket?.write(stringData: paramData, completion: nil)
resultCompletion = completion
} catch {
completion(.failure(NSError(domain: "Params Not Found", code: 404, userInfo: nil)))
}
}

Related

Running callback on the same thread as the caller in iOS

I call a method on the native iOS side from Kotlin/Native framework. The method does its work asynchronously and responds back with some data in a different thread than it was called with.
I want a way to call the response function also in the same thread. Below is the code:
func makePostRestRequest(url: String, body: String) {
let thread1 = Thread.current
let request = NetworkServiceRequest(url: url,
httpMethod: "POST",
bodyJSON: body)
NetworkService.processRequest(requestModel: request) { [weak self] (response: Any?, requestStatus: RequestStatus) in
// This is different thread than "thread1". Need the below code to execute on "thread1"
if requestStatus.result == .fail {
knResponseCallback.onError(errorResponse: requestStatus.error)
} else {
knResponseCallback.onSuccess(response: response)
}
}
}
I have tried to solve this using two ways.
One is to use "semaphores". I just blocked the code execution after the network call and when I got the result back in the callback of network request, I saved it in a variable and signal the semaphore. After that I just call knResponseCallback and use the response variable to return back the response.
Another way I used is to use RunLoops. I got the handle of RunLoop.current, start the runLoop in a mode and in the callback of network request, I just call perform selector on thread with object method of NSObject which dispatches the work to that thread itself.
The problem with both of them is that they are blocking calls. RunLoop.run and semaphore.wait both blocks the thread they are called in.
Is there a way to dispatch some work onto a particular thread from another thread without blocking the particular thread?
You need to create a queue to send the request and use that same queue to handle the response. Something like this should work for you:
let queue = DispatchQueue(label: "my-thread")
queue.async {
let request = NetworkServiceRequest(url: url,
httpMethod: "POST",
bodyJSON: body)
NetworkService.processRequest(requestModel: request) { [weak self] (response: Any?, requestStatus: RequestStatus) in
queue.async {
if requestStatus.result == .fail {
knResponseCallback.onError(errorResponse: requestStatus.error)
} else {
knResponseCallback.onSuccess(response: response)
}
}
}
}

PromiseKit no callback / deallocates? (Alamofire)

My promise chain is broken (maybe deallocated) before it's resolved.
This happens (so far ONLY) when I make Alamofire request fail due to host trust evaluation -> forcing evaluation to fail which results in -999 cancelled etc).
Setup is rather simple:
APIRequest:
func start(_ onSuccess:#escaping SuccessBlock, onError:#escaping ErrorBlock) {
do {
try executeRequest { dataResponse in
self.onSuccess(dataResponse)
}
} catch {
self.onError(error)
}
}
where executeRequest() is:
self.manager.request(request).responseJSON(queue: self.queue) { (response) in
completion(response)
}
Then, there is PromiseKit wrapper defined as APIRequest extension:
(This wrapper callbacks are called correctly in either case)
func start() -> Promise<APIResponse> {
return Promise<APIResponse> { resolver in
self.start({ response in
resolver.fulfill(response)
}) { error in
resolver.reject(error)
}
}
}
And finally, unit test calling the start promise (extension):
( flow never reaches this place in case of Alamofire failing )
request.start().done { response in
}.catch { error in
// not called if request failed
}
Outcome: if request fails -> the extension promise wrapper (catch) block is called, but it's not propagated back to UnitTest promise block.
Simply replacing Alamofire request with mock implementation (which triggers some other error( makes all setup work as expected (Unit Test completes with catch block being called etc) ex:
DispatchQueue.global(qos: .default).asyncAfter(deadline: .now() + 2) {
let result = Alamofire.Result { () -> Any in
return try JSONSerialization.data(withJSONObject: [:], options: .fragmentsAllowed)
}
completion(DataResponse<Any>(request: nil, response: nil, data: nil, result: result))
}
Is this something to do with Alamofire? I don't really see how else to handle the promises there ( they shouldn't deallocate anyways... Or is this bug in PromiseKit? Alamofire? I yet have to test this in the app itself ( maybe it's Unit test issue ... )
Looking at related issues -> i'm definitely resolving promises (fulfilling / rejecting) for any flow path.
I don't see how Alamofire request is different from DispatchAsync (where the latter works as expected).
I was only 10 mins short of finding the answer... Problem is also described here:
https://github.com/mxcl/PromiseKit/issues/686
Issue is that '-999 cancelled' error is not treated as 'Error' by PromiseKit. Solution is to use "catch(policy: .allErrors)" - then catch block is called as expected.
func start(_ onSuccess:#escaping SuccessBlock, onError:#escaping ErrorBlock) {
do {
try executeRequest { dataResponse in
onSuccess(dataResponse)
}
} catch {
onError(error)
}
}
You are using self.onSuccess that means its not function parameter block but instance block thats why its not going back to block from you are calling start.

RxSwift - Recursive Observables?

Learning RxSwift - Here's my Problem:
i have a webservice that fetches data using an active access token, whenever the token expired , then first call the token generate api and then call the current request to run again. so that it will have an active access token to valid results.
but i have problem in getting the response for token and then call the prev. request?
so i tried adding an observable request , then in response check if the token is invalid, then call another observable to return an active token, once token is received , call the older request again.
func apirequest(_ urlConvertible:URLRequestConvertible) -> Observable<[String:AnyObject]> {
return Observable.create({ observer -> Disposable in
let _ = Alamofire.request(urlConvertible).responseJSON
{ response in
if isTokenExpired() {
self.generateToken().subscribe(onNext: response {
self.apirequest(oldRequest)
})
}
}
return Disposables.create()
})
}
i was expecting like any Rx operators or any ideas to try?
Thanks
I wrote an article about how to do this: https://medium.com/#danielt1263/retrying-a-network-request-despite-having-an-invalid-token-b8b89340d29
Wrap your network calling code in something like this:
/// Builds and makes network requests using the token provided by the service. Will request a new token and retry if the result is an unauthorized (401) error.
///
/// - Parameters:
/// - response: A function that sends requests to the network and emits responses. Can be for example `URLSession.shared.rx.response`
/// - tokenAcquisitionService: The object responsible for tracking the auth token. All requests should use the same object.
/// - request: A function that can build the request when given a token.
/// - Returns: response of a guaranteed authorized network request.
public func getData<T>(response: #escaping (URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)>, tokenAcquisitionService: TokenAcquisitionService<T>, request: #escaping (T) throws -> URLRequest) -> Observable<(response: HTTPURLResponse, data: Data)> {
return Observable
.deferred { tokenAcquisitionService.token.take(1) }
.map { try request($0) }
.flatMap { response($0) }
.map { response in
guard response.response.statusCode != 401 else { throw TokenAcquisitionError.unauthorized }
return response
}
.retryWhen { $0.renewToken(with: tokenAcquisitionService) }
}
The article and accompanying gist shows how to write the tokenAcquisitionService and includes unit tests.

How to not freeze the UI, and wait for response?

I've been trying since the morning but I didnt achieve what I wanted.
I tried DispatchQueue.main.async and completion block but my "Submit" button in the UI still freezes waiting for the data to be returned from the server. This is my code:
func createData(request:Crudpb_CreateRequest, with completion: #escaping (String) -> Void) throws {
DispatchQueue.main.async {
self.response = try! self.client.create(request) // <---- How to handle error for this server call when the server is not available or is down?
completion(self.response.result)
}
}
I just noticed Im calling the 1st method from the following which is a Synchronous Unary which might be the reason behind the problem. But again I dont know how to call the second function in the fallowing:
/// Synchronous. Unary.
internal func create(_ request: Crudpb_CreateRequest, metadata customMetadata: Metadata) throws -> Crudpb_CreateResponse {
return try Crudpb_CrudServiceCreateCallBase(channel)
.run(request: request, metadata: customMetadata)
}
/// Asynchronous. Unary.
#discardableResult
internal func create(_ request: Crudpb_CreateRequest, metadata customMetadata: Metadata, completion: #escaping (Crudpb_CreateResponse?, CallResult) -> Void) throws -> Crudpb_CrudServiceCreateCall {
return try Crudpb_CrudServiceCreateCallBase(channel)
.start(request: request, metadata: customMetadata, completion: completion)
}
Server Side Code:
func (*server) Create(ctx context.Context, req *crudpb.CreateRequest) (*crudpb.CreateResponse, error) {
var result string
firstName := req.GetAccount().GetFirstName()
lastName := req.GetAccount().GetLastName()
// id := gocql.TimeUUID()
fmt.Println("Triggered CREATE function on Go Server " + firstName + " " + lastName + "! Yayy!")
result = fmt.Sprintf("id for %s %s : %s", firstName, lastName, strconv.Itoa(rand.Intn(100)))
return &crudpb.CreateResponse{
Result: result,
}, nil
I need this app / submit button not to freeze while it fetches result from server.
Please help.
You are still performing work on the main thread.. async only makes the createData() method to return before the task is completed.. but the task will still be processed at some time in the main thread and during this time your application will become unresponsive.. try using a global queue instead.. to keep your main thread clean..
Dont forget to perform all your UI work on the main thread after getting your response.
Use the asynchronous function instead and call the completion block inside create function's completion.
func createData(request:Crudpb_CreateRequest, with completion: #escaping (String) -> Void) throws {
try! self.client.create(request) { (response: Crudpb_CreateResponse?, result: CallResult) in
DispatchQueue.main.async {
// This is assuming your completion involves UI operations. Otherwise there is no need for this async call.
let stringOutput = String(data: result.resultData!, encoding: String.Encoding.utf8))
completion(stringOutput)
}
}
}
Remove DispatchQueue.main.async block from the createData method
func createData(request:Crudpb_CreateRequest, with completion: #escaping (String) -> Void) throws {
self.response = try! self.client.create(request)
completion(self.response.result)
}
Use main queue only where you update the UI from the api response
myobj.createData(request: request, with: { string in
print(string)//background thread
DispatchQueue.main.async {
self.label.text = sting//main thread
}
})
The UI freeze because you are doing too much work on the main thread. You should find out what function blocks the main thread.
The instruments time profiler is an easy way to see which function is spending too much time.

How to have a completion handler/block after Alamofire Post request?

I have a method which handles a Apple Push Notification Service remote notification. When this method is executed, I want it to call my server and do a HTTP POST request using the Alamofire library. I want to execute another method that will handle the response of the POST request.
The problem for me is that I am using an existing API to fetch a profile from the server in this POST request. So I need to use this existing API and figure out when this profile fetch is specifically triggered from the remote notification.
Since Alamofire requests are done in a background queue, how would I go about doing an execution of a method after receiving the profile back from the server?
What would be a good option to solving this issue?
Thank you!
Since Alamofire requests are done in a background queue, how would I go about doing an execution of a method after receiving the profile back from the server?
Response handling is built in to Alamofire. You can do something like this (adapted from the docs):
Alamofire.request(.POST, "http://httpbin.org/get", parameters: ["foo": "bar"])
.response { (request, response, data, error) in
println(request)
println(response)
println(error)
}
Note the .response method call, which adds a completion handler to the request object; the completion handler is invoked by Alamofire when the request completes (or fails).
It wasn't clear from your question formulation what problem you were trying to solve. But you've clarified your intent in the question comments above.
As I understand the problem now, you're got some code that updates a profile on the server and handles the server's response. The code is called in two contexts, one initiated by a manual request from the user, another initiated by a push notification. In the first case, you don't want to generate an alert after you process the response from the server, but in the second case you do.
You do indeed have a closure that you can use to handle the different behavior even though the difference happens in the asynchronous part of the process. Here's a sketch (not actual working code) of how that might look:
func updateProfile(parameters: [String:String], showAlert: Bool) {
Alamofire.request(.POST, "http://myserver.com/profile", parameters: parameters)
.response { (request, response, data, error) in
if (error == nil) {
processProfileResponse(response)
if showAlert {
showProfileWasUpdatedAlert()
}
}
}
}
Note the showAlert parameter passed in to the updateProfile method. If you pass in true, it calls the showProfileWasUpdatedAlert method to show your alert after receiving the server's response. Note that this boolean value is "captured" by the closure that handles the Alamofire response because the closure was defined inside the updateProfile function.
This, IMHO, is a better approach than declaring an app global inside your AppDelegate.
Here you go
func AlamofireRequest(method: Alamofire.Method, URLString: URLStringConvertible, parameters: [String : AnyObject]?, encoding: ParameterEncoding, headers: [String : String]?) -> Alamofire.Result<String>? {
var finishFlag = 0
var AlamofireResult: Alamofire.Result<String>? = nil
Alamofire.request(method, URLString, parameters: parameters, encoding: encoding, headers: headers)
.responseString { (_, _, result) -> Void in
if result.isSuccess {
finishFlag = 1
AlamofireResult = result
}
else {
finishFlag = -1
}
}
while finishFlag == 0 {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture())
}
return AlamofireResult
}

Resources