i just started learning Swift a year ago, so please be patient with me :)
i am downloading JSON data with a network call, and as soon as i successfully received those rows, i then continue to clear the rows inside my coreData entity, and rewrite those new rows into coredata..
i am having a hard time understanding this asynchronous procedure..
what i've learned is that i have to use completion handlers, but i still can't use it the way i need to.. especialy when i need to proceed after those 3 steps were executed..
First call from button action:
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
textField.text = "blabla" // gets executed before the result is available
methods:
func update(completion: #escaping (String?) -> Void) { //parent method which calls sub methods
var returnValue = ""
Step1getJson {_ in. // step 1
self.Step2Delete { // step 2
self.Step3Save { // step 3
returnValue = "return Value: \(self.step1Result)"
completion(returnValue)
}
}
}
}
func Step1getJson(completion: #escaping (Bool) -> ()) {
var success = false
if let url = URL(string: "https:foo") {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
do {
let parsedJSON = try JSONDecoder().decode([RemoteWire].self, from: data)
print("-- Successfully received \(parsedJSON.count) datarows ")
self.JSON = parsedJSON
self.step1Result = "-- Successfully received \(parsedJSON.count) datarows "
success = true
} catch {
print(error)
}
completion(success)
}.resume()
}
}
func Step2Delete(completion: () -> Void) {
...delete entity rows
completion()
}
func Step3Save(completion: () -> Void) {
.. save new JSON rows to coreData
completion()
}
Everything is working fine that far, and step 2 and step 3 get successfully called when network download has finished..
but how can i proceed after those steps were executed inside my updateButtonPressed function?
if i try to write those results into any UI element inside my completion block, a textField or whatever, i get an error that this has to happen in the main thread, and if i execute it outside the completion block those lines get executed far too early, when no results are available yet.
i feel like i have understanding problem with this, i hope you guys can help me out and guide me in the right direction.
As swift allows any changes or updates in UI element only from main thread, you need to call the main thread to update the UI.
Replace the below code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
}
with the new code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
DispatchQueue.main.async {
textField.text = success! // Now possible because it is in main thread
}
}
}
Related
First-time poster, long-time reader ... I've wrapped an async call to the Firebase Authorization API. I'm calling it from inside a SwiftUI View function.
func authenticateFirebaseEmail(email: String, password: String) ->
Future<String, Error> {
return Future<String,Error> { promise in
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error=error {
print("failure detected")
promise(.failure(error))
}
if let result=result {
print("result detected - returning success promise")
promise(.success(result.user.email!))
}
}
}
}
...
func logMeInFuncInView() {
var cancellable : AnyCancellable?
cancellable = authenticateFirebaseEmail(email: self.userEmail, password: self.password).map( {
value in return value
})
.sink(receiveCompletion: { (completion) in
print("completion received")
}, receiveValue: { user in
print("value received")
self.errorMessage = user
})
}
The console output is as follows, but never reaches the "completion received" or "value received" calls:
result detected - returning successful promise
Is the issue with the wrapped callback, the future, the way I'm using the future, or something that I'm not seeing entirely?
Your cancellable is local variable, so destroyed once went off context. As soon as subscriber is destroyed it cancels subscription and, as it is only one, publisher cancelled as well.
Your solution is to make your cancellable as property, ie
var cancellable : AnyCancellable? // << here !!
func logMeInFuncInView() {
cancellable = authenticateFirebaseEmail(email: self.userEmail, password: self.password).map( {
value in return value
})
// .. other code
}
I'm trying to build a basic proof-of-concept watchOS app and complication that pulls JSON data from an API and displays a gauge based on that.
I've watched Apple's 'Keping your watch app up to date' and found several other questions on the subject but the sample code has been taken down.
The API provides forecasts for the next ~48 hours and can be used to populate the timeline entries for the complication. When the Complication Controller requests the timeline entries I pull the data from the Extension Delegate and therefore it must be kept up to date. However the process of scheduling background data tasks has got me stumped. When I call backgroundSession.dataTask(with: URL(string: "https://...... I expect my URLSessionDataDelegate functions to be called but they never are and I never get a the handle(_ backgroundTasks) called with WKURLSessionRefreshBackgroundTask
Question:
When a WKApplicationRefreshBackgroundTask is sent to my ExtensionDelegate how should I request/schedule data from the API and then receive it?
Code
//ExtensionDelegate
class ExtensionDelegate: NSObject, WKExtensionDelegate, URLSessionDelegate, URLSessionDataDelegate {
...
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
print("background task as WKApplicationRefreshBackgroundTask")
self.scheduleURLSession()
backgroundTask.setTaskCompletedWithSnapshot(false)
return
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
print("background task as WKURLSessionRefreshBackgroundTask")
let backgroundConfigObject =
URLSessionConfiguration.background(withIdentifier: urlSessionTask.sessionIdentifier)
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
print("Rejoining session ", backgroundSession)
self.savedTask = urlSessionTask
return
default:
// make sure to complete unhandled task types
task.setTaskCompletedWithSnapshot(false)
}
}
}
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "nz.co.craigstanton")
backgroundConfigObject.sessionSendsLaunchEvents = true
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
let dataTask = backgroundSession.dataTask(with: URL(string: "https://craigstanton.co.nz/uvi-test?latitude=-36&longitude=174")!)
print("scheduleURLSession about to 'resume' ")
dataTask.resume()
}
//Delegate callbacks
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("Data task error", error)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("urlSession Delegate did receive everything ")
}
func urlSession(_: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("urlSession Delegate did receive something ")
}
In my application the main entity is threads, I mean sneakers, jackets, t-shorts and so on.
This is firebase db:
I have logic witch fetch threads by 'threadTypes'. In my app there're 3 types - outwear, footwear and accessory.
That is code:
extension GoodsViewController {
func fetchThreads(completion: #escaping (Swift.Void) -> Swift.Void) {
self.ref
.child("threads")
.observeSingleEvent(of: .value, with: { (snapshot) in
for rest in snapshot.children.allObjects as! [FIRDataSnapshot] {
guard let restDict = rest.value as? [String: Any] else { continue }
let thread = Thread()
thread.setValuesForKeys(restDict)
if Search.searchFilters.stuffTypes.isEmpty {
self.threads.append(thread)
}
else {
if let threadType = thread.threadType {
if Search.searchFilters.stuffTypes.contains(threadType) {
self.threads.append(thread)
}
}
}
}
completion()
})
}
}
self.threads - is variable which is used as table view datasource.
Search.searchFilters.stuffTypes - array which contains types for search.
As you see I fetch all threads and then check if current thread type contains in Search.searchFilters.stuffTypes array.
My question is - is it possible to perform this checking before I fetch all threads?
In C# it should be something like that -
threads.Where(t => Search.searchFilters.stuffTypes.Contains(t.threadType)
I need to call an API to upload a photo, this API returns an ID of the photo. Then, I need to get that ID and use it as a parameter for another API.
The problem is, the second API gets called before the first API has a chance to complete (and return the ID). What can be done about this?
I'm using Alamofire 4 and Swift 3.
My code:
// First API - Upload file
func uploadFile(url: String, image: UIImage, callback: #escaping (JSON) -> ()) {
let imageData = UIImagePNGRepresentation(image)
let URL2 = try! URLRequest(url: url, method: .post, headers: header)
Alamofire.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(imageData!, withName: "photo", fileName: "picture.png", mimeType: "image/png")
}, with: URL2, encodingCompletion: { (result) in
switch result {
case .success(let upload, _, _):
upload.responseJSON
{
response in
switch response.result {
case .success(let value):
let json = JSON(value)
callback(json)
case .failure(let error):
print(error)
}
}
case .failure(let encodingError):
print(encodingError)
}
})
}
// Second API
func Post(url: String, parameters: [String:Any], callback: #escaping (JSON) -> ()) {
Alamofire.request(url, method: .post, parameters: parameters.asParameters(), encoding: ArrayEncoding(), headers: header).responseData { (response) in
switch response.result {
case .success(let value):
callback(json)
case .failure(let error):
print(error)
}
}
}
// Calling the First API
var uploadedImage: [String:String]!
uploadFile(url: baseUrl, image: image, callback: { (json) in
DispatchQueue.main.async {
uploadedImage = ["filename": json["data"]["photo"]["filename"].stringValue, "_id": json["data"]["photo"]["_id"].stringValue]
}
})
// Calling the Second API
Post(url: baseUrl, parameters: uploadedImage) { (json) in
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
}
}
In order to avoid the race condition that you're describing, you must be able to serialize the two calls somehow. The solutions that come to mind are either barriers, blocking calls, or callbacks. Since you're already using asynchronous (non-blocking) calls, I will focus on the last item.
I hope that a pseudocode solution would be helpful to you.
Assuming the 1st call is always performed before the 2nd, you would do something like:
firstCall(paramsForFirstOfTwo){
onSuccess {
secondCall(paramsForSuccess)
}
onFailure {
secondCall(paramsForFailure)
}
}
However, if the 1st call is optional, you could do:
if (someCondition){
// The above example where 1->2
} else {
secondCall(paramsForNoFirstCall)
}
If you must perform the 1st call a certain amount of times before the 2nd is performed, you can:
let n = 3; var v = 0; // assuming these are accessible from within completedCounter()
firstCall1(...){/* after running code specific to onSuccess or onFailure call c..C..() */}
firstCall2(...){/* same as previous */}
firstCall3(...){/* same as previous */}
function completedCounter(){
// v must be locked (synchronized) or accessed atomically! See:
// http://stackoverflow.com/q/30851339/3372061 (atomics)
// http://stackoverflow.com/q/24045895/3372061 (locks)
lock(v){
if (v < n)
v += 1
else
secondCall(paramsBasedOnResultsOfFirstCalls);
}
}
First I use the "transferUserInfo"-method in order to send a dictionary from the iPhone to the Apple Watch:
let dicty = //...my dictionary of property-list values...
if WCSession.isSupported() {
let session = WCSession.defaultSession()
if session.paired == true { // Check if your Watch is paired with your iPhone
if session.watchAppInstalled == true { // Check if your Watch-App is installed on your Watch
session.transferUserInfo(dicty)
}
}
}
Then I am using the following delegate callback method "didFinishUserInfoTransfer" to check upon the state of the transfer:
func session(session: WCSession, didFinishUserInfoTransfer userInfoTransfer: WCSessionUserInfoTransfer, error: NSError?) {
if error == nil {
let session = WCSession.defaultSession()
let transfers = session.outstandingUserInfoTransfers
if transfers.count > 0 { //--> is always > 0, why ?????????
for trans in transfers {
trans.cancel() // cancel transfer that will be sent by updateApplicationContext
let dict = trans.userInfo
session.transferUserInfo(dict) //--> creates enless-transfer cycle !!!!!
}
}
}
else {
print(error)
}
}
In the Apple documentation, it sais about the didFinishUserInfoTransfer method:
The session object calls this method when a data transfer initiated by the
current app finished, either successfully or unsuccessfully. Use this method
to note that the transfer completed or to respond to errors, perhaps by
trying to send the data again at a later time.
So far so good - I understood. But now - there is something I do not understand:
If didFinishUserInfoTransfer is entered and the error == nil, why on earth can the session.outstandingUserInfoTransfers COUNT be bigger than zero ??????
According to the Apple-documentation, the only non-error-state of didFinishUserInfoTransfer should be when the transfer is over !! Bit it does not seem to be over... Why ???
Thanks for any clarification on this.
And also, I am glad of any example-code on how to use these 3 methods correctly !
(i.e.
session.transferUserInfo(dicty)
didFinishUserInfoTransfer
session.outstandingUserInfoTransfers)
It seems that the userInfoTransfer that triggers the didFinishUserInfoTransfer callback is not removed from the outstandingUserInfoTransfers until the delegate callback has returned. To get the behavior you want (where count can go down to 0) you'd want to dispatch_async away from the delegate callback thread. So this should work:
func session(session: WCSession, didFinishUserInfoTransfer userInfoTransfer: WCSessionUserInfoTransfer, error: NSError?) {
if error == nil {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
let transfers = session.outstandingUserInfoTransfers
if transfers.count > 0 { //--> will in most cases now be 0
for trans in transfers {
trans.cancel() // cancel transfer that will be sent by updateApplicationContext
let dict = trans.userInfo
session.transferUserInfo(dict) // ***
}
}
});
}
else {
print(error)
}
}
That said, I don't quite understand why you'd want to cancel all the remaining outstanding userInfoTransfers whenever any of them completes, just to re-queue them (spot in question is indicated by ***)
There is a little misunderstanding, as far as I read the docs: only send again if an error occurs. To have outstanding userInfoTransfers if no error has been raised is the expected behavior; they have not yet successfully been send and are still queued.
Btw. code uses the actual dispatchQueue.
func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) {
if error != nil { // resend if an error occured
DispatchQueue.main.async {
let transfers = session.outstandingUserInfoTransfers
if transfers.count > 0 {
// print("open transfers: \(transfers.count)")
for trans in transfers {
// print("resend transfer")
trans.cancel() // cancel transfer
let dict = trans.userInfo
session.transferUserInfo(dict) // send again
}
}
}
}
}