Can't get closure to close over variables - ios

I'm working with Parse and I'm trying to store an objects unique identifier that Parse gives you. Here's the code to help make sense of this.
var objectID = ""
var object = PFObject(className: "class")
object["columnTitle"] = "String Data"
object.saveInBackgroundWithBlock { (success, error) -> Void in
guard error == nil else {
print("Error")
return
}
objectID = object.objectId!
}
print(objectID)
So I thought when I ran the app the print statement would print out the objectId that Parse gives you but it doesn't. If I add the print statement inside the closure it is definitely getting the value I'm expecting. It seems like the print statement is running before the value can be changed. Any idea how to fix this? I'm assuming using GCD?
All of this is inside viewDidLoad()

saveInBackgroundWithBlock is an asynchronous call. If you print the object ID with in the block, it gets printed while the block is executed in a different thread.
If you place the print outside the block, it will be the immediate next statement executed after the call to saveInBackgroundWithBlock. You should place break points in these two statements and understand the behavior.
You could define a method which contains the object ID related processing code and call that method at the end of your saveInBackgroundWithBlock

Related

Swift Closures and order of execution

I'm struggling a little bit trying to create an application for my own education purposes using Swift.
Right now I have the following (desired) order of execution:
TabView
FirstViewController - TableView
Check into CoreData
If data exists update an array using a closure
If data doesn't exists then download it using Alamofire from API and store it into Core Data
SecondViewController - CollectionView
Checks if data of images exists in Core Data, if it does, loads it from there, otherwise download it.
The problem that I'm struggling the most is to know if the code after a closure is executed after (synchronously) the closure ends or it might be executed before or while the closure is executed.
For example:
FirstViewController
var response: [DDGCharacter]
//coreData is an instance of such class
coreData.load(onFinish: { response in //Custom method in another class
print("Finished loading")
self.response = response
})
print("Executed after loading data from Core Data")
//If no data is saved, download from API
if response.count == 0 {
//Download from API
}
I have done the above test with the same result in 10 runs getting:
Finished loading
Executed after loading data from Core Data
In all 10, but it might be because of load is not taking too much time to complete and thus, appear to be synchronous while it's not.
So my question is, is it going to be executed in that order always independent of amount of data? Or it might change? I've done some debugging as well and both of them are executed on the main thread as well. I just want to be sure that my suppositions are correct.
As requested in the comments, here's the implementation done in the load() method:
func load(onFinish: ([DDGCharacter]) -> ()) {
var characters: [DDGCharacter] = []
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject> (entityName: "DDGCharacter")
do {
characters = try managedContext.fetch(fetchRequest) as! [DDGCharacter]
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
onFinish(characters)
}
Your implementation of load(onFinish:) is very surprising and over-complicated. Luckily, though, that helps demonstrate the point you were asking about.
A closure is executed when something calls it. So in your case, onFinish is called at the end of the method, which makes it synchronous. Nothing about being "a closure" makes anything asynchronous. It's just the same as calling a function. It is completely normal to call a closure multiple times (map does this for instance). Or it might never be called. Or it might be called asynchronously. Fundamentally, it's just like passing a function.
When I say "it's slightly different than an anonymous function," I'm just referring to the "close" part of "closure." A closure "closes over" the current environment. That means it captures variables in the local scope that are referenced inside the closure. This is slightly different than a function (though it's more about syntax than anything really deep; functions can actually become closures in some cases).
The better implementation would just return the array in this case.

How can I transform a signal with errors into a NoError one with ReactiveSwift? (and be elegant)

What is the most elegant way to transform my ReactiveSwift's SignalProducer<A, NetworkError> into a Signal<A, NoError>?
Most of the time, my signal producer is the result of a network call, so I want to split the results into two cases:
if a value is available, send a Signal<A, NoError>
if an error happened, send a Signal<String, NoError> with the error's localized description
(why? because i'm trying to be as MVVM as possible)
So far, I end up writing a lot of boilerplate like the following:
let resultsProperty = MutableProperty<SearchResults?>(nil)
let alertMessageProperty = MutableProperty<String?>(nil)
let results = resultsProperty.signal // `Signal<SearchResults?, NoError>`
let alertMessage = alertMessageProperty.signal // `Signal<String?, NoError>`
// ...
searchStrings.flatMap(.latest) { string -> SignalProducer<SearchResults, NetworkError> in
return MyService.search(string)
}
.observe { event in
switch event {
case let .value(results):
resultsProperty.value = results
case let .failed(error):
alertMessageProperty.value = error
case .completed, .interrupted:
break
}
}
ie:
using MutableProperty instances, that I have to set as optional to be able to initialize them
creating signals from those, ie getting a signal sending optionals as well
it feels dirty and makes the code so intertwined it kind of ruins the point of being reactive
Any help on (A) keeping my signals non optional and (B) splitting them into 2 NoError signals elegantly would be greatly appreciated.
Edit - Second Attempt
I will try to answer all your questions / comments here.
The errors = part doesn't work as flatMapError expects a SignalProducer (ie your sample code works just because searchStrings is a Signal string, which coincidently is the same as the one we want for errors: it does not work for any other kind of input)
You are correct, this is because flatMapError does not change the value type. (Its signature is func flatMapError<F>(_ transform: #escaping (Error) -> SignalProducer<Value, F>) -> SignalProducer<Value, F>). You could add another call to map after this if you need to change it into another value type.
the results = part behaves weirdly as it terminates the signal as soon as an error is met (which is a behavior I don't want) in my real-life scenario
Yes, this is because the flatMap(.latest) forwards all errors to the outer signal, and any error on the outer signal will terminate it.
Okay so here's an updated version of the code, with the extra requirements that
errors should have different type than searchStrings, let's say Int
Any error from MyService.search($0) will not terminate the flow
I think the easiest way to tackle both these issues is with the use of materialize(). What it does is basically "wrap" all signal events (new value, error, termination) into a Event object, and then forward this object in the signal. So it will transform a signal of type Signal<A, Error> into a Signal<Event<A, Error>, NoError> (you can see that the returned signal does not have an error anymore, since it is wrapped in the Event).
What it means in our case is that you can use that to easily prevent signals from terminating after emitting errors. If the error is wrapped inside an Event, then it will not automatically terminate the signal who sends it. (Actually, only the signal calling materialize() completes, but we will wrap it inside the flatMap so the outer one should not complete.)
Here's how it looks like:
// Again, I assume this is what you get from the user
let searchStrings: Signal<String, NoError>
// Keep your flatMap
let searchResults = searchStrings.flatMap(.latest) {
// Except this time, we wrap the events with `materialize()`
return MyService.search($0).materialize()
}
// Now Since `searchResults` is already `NoError` you can simply
// use `filterMap` to filter out the events that are not `.value`
results = searchResults.filterMap { (event) in
// `event.value` will return `nil` for all `Event`
// except `.value(T)` where it returns the wrapped value
return event.value
}
// Same thing for errors
errors = searchResults.filterMap { (event) in
// `event.error` will return `nil` for all `Event`
// except `.failure(Error)` where it returns the wrapped error
// Here I use `underestimatedCount` to have a mapping to Int
return event.error?.map { (error) in
// Whatever your error mapping is, you can return any type here
error.localizedDescription.characters.count
}
}
Let me know if that helps! I actually think it looks better than the first attempt :)
First Attempt
Do you need to access the state of you viewModel or are you trying to go full state-less? If state-less, you don't need any properties, and you can just do
// I assume this is what you get from the user
let searchStrings: Signal<String, NoError>
// Keep your flatMap
let searchResults = searchStrings.flatMap(.latest) {
return MyService.search($0)
}
// Use flatMapError to remove the error for the values
results = searchResults.flatMapError { .empty }
// Use flatMap to remove the values and keep the errors
errors = searchResults.filter { true }.flatMapError { (error) in
// Whatever you mapping from error to string is, put it inside
// a SignalProducer(value:)
return SignalProducer(value: error.localizedDescription)
}

How to Update uitableview from CloudKit using discoverAllIdentities

So, I am new to cloudKit and to working with multiple threads in general, which I think is the source of the problem here, so if I simply need to research more, please just comment so and I will take that to heart.
Here is my question:
I am working in Swift 3 Xcode 8.1
I have in my view controller this variable:
var contactsNearby: [String:CLLocation]?
Then at the end of ViewDidLoad I call one of my view controllers methods let's call it:
populateContactsNearby()
inside that method I call:
container.discoverAllIdentities(completionHandler: { (identities, error) in
for userIdentity in identities! {
self.container.publicCloudDatabase.fetch(withRecordID: userIdentity.userRecordID!, completionHandler: { (userRecord, error) in
let contactsLocation = userRecord?.object(forKey: "currentLocation")
if self.closeEnough(self.myLocation!, contactLocation: contactsLocation as! CLLocation) {
var contactsName = ""
contactsFirstName = userIdentity.nameComponents?.givenName
if contactsName != "" && contactsLocation != nil {
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
}
}
})
}
})
}
I apologize if I am missing or have an extra bracket somewhere. I have omitted some error checking code and so forth in order to get this down to bare-bones. So the goal of all that is to populate my contactsNearby Dictionary with data from CloudKit. A name as the key a location as the value. I want to use that data to populate a tableview. In the above code, the call to closeEnough is a call to another one of my view controllers methods to check if the contact from CloudKit has a location close enough to my user to be relevant to the apps purposes. Also myLocation is a variable that is populated before the segue. It holds the CLLocation of the app users current location.
The Problem:
The if statement:
if contactsName != "" && contactsLocation != nil { }
Appears to succeed. But my view controllers variable:
var contactsNearby: [String:CLLocation]?
Is never populated and I know there is data available in cloudKit.
If it's relevant here is some test code that I have in cellForRowAtIndexPath right now:
let contact = self.contactsNearby?.popFirst()
let name = contact?.key
if name != nil {
cell.textLabel?.text = name
}else {
cell.textLabel?.text = "nothing was there"
}
My rows alway populate with "nothing was there". I have seen answers where people have done CKQueries to update the UI, but in those answers, the user built the query themselves. That seems different from using a CloudKit function like discoverAllIdentities.
I have tried to be as specific as possible in asking this question. If this question could be improved please let me know. I think it's a question that could benefit the community.
Okay, I need to do some more testing, but I think I got it working. Thank you Paulw11 for your comment. It got me on the right track.
As it turns out there were 2 problems.
First, as pointed out I have an asynchronous call inside a for loop. As recommended, I used a dispatchGroup.
Inside the cloudKit call to discoverAllIdentities I declared a dispatchGroup, kind of like so:
var icloudDispatchGroup = DispatchGroup()
Then just inside the for loop that is going to make an async call, I enter the dispatchGroup:
icloudDispatchGroup.enter()
Then just before the end of the publicCloudDatabase.fetch completion handler I call:
icloudDispatchGroup.leave()
and
icloudDispatchGroup.wait()
Which, I believe, I'm still new to this remember, ends the dispatchGroup and causes the current thread to wait until that dispatchGroup finishes before allowing the current thread to continue.
The Above took care of the multithreading issue, but my contactsNearby[String:CLLocation]? Dictionary was still not being populated.
Which leads me to the 2nd problem
At the top of my view controller I declared my Dictionary:
var contactsNearby: [String: CLLocation]?
This declared a dictionary, but does not initialize it, which I don't think I fully realized, so when I attempted to populate it:
self.contactsNearby?["\(contactsName)"] = contactsLocation as? CLLocation
It quietly failed because it is optional and returned nil
So, in viewDidLoad before I even call populateContactsNearby I initialize the dictionary:
contactsNearby = [String:CLLocation]()
This does not make it cease to be an optional, which Swift being strongly typed would not allow, but merely initializes contactsNearby as an optional empty Dictionary.
At least, that is my understanding of what is going on. If anyone has a more elegant solution, I am always trying to improve.
In case you are wondering how I then update the UI, I do so with a property observer on the contactsNearby Dictionary. So the declaration of the dictionary at the top of the view controller looks like this:
var contactsNearby: [String: CLLocation]? {
didSet {
if (contactsNearby?.isEmpty)! || contactsNearby == nil {
return
}else{
DispatchQueue.main.sync {
self.nearbyTableView.reloadData()
}
}
}
}
I suppose I didn't really need to check for empty and nil. So then in cellForRowAtIndexPath I have something kind of like so:
let cell = tableview.dequeueReusableCell(withIdentifier: "nearbyCell", for: indexPath)
if contactsNearby?.isEmpty == false {
let contact = contactsNearby?.popFirst()
cell.textLabel?.text = contact?.key
}else {
cell.textLabel?.text = "Some Placeholder Text Here"
}
return cell
If anyone sees an error in my thinking or sees any of this heading for disaster, feel free to let me know. I still have a lot of testing to do, but I wanted to get back here and let you know what I have found.

Issue with global variables and Alamofire

So, I am trying to do a Alamofire request, then, I'd take the information I need from the JSON data and put it into a global variable, here's my code.
struct myVariables {
static var variableOne = ""
}
func function() {
Alamofire.request(.GET, "API URL").responseJSON { response in
if let rawJSON = response.result.value {
// Here I just take the JSON and put it into dictionaries and parse the data.
myVariables.variableOne = String("data")
}
}
}
Ok, so basically, I am trying to access variableOne's data from another Swift file. Let's say I made two Swift files and in one of those files I had a function that edited the value of global variable, in the other file, if I attempted to print that global variable, I'd see the edited value. But whenever I use Alamofire, when I try to edit a global variable, the other Swift file doesn't see the changed value. So if I tried to edit the global variable within the Alamofire request block of code, I don't see the change whenever I print the variable from another file.
If anyone knows a better way to phrase that, please do correct it.
I suspect the problem isn't that you're not seeing the value change, but rather an issue arising from the fact that you're dealing with an asynchronous method. For example, when you call function, it returns immediately, but your variableOne may not be updated immediately, but rather later. I bet you're checking it before this asynchronous response closure had a chance to be called.
You wouldn't have this problem if, rather than using this "global" (which is a bad idea, anyway), you instead adopted the completion handler pattern yourself.
func performRequest(completionHandler: #escaping (String?, Error?) -> Void) {
Alamofire.request("API URL").responseJSON { response in
switch response.result {
case .failure(let error):
completionHandler(nil, error)
case .success(let responseObject):
let dictionary = responseObject as? [String: Any]
let string = dictionary?["someKey"] as? String
completionHandler(string, nil)
}
}
}
An you'd call this like so:
performRequest() { string, error in
guard let string = string, error == nil else {
// handle error here
return
}
// use `string` here
}
// but not here, because the above closure runs asynchronously (i.e. later)
By using this completion handler pattern, we solve the "how do I know when the asynchronous method is done" problem. And by passing the necessary data back as a parameter of the closure, we can excise the use of globals, keeping the scope of our data as narrow as possible.
Clearly, you should change the parameter of the closure to match whatever is appropriate in your case. But hopefully this illustrates the basic idea.
See previous revision of this answer for Swift 2/Alamofire 3 answer.

Swift - Variable doesn't retain value

I am using AWS to host images for my iOS app. Right now I am trying to list all the objects in an S3 bucket.
Here is my code:
var description = ""
AWSS3.registerS3WithConfiguration(serviceConfig2, forKey: "s3")
AWSS3.S3ForKey("s3").listObjects(objectList).continueWithBlock { (task: AWSTask!) -> AnyObject! in
if task.error != nil {
println(task.error)
}
if task.result != nil {
description = task.result!.description
println(description)
}
return nil
}
println(description == "")
The output is true followed by the correct contents of task.result!.description. In other words, the println outside of the continueWithBlock is printing first and description has not been updated at that time.
How am I supposed to do things with description outside of the continueWithBlock?
You can assign a value that you need to another variable inside the scope of your class or function, then you can call didSet on the variable and carry out another function if you need to, like this:
var someVariableInScopeOfWhereItsNeeded = "abc" {
didSet {
self.maybeSomeOtherFunctionNow
}
}
You asked:
How am I supposed to do things with description outside of the
continueWithBlock
Short answer: You're not.
The whole point of an async method is that it continues immediately, before the time-consuming task has even begun processing. You put the code that depends on the results inside your block. See my answer on this thread for a detailed explanation, including an example project:
Why does Microsoft Azure (or Swift in general) fail to update a variable to return after a table query?
(Don't be fooled by the fact that it mentions MS Azure. It actually has nothing to do with Azure.)
#thefredelement 's solution of using a didSet method on the variable that gets set would work too.

Resources