Swift array empty on return [duplicate] - arrays

I am trying to get learn how to use AlamoFire and I am having trouble.
My method so far is as follows:
func siteInfo()->String?{
var info:NSDictionary!
var str:String!
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {(request, response, JSON, error) in
info = JSON as NSDictionary
str = info["access_key"] as String
//return str
}
return str
}
This returns nil which is a problem. From what I have read here, this is because the request can take a while so the closure doesn't execute till after the return. The suggested solution of moving the return into the closure does not work for me and the compiler just yells (adding ->String after (request,response,JSON,error) which gives "'String' is not a subtype of void"). Same goes for the other solution provided.
Any ideas? Even some source code that is not related to this problem, that uses AlamoFire, would be helpful.
Thanks!

One way to handle this is to pass a closure (I usually call it a completionHandler) to your siteInfo function and call that inside Alamofire.request's closure:
func siteInfo(completionHandler: (String?, NSError?) -> ()) -> () {
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let str = info?["access_key"] as? String // str will be nil if info is nil or the value for "access_key" is not a String
completionHandler(str, error)
}
}
Then call it like this (don't forget error handling):
siteInfo { (str, error) in
if str != nil {
// Use str value
} else {
// Handle error / nil value
}
}
In the comments you asked:
So how would you save the info you collect from the get request if you
can only do stuff inside the closure and not effect objects outside of
the closure? Also, how to keep track to know when the request has
finished?
You can save the result of the get request to an instance variable in your class from inside the closure; there's nothing about the closure stopping you from doing that. What you do from there really depends on, well, what you want to do with that data.
How about an example?
Since it looks like you're getting an access key form that get request, maybe you need that for future requests made in other functions.
In that case, you can do something like this:
Note: Asynchronous programming is a huge topic; way too much to cover here. This is just one example of how you might handle the data you get back from your asynchronous request.
public class Site {
private var _accessKey: String?
private func getAccessKey(completionHandler: (String?, NSError?) -> ()) -> () {
// If we already have an access key, call the completion handler with it immediately
if let accessKey = self._accessKey {
completionHandler(accessKey, nil)
} else { // Otherwise request one
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let accessKey = info?["access_key"] as? String // accessKey will be nil if info is nil or the value for "access_key" is not a String
self._accessKey = accessKey
completionHandler(accessKey, error)
}
}
}
public func somethingNeedingAccessKey() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Use accessKey however you'd like here
println(accessKey)
} else {
// Handle error / nil accessKey here
}
}
}
}
With that setup, calling somethingNeedingAccessKey() the first time will trigger a request to get the access key. Any calls to somethingNeedingAccessKey() after that will use the value already stored in self._accessKey. If you do the rest of somethingNeedingAccessKey's work inside the closure being passed to getAccessKey, you can be sure that your accessKey will always be valid. If you need another function that needs accessKey, just write it the same way somethingNeedingAccessKey is written.
public func somethingElse() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Do something else with accessKey
} else {
// Handle nil accessKey / error here
}
}
}

Related

How to convert string containing array to array in Swift?

I have a string in Swift that contains an array in it. Is it possible to convert the string into an array? All I have found on the internet is converting "abc" to ["a","b","c"], which I would not like to do.
String: "[\"value1\",\"value2\",\"value3\"]"
Result: ["value1","value2","value3"]
I am getting the string from a web request. The code for the request is here:
func webRequest(uri:String)->String{
var value = "";
let request = URLRequest(url: NSURL(string: uri)! as URL)
do {
let response: AutoreleasingUnsafeMutablePointer<URLResponse?>? = nil
let data = try NSURLConnection.sendSynchronousRequest(request, returning: response)
value = String(data: data, encoding: .utf8)!;
} catch _ {
}
return value;
}
First off, the problem here is not converting your string into an array. The problem is getting the array from the web request in the first place.
Let me update your web request function.
func webRequest(url: URL, completion: ([String]?) -> () { // I have updated this function to be asynchronous
let dataTask = URLSession.shared.dataTask(with: url) {
data, urlResponse, error in
// you might want to add more code in here to check the data is valid etc...
guard let data = data,
let arrayOfStrings = JSONDecoder().decode([String].self, from: data) else {
// something went wrong getting the array of strings so return nil here...
completion(nil)
return
}
completion(arrayOfStrings)
}
dataTask.resume()
}
Using this code instead of the code in your question you now have an asynchronous function that will not block the app and one that will pass your array of strings into the completion.
You can now run it like this...
webRequest(url: someURL) { strings in
guard let strings = strings else {
// strings is nil because something went wrong with the web request
return
}
print(strings)
}
Creating the URL
In your question you have this code... NSURL(string: someString)! as URL
You can change this to... let url = URL(string: someString)
Quick side note
Careful where you find tutorials and using code you find on the web. The code used in this question is very old. (at least 4 or 5 years "out of date").
If you're looking for tutorials to help with Swift then some recommendations are...
Ray Wenderlich
Hacking with swift

How to access/manipulate a mutable array passed as parameter within a function in Swift 4.2x?

I have the following code below:
var rootTasks: [Task]?
func loadRootTasks() {
rootTasks == nil ? rootTasks = [Task]() : rootTasks?.removeAll() // removeAll() works here
TasksManager.loadTasks(parentTaskIDString: "0", tasks: &rootTasks!)
}
static func loadTasks(parentTaskIDString: String, tasks: inout [Task]) {
let urlString = Config.httpsProtocol + "://" + Config.server + Config.portString + "/" + TasksManager.getTasksEndpoint + "/" + parentTaskIDString
let url = URL(string: urlString)
var urlRequest = URLRequest(url: url!)
urlRequest.setValue(AccountsManager.sharedInstance.securityAccessToken, forHTTPHeaderField: "Authorization")
let defaultSession = URLSession(configuration: .default)
let getTasksTask = defaultSession.dataTask(with: urlRequest, completionHandler: { (data, response, error) in
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
print("GetTasks response status code != 200")
return
}
guard error == nil else {
print("GetTasks error")
return
}
guard let jsonData = data else {
print("GetTasks did not receive JSON data")
return
}
do {
// PROBLEM is here:
// compiler flags "Escaping closures can only capture inout parameters explicitly by value"
tasks.removeAll() // removeAll() does not work here
// same error here
tasks = try JSONDecoder().decode([Task].self, from: jsonData)
NotificationCenter.default.post(name: .rootTasksRefreshed, object: nil, userInfo: nil)
}
catch {
print("GetTasks JSON parsing exception")
}
})
getTasksTask.resume()
}
The problem is in the line "// PROBLEM ...". The compiler flags the "Escaping closures can only capture inout parameters explicitly by value" error.
The function "loadTasks" is a static method, which is called by "loadRootTasks". It needs to pass a "tasks" array which is a member variable, and needs to be modified from within the static method after the asynchronous method dataTask() runs.
How do I resolve the problems to be able to "tasks.removeAll()", etc?
I have read other posts, but there are not for Swift 4.2. I need help specifically for Swift 4.2. Thanks!
You cannot manipulate an inout parameter in asynchronous code.
In this case, though, there's no need to do so. Don't pass rootTasks to your loadTasks method at all. First, make your loadTasks method an instance method instead of a static method. Now your loadTasks method can see rootTasks, directly. So it can just change it, directly!
So, at that point, there's no need to say
tasks.removeAll() // removeAll() does not work here
Just say
self.rootTasks.removeAll()
And so on.
(However, your asynchronous code should probably take care to touch self.rootTasks only on the main thread.)
If you don't want to do that — that is, if you insist on leaving loadTasks as a static method — then you will have to do this in a normal way: loadTasks must take a completion handler which it will then call, as a way of passing the array back to the original caller asynchronously.

array keep return empty [swift} [duplicate]

I am trying to get learn how to use AlamoFire and I am having trouble.
My method so far is as follows:
func siteInfo()->String?{
var info:NSDictionary!
var str:String!
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {(request, response, JSON, error) in
info = JSON as NSDictionary
str = info["access_key"] as String
//return str
}
return str
}
This returns nil which is a problem. From what I have read here, this is because the request can take a while so the closure doesn't execute till after the return. The suggested solution of moving the return into the closure does not work for me and the compiler just yells (adding ->String after (request,response,JSON,error) which gives "'String' is not a subtype of void"). Same goes for the other solution provided.
Any ideas? Even some source code that is not related to this problem, that uses AlamoFire, would be helpful.
Thanks!
One way to handle this is to pass a closure (I usually call it a completionHandler) to your siteInfo function and call that inside Alamofire.request's closure:
func siteInfo(completionHandler: (String?, NSError?) -> ()) -> () {
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let str = info?["access_key"] as? String // str will be nil if info is nil or the value for "access_key" is not a String
completionHandler(str, error)
}
}
Then call it like this (don't forget error handling):
siteInfo { (str, error) in
if str != nil {
// Use str value
} else {
// Handle error / nil value
}
}
In the comments you asked:
So how would you save the info you collect from the get request if you
can only do stuff inside the closure and not effect objects outside of
the closure? Also, how to keep track to know when the request has
finished?
You can save the result of the get request to an instance variable in your class from inside the closure; there's nothing about the closure stopping you from doing that. What you do from there really depends on, well, what you want to do with that data.
How about an example?
Since it looks like you're getting an access key form that get request, maybe you need that for future requests made in other functions.
In that case, you can do something like this:
Note: Asynchronous programming is a huge topic; way too much to cover here. This is just one example of how you might handle the data you get back from your asynchronous request.
public class Site {
private var _accessKey: String?
private func getAccessKey(completionHandler: (String?, NSError?) -> ()) -> () {
// If we already have an access key, call the completion handler with it immediately
if let accessKey = self._accessKey {
completionHandler(accessKey, nil)
} else { // Otherwise request one
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let accessKey = info?["access_key"] as? String // accessKey will be nil if info is nil or the value for "access_key" is not a String
self._accessKey = accessKey
completionHandler(accessKey, error)
}
}
}
public func somethingNeedingAccessKey() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Use accessKey however you'd like here
println(accessKey)
} else {
// Handle error / nil accessKey here
}
}
}
}
With that setup, calling somethingNeedingAccessKey() the first time will trigger a request to get the access key. Any calls to somethingNeedingAccessKey() after that will use the value already stored in self._accessKey. If you do the rest of somethingNeedingAccessKey's work inside the closure being passed to getAccessKey, you can be sure that your accessKey will always be valid. If you need another function that needs accessKey, just write it the same way somethingNeedingAccessKey is written.
public func somethingElse() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Do something else with accessKey
} else {
// Handle nil accessKey / error here
}
}
}

Storing array from Parse into local array

When I try to store arrays from Parse into a local array I can only access it within the findObjectsInBackgroundWithBlock {...}. When I print it outside of that block, it shows []...
Code :
var qArray : [[Int]] = []
override func viewDidLoad() {
super.viewDidLoad()
let query = PFQuery(className: "Trivia")
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) in
if objects != nil {
if let objects = objects {
for object in objects {
self.qArray.append(object["mainPattern"] as! [Int])
}
print(self.qArray) // Prints a multi dimension array
}
}
if error != nil {
print(error)
}
}
print(self.qArray) // prints []
}
It's most likely because the array hasn't been populated yet because it's running in the background. You can try using dispatch_group to circumvent this issue.
I think you're misunderstanding what findInBackground means. It means the code after the callback continues to execute, so it calls query.findInBackground.... and then it continues with the next line, which is print(self.qArray). At some point later on, it hears back from the database and it executes all the code inside the Callback, which is when the array finally gets populated.

Retrieving values from an empty array

I want the user to upload an image, and other users leave replies to that image. Everything works fine so far except if the row for that specific image object is empty, the app crashes.
fatal error: unexpectedly found nil while unwrapping an Optional value
Here is my code :
override func viewDidLoad() {
super.viewDidLoad()
var error = ""
var query = PFQuery(className:"Posts")
query.getObjectInBackgroundWithId(cellID) {
(objects: PFObject!, error: NSError!) -> Void in
if error == nil {
var array = objects.objectForKey("replies") as [String] // <- when error occurs the compiler point here.
for object in array {
self.repliesArray.append(object as String)
}
} else {
self.displayError("Error", error: "Error retreiving")
}
self.tableView.reloadData()
}
}
Does this work? You can't append null objects to an array so this appends an empty string instead of null. Also you need the explanation mark so that it can be nil.
var array = objects.objectForKey("replies") as [String!]
for object in array {
if object != nil {
self.repliesArray.append(object as String)
}
else {
self.repliesArray.append("")
}
}
query.getObjectInBackgroundWithId(cellID) {
(objects: PFObject!, error: NSError!) -> Void in
if error == nil {
var array = objects.objectForKey("replies") as [String] // <- when error occurs the compiler point here.
The key here is that you're accepting an implicitly unwrapped optional (PFObject!) which is implying a promise that it will never be nil, but is nil.
Ideally (*), the type should be (objects: PFObject?, error: NSError?) -> Void to make the optionals explicit and force you to do the nil checking that is required. I believe you can do this even if the caller claims to send you (PFObject!, NSError!) -> Void, since I think Swift will make the implicit->explicit conversion for you.
If that's still impossible, then you will have to manually verify that objects is not nil before using it. (This is unlike #Dehli's solution, which checks that the things contained in objects are non-nil. That's not the problem; the problem is that objects itself is nil.)
(*) I say "ideally" here in terms of the existing interface. Passing a tuple of optional value/error is a lousy pattern. The better solution is to use something like a Result object.

Resources