Swift Update ViewController Variable with results of dataTaskWithRequest - ios

I have seen similar posts on this, but nothing that is helping me out. I can not for the life of me determine why I can't extract data from my task. I am trying to update an object (myUser) with data obtained from a SOAP XML response from a Web Service I built. I am new to Swift and IOS, but I've done this with C#. I can update the label on the viewcontroller with the results from the web service, but not a class variable. Thanks in advance for your help!
class FirstViewController: UIViewController {
#IBOutlet weak var label: UILabel! //CAN UPDATE THIS FROM TASK
var myUser = User() //CAN NOT UPDATE THIS FROM TASK
func soapRequest(){
...
let task = session.dataTaskWithRequest(request) {(data, response, error)-> Void in
do{ xmlResponse = try AEXMLDocument(xmlData: data!)}
catch{print("\(error)")}
//Example of the data I am trying to send to the ViewController object
let firstName = xmlResponse.root["element1"].stringValue
//I've tried this
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.myUser.firstName = firstName //This does not work
self.label.text = firstName //This DOES work
})
}
task.resume()
}
}

Dan's comment was the correct solution to my issue. The code was executing correctly, but since the request is asynchronous, it wasn't finishing in time and I assumed it was broken.
Your network request is asynchronous, it won't have finished yet when
you print the value in viewDidLoad so the value won't have been set
yet. – dan 1 hour ago

Related

Alamofire could not update class property data

Hi i am trying to use alamofire to download json weather data. Here is my code, the working version:
class WeatherModel {
private var _date: String?
private var _location: String?
private var _weatherType: String?
private var _temperature: Double?
func getWeatherInfoFromAPI(completed: #escaping ()-> ()) {
let url = URL(string: WEATHER_URL)!
Alamofire.request(url).responseJSON(completionHandler: { response in
// Test updating data
self._temperature = 25
self._weatherType = "Clear"
self._location = "Vietnam"
completed()
})
}
}
-> This way, i am able to update the property of the class.
Failing to update class property version of getWeatherInfoFromAPI func:
func getWeatherInfoFromAPI(completed: #escaping ()-> ()) {
let url = URL(string: WEATHER_URL)!
Alamofire.request(url).responseJSON{ response in
// Test updating data
self._temperature = 25
self._weatherType = "Clear"
self._location = "Vietnam"
}
completed()
}
So, i dont know what is the difference between them. Please help me to clarify between 2 ways here.
Alamofire.request(url).responseJSON(completionHandler: { response in })
and
Alamofire.request(url).responseJSON{ response in }
What is the reason that my code does not work? Since i see the Alamofire docs also use like the second way! I am thinking about thread difference between them
Also, how do i know what thread the code is running in responseJSON?
Thanks, i appreciate your time and help!
Those two ways are functionally identical, the second one just uses Swift's trailing closure syntax.
What do you do in completed()? Because in first example, you are calling it upon completion of network call, and in second case you are calling it immediately after you start the network call - the call is not completed yet. You should call if in Alamofire callback, like in first example. In second example, if you're inspecting those properties inside completed, then it's no wonder they're not updated yet.

TableViewController search results don't load properly because of a lag

So I have an app that pulls up movie results when I type in a search. (Like IMDb.) I use a free API from themoviedb.org to load the results. I load them in a TableViewController. I load the posters for the results using a mod on the .dataTaskWithRequest method. to make it synchronous. Other than that, it's just basic API sending and receiving for the titles, genres, and years of the movies or TV Shows.
Now my app lags when I type too fast, this isn't completely because of the synchronous loading, because it still happens when I don't load images at all, but image loading makes the app lag, too. Now this is an issue in and of itself, but the problem is that when the app loads the words on to the screen, and is done with the lag, the results are the results of part of the word I have on screen. For example, if I type "The Simpsons" too fast, I get results for "The Sim", but if I backspace once, and retype "The Simpsons", the results reload correctly. Something that complicates things even more, is that sometimes I get the top result only being one of the old, partial results, and the rest are normal and loaded underneath.
Here is a video explaining the situation. The first time i type down "the simpsons", you can see the lag. I typed it all really fast, but it lags past the word "the". When it is done loading, it loads up a beowulf result that shouldn't even be there. I have no idea what's going on and it's driving me nuts. Even when I don't load images, and the typing doesn't lag, the results still don't update.
Here are the relevant code snippets, if you want any more, feel free to ask. I just don't want to bombard you with too much code at once:
This updates search results when text is typed in search bar:
extension SearchTable : UISearchResultsUpdating {
func updateSearchResultsForSearchController(searchController: UISearchController) {
//To Handle nils
var searchBarText = searchController.searchBar.text
if (searchBarText == nil) {
searchBarText = ""
}
searchBarText! = searchBarText!.condenseWhitespace()
//To Handle Disallowed Characters
searchBarText = searchBarText!.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
//Find Results from themoviedb
let urlString = "https://api.themoviedb.org/3/search/multi?query=" + searchBarText! + "&api_key= (I can't post the api key publicly online, sorry)"
let results = NSURL(string: urlString)
if (results == nil) {
//Server Error
}
//Wire Up Results with matchingItems Array
let task = NSURLSession.sharedSession().dataTaskWithURL(results!) { (data, response, error) -> Void in
if let jsonData = data {
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)
if var results = jsonData["results"] as? [NSDictionary] {
if results.count > 0 {
//Clean out non-english results:
//I wrote the function, it shouldn't be the source of the lag, but I can still provide it.
self.cleanArray(&results)
self.matchingItems = results
} else {
self.matchingItems = []
}
}
} catch {
//JSON Serialization Error
}
}
}
task.resume()
self.tableView.reloadData()
}
}
Then, after I get the results, I reload the table using the two required methods from a TableViewDataSource:
//Table Data Source
extension SearchTable {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return matchingItems.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell")! as! CustomCell
//Safe-Guard. This shouldn't be needed if I understood what I was doing
if (indexPath.row < matchingItems.count) {
cell.entry = matchingItems[indexPath.row] //404
//Name & Type & Year
//This is only for TV Shows, I removed the rest for simplicity
cell.title.text = matchingItems[indexPath.row]["name"] as? String
cell.icon.image = UIImage(named: "tv.png")
let date = (matchingItems[indexPath.row]["first_air_date"] as? String)
cell.year.text = date == nil ? "" : "(" + date!.substringToIndex(date!.startIndex.advancedBy(4)) + ")"
//Genre
//Code here removed for simplicity
//Poster
cell.poster.image = UIImage(named: "Placeholder.jpg")
if let imagePath = matchingItems[indexPath.row]["poster_path"] as? String {
let url = NSURL(string: "http://image.tmdb.org/t/p/w185" + imagePath)
let urlRequest = NSURLRequest(URL: url!)
let session = NSURLSession.sharedSession()
//Synchronous Request
let semaphore = dispatch_semaphore_create(0)
let task = session.dataTaskWithRequest(urlRequest) { data, response, error in
if let poster = UIImage(data: data!) {
cell.poster.image = poster
}
dispatch_semaphore_signal(semaphore)
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
}
return cell
}
}
Thanks!
First of all, I strongly recommend you don't use synchronous request, mainly because it blocks your UI until it finish and this is a very bad responsiveness for an app.
In your case you can place a placeholder for the UIImage and when the request finish substitute it for the correct image.
Regarding your issue of typing faster, it's called throttle or debounce, Apple recommends:
Performance issues. If search operations can be carried out very rapidly, it is possible to update the search results as the user is typing by implementing the searchBar:textDidChange: method on the delegate object. However, if a search operation takes more time, you should wait until the user taps the Search button before beginning the search in the searchBarSearchButtonClicked: method. Always perform search operations a background thread to avoid blocking the main thread. This keeps your app responsive to the user while the search is running and provides a better user experience.
But if you until want it to handle yourself you can see this two good answers explaining how to handle it correctly:
How to throttle search (based on typing speed) in iOS UISearchBar?
How can I debounce a method call?
I recommend you handle it as Apple recommends or you can change your philosophy and adopt some libraries that handle it for your automatically like:
Bond
RxSwift
The first one in more easy to learn, the second one needs to learn Reactive Programming and concepts of Functional Programming, It's up to you.
I hope this help you.
Just for people who may be struggling in the future with this same issue. First of all, read my comment to Victor Sigler's answer.
Here were the issues:
1 - I searched for the results online using .dataTaskWithURL() This is an asynchronous method which ran in the background while the code continued. So on occasion, the table would reload before the new results were in. See this thread for more information. I highly recommend checking this tutorial on concurrency if you are serious about learning Swift:
www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1
2 - The images lagged because of the search being synchronous, as Victor said. His answer pretty much handles the rest, so read it!

Download multiple small files in background state

Can anyone suggest a solution for downloading multiple files say 100s, in background. The important thing being that the download url has a life span of 15 minutes, so it's required that we fetch a download url and then start downloads. We can't prefetch all urls and add it to download task, as that may lead to download failures of expired url after few successful downloads.
Thanks in advance.
you can do the following:
var data : NSData?{
didSet{
//Parse the data to any thing you want
}
}
var urlFetchedAsString : String? {
didSet{
if(urlFetchedAsString == nil)
return
let url : NSURL = NSURL(string: urlFetchedAsString!)!
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
() -> Void in
data = NSData(contentsOfURL: url)
})
}
}
func fetchURL() ->String{
//Fetched Your url and return
}
override func viewDidLoad(){
super.viewDidLoad()
urlFetchedAsString = fetchURL()
}
Explanation
The OS will execute the didSet block in the variable urlFetchedAsString each time it is set
The didSet block will fetch the data from the url and save them as NSData
The dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0) means that fetching data from url will be done on the different thread to prevent blocking the UI thread
After done saving you, the variable data will be set and the didSet for this variable will be executed. In this block you can implement your parsing algorithm.
Fetching the url itself is up to you because you didn't clarify where they are or how will u get them
Note
Here i assumed that you don't need to copy of all the urls because as you said they will expire in 15 mins

Realm Threading Confusion

So I'm working on setting up a background queue that does all realm writes on its own thread. I've run into some strange issues I can't figure out.
Issue #1
I'm not sure if this is related (see post: Xcode debug issues with realm) but I do have an apparent mismatch with my lldbg output as to whether a certain field:
messages element
My DataTypes
OTTOSession
class OTTOSession : Object {
dynamic var messages : MessageList?
dynamic var recordingStart : Double = NSDate().timeIntervalSince1970
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
}
MessageList
public class MessageList : Object {
dynamic var date : NSDate = NSDate();
dynamic var test : String = "HI";
let locationMessages = List<LocationMessage>()
let ahrsMessages = List<AHRSMessage>()
// let statusMessages = List<StatusMessageRLM>()
let logMessages = List<LogMessage>()
}
Realm Interactions
In my code I create my new OTTOSession in a code block on my realmQueue
internal var realmQueue = dispatch_queue_create("DataRecorder.realmQueue",
DISPATCH_QUEUE_SERIAL)
All realm calls are done on this realmQueue thread
dispatch_async(realmQueue) {
self.session = OTTOSession()
}
I've also tried different variants such as:
dispatch_async(realmQueue) {
self.session = OTTOSession()
// Directly making a message list
self.session!.messages = MessageList()
//Making a separate message list var
self.messages = MessageList()
self.session!.messages = self.messages
}
The reason I've played around with the MessageList is that I cant tell from the debugger whether the .messages variable is set or not
Recording
Once I signal to my processes I want to start recording I then actually make the write calls into Realm (which I'm not 100% sure i'm doing correctly)
dispatch_async(realmQueue){
// Update some of the data
self.session!.recordingStart = NSDate().timeIntervalSince1970
// Then start writing the objects
try! Realm().write {
// I've tried different variants of:
let session = self.session!
try! Realm().add(self.session!)
// Or
try! Realm().add(self.session!)
// or
let session = self.session!
session.messages = MessageList()
session.messages!.ahrsMessages
try! Realm().add(self.session!)
try! self.session!.messages = Realm().create(MessageList)
try! Realm().add(self.session!.messages!)
print ("Done")
}
}
Basically I've tried various combinations of trying to get the objects into realm.
Question: When adding an object with a one-to-one relationship do I have to add both objects to Realm or will just adding the parent object cause the related object to also be added to realm
Adding Data
Where things start to go awry is when I start adding data to my objects.
Inside my OTTOSession Object I have the following function:
func addLocationMessage(msg : dmParsedMessage) -> LocationMessage {
let dmsg : dmLocationMessage = msg as! dmLocationMessage
let locMsg = LocationMessage(locMsg: dmsg)
// THIS LINE IS CAUSING A 'REALM ACCESSED FROM INCORRECT THREAD ERROR
self.messages!.locationMessages.append(locMsg)
return locMsg;
}
I'm getting my access error on this line:
self.messages!.locationMessages.append(locMsg)
Now the function call itself is wrapped in the following block:
dispatch_async(realmQueue) {
try! Realm().write {
self.session?.addLocationMessage(msg)
}
}
So as far as I can tell by looking at the debugger - and by looking at the code - everything should be running inside the right thread.
My queue is SERIAL so things should be happening one after another. The only thing I can't figure out is when I break at this point the debugger does show that messages is nil but I cant trust that because:
Question
So my question is two fold
1) Is my code for adding an object into the RealmDB correct. i.e. do I need to make two separate Realm().add calls for both the OTTOSession and the MessageList or can I get away with a single call
2) Is there anything that pops out to explain why I'm getting a thread violation here - should doing all my realm writing calls on a single thread be enough ?
1) No, you don't need to make two separate calls to Realm.add(). When you add an object to a Realm all related objects are persisted as well.
2) Your thread violation very likely originates from the fact that dispatch queues make no guarantee over the thread on which they are executed on (beside the main queue). So that means your Realm queue is executed on different threads. You will need to make sure to retrieve your session object from a Realm opened on this thread. You might want to use primary keys for that purpose and share those between queues / threads.

Apple Swift - Unable to retrieve data from CMPedometer's handler

I am trying out Swift with some of the new iOS 8 API's and have tried the best part of the day to get the CMPedometer queryPedometerDataFromDate API to return any data within the handler. I believe its an error on my part, getting a little confused with the syntax.
Here is my code, with comments on what prints out:
var ped = CMPedometer()
var stepsTaken = NSNumber(int: 0)
println(dateNow) // 2014-06-07 21:23:55 +0000
println(dateMidnight) // 2014-06-07 00:00:00 +0000
ped.queryPedometerDataFromDate(dateMidnight, toDate: dateNow, withHandler:{
data, error in
println("Test1") // Does not print
println(error) // Does not print
stepsTaken = data.numberOfSteps
})
println("My Int Value \(stepsTaken)") // My Int Value 0
It works for me with CMPedometer queryPedometerDataFromDate, if i define the CMPedometer as a class wide constant:
let pedometer = CMPedometer()
and then in a func i'm using:
self.pedometer.queryPedometerDataFromDate(today, toDate: now, withHandler: ...
A code sample from Hipster.
import CoreMotion
let lengthFormatter = NSLengthFormatter()
let pedometer = CMPedometer()
pedometer.startPedometerUpdatesFromDate(NSDate(), withHandler: { data, error in
if !error {
println("Steps Taken: \(data.numberOfSteps)")
var distance = data.distance.doubleValue
println("Distance: \(lengthFormatter.stringFromMeters(distance))")
var time = data.endDate.timeIntervalSinceDate(data.startDate)
var speed = distance / time
println("Speed: \(lengthFormatter.stringFromMeters(speed)) / s")
}
})
After many hours playing around with the syntax, thinking I have made a rookie error, I tried alternative API's and found that the following works to grab pedometer data:
var ped2 = CMStepCounter()
ped2.queryStepCountStartingFrom(dateMidnight, to: dateNow, toQueue: NSOperationQueue(), withHandler:{data, error in
println("Test 2") // "Test 2"
println(data) // 491 (I really should go for a walk!)
println(error) // nil
})
I will file a radar with Apple as it looks like a bug in the new API. Also the CMStepCounter class is due to be deprecated for CMPedometer. From CMStepCounter.h:
NS_CLASS_DEPRECATED_IOS(7_0,8_0,"Use CMPedometer instead")
You're querying the data, which goes onto another thread. The execution continues after your call, and your NSNumber is still at 0. By the time you've come back you've printed the message a long time ago, and have no way of recovering the stepsTaken.
You need to keep a reference to your ped variable. Something like:
self.ped = CMPedometer();
Currently your ped variable is going out of scope before the handler is even called.

Resources