Load data with NSURLSession and closure iOS - ios

i use this function to get the link of image but i just have the variable in the initialization.
func getLinkImage(link_news: String, separator: String) -> String {
let url = NSURL(string: link_news)
var link_image_news = "http://www.website.com"
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { (data, response, error) -> Void in
if error == nil {
let urlContent = NSString(data: data!, encoding: NSUTF8StringEncoding)
//print(urlContent)
let urlContentArray = urlContent?.componentsSeparatedByString(separator)
// search link's image
print("----------Link's Image----------")
var image_news = urlContentArray?[1].componentsSeparatedByString("<img alt=")
image_news = image_news?[1].componentsSeparatedByString("src=")
image_news = image_news?[1].componentsSeparatedByString("\"")
link_image_news = "http://www.website.com" + image_news![1]
print("the link of image is : "+link_image_news)
// end of search link's image
}
else {
print("Error in the Image News load from Website")
print(url!)
}
}
task.resume()
return link_image_news
}
when i call the function, i have only the initialization value (link_image_news = http://www.website.com), after many seconds i have the print with right value (the link of image).
i think it's issue with response time of server. how can i solve this ?
i found some stuffs with closure (completion) but i dont really understand how it's works, im new in Swift

Here's the deal:
An NSURLSession "task" takes a block of code that it calls once the response from the server has been completely received.
When you call task.resume() that call returns immediately, before iOS has even begun sending the request to the remote server. What you need to do is to rewrite your getLinkImage function to not return a value, and to take a completion block as a parameter.
Make that completion block take a string as a parameter. Make your getLinkImage function call the completion block from inside the data task's completion block, wrapped in a dispatch_async that invokes the completion block on the main thread.
Edit:
Your modified getLinkImage method might look like this:
func getLinkImage(
link_news: String,
separator: String,
completion: (ok: Bool, resultString: String?) -> ()
)
{
let url = NSURL(string: link_news)
var link_image_news = "http://www.website.com"
let task = NSURLSession.sharedSession().dataTaskWithURL(url!)
{
(data, response, error) -> Void in
if error == nil {
let urlContent = NSString(data: data!,
encoding: NSUTF8StringEncoding)
//print(urlContent)
let urlContentArray =
urlContent?.componentsSeparatedByString(separator)
// search link's image
print("----------Link's Image----------")
var image_news =
urlContentArray?[1].componentsSeparatedByString("<img alt=")
image_news = image_news?[1].componentsSeparatedByString("src=")
image_news = image_news?[1].componentsSeparatedByString("\"")
link_image_news = "http://www.website.com" + image_news![1]
print("the link of image is : "+link_image_news)
// end of search link's image
dispatch_async(dispatch_get_main_queue())
{
//We now have the string, so pass it to the completion block
completion(true, link_image_news);
{
}
else {
print("Error in the Image News load from Website")
print(url!)
dispatch_async(dispatch_get_main_queue())
{
//There was an error, so pass nil to completion
completion(false, nil);
{
}
task.resume()
}

Related

How can I stop URLSessionTask when the Internet is disconnected?

I am using URLSessionTask to get the source code of url. When the internet is connected, it works well.
However, when the Internet is disconnected, I try building. And in simulator it is blank and the cpu is 0%. What affects is that My Tab Bar Controller is also missing and blank (It is my initial view controller). It seems that this task is under connecting?
I want the data received from dataTask, so I use semaphore to make it synchronous. Otherwise, as dataTask is an asynchronous action, what I
get is an empty string.
How can I fix this problem?
Thanks!
let urlString:String="http://www.career.fudan.edu.cn/jsp/career_talk_list.jsp?count=50&list=true"
let url = URL(string:urlString)
let request = URLRequest(url: url!)
let session = URLSession.shared
let semaphore = DispatchSemaphore(value: 0)
let dataTask = session.dataTask(with: request,
completionHandler: {(data, response, error) -> Void in
if error != nil{
errorString = "Error!"
}else{
htmlStr = String(data: data!, encoding: String.Encoding.utf8)!
//print(htmlStr)
}
semaphore.signal()
}) as URLSessionTask
//start task
dataTask.resume()
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
Update: As #Moritz mentioned, I finally use completion handler (callback).
func getforData(completion: #escaping (String) -> ()) {
if let url = URL(string: "http://XXXXX") {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data, let getString = String(data: data, encoding: String.Encoding.utf8), error == nil {
completion(getString)
} else {
print("error=\(error!.localizedDescription)")
}
}
task.resume()
}
}
And in viewdidload
override func viewDidLoad() {
super.viewDidLoad()
getforData { getString in
// and here we get the "returned" value from the asynchronous task
print(getString) //works well
//tableview should work in main thread
DispatchQueue.main.async {
self.newsTableView.dataSource = self
self.newsTableView.delegate = self
self.newsTableView.reloadData()
}
}

Calling methods in swift (Method doesnt finish before next line)

Trying to pull in some JSON data from an API and then save that to core data.
My current method of doing this is to pull in the JSON data and return that array which ill then iterate and save to core data.
Pull in Data: (Works fine)
func getPlayerDataFromAPI() -> [Dictionary<String,AnyObject>]{
let url: String = "http://api.fantasy.nfl.com/v1/players/stats?"
let request : NSMutableURLRequest = NSMutableURLRequest()
var jsonData = [Dictionary<String,AnyObject>]()
request.HTTPMethod = "GET"
request.URL = NSURL(string: url)
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as? NSDictionary
if (jsonResult != nil) {
if let playerData = jsonResult?["players"] as? [Dictionary<String, AnyObject>]{
jsonData = playerData
print(jsonData.count)
}
} else {
print("No Data")
}
}
catch {
print("Error Occured")
}
}.resume()
return jsonData;
}
And then I wanted to test the returned Dictionary to ensure it was being populated:
func saveData(){
let players = getPlayerDataFromAPI()
print(players.count)
}
I call saveData() in the viewController viewDidLoad method and get an empty dictionary... Moments later, the print statement in the JSON function prints.
0
1427
Is there a reason the getPlayerDataFromAPI() function doesnt finish before the print(count) is being called? Do I have this wrong logically? I always get an empty dictionary returned in this instance and thats no good.
You're trying to synchronously return the results of an asynchronous function. session.dataTaskWithRequest is passed a closure, which doesn't execute until the request completes. So your jsonData = playerData statement doesn't get executed until after your getPlayerDataFromAPI() function has already returned (at which point jsonData is still the empty dictionary you defined at the beginning of the function).
One way to do what you're trying to do is to allow a closure to be passed in to your function; something like this (I haven't tested this code):
func getPlayerDataFromAPI(completion: (data: [String: AnyObject]) -> Void)
Then, at the point you assign jsonData = playerData, you can "return" the data to the caller like this:
completion(data: jsonData)
Calling this function would look something like this:
getPlayerDataFromAPI() { (data) -> Void in
print(data)
}

Making a re-useable function of JSON URL fetching function in SWIFT 2.0

I am stuck in a problem. I think it is all due to my weak basics. I am sure someone can help me easily and put me in the right direction.
I have different segues and all get the data from JSON via remote URL.
So in-short all segues need to open URL and parse JSON and make them into an ARRAY
I have made the first segue and it is working fine.
Now i plan to use the functions where it download JSON and turns it into ARRAY as a common function
I read in another page on stackoverflow that I can declare all common functions outside the class in ViewController
I hope everyone is with me this far.
now in ViewController i declare a function
getDataFromJson(url: String)
This function code looks like following
func getJsonFromURL(url: String)
{
// some class specific tasks
// call the common function with URL
// get an array
let arrJSON = getJsonArrFromURL(url)
for element in arrJSON
{
// assign each element in json to ur table
print("Element: \(element)")
}
// some class specific tasks
}
and this will call the common function declared outside the score of class
getArrFromJson(url: String) -> NSArray
This common function is just very generic.
Take a URL, call it, open it, parse its data into ARRAY and return it back.
The problem i am stuck is where to put the return
It returns empty array as the task is not finished and i am clueless
func getJsonArrFromURL(var url: String) -> NSArray
{
var parseJSON : NSArray?
if ( url == "" )
{
url = self.baseURLHomepage
}
print("Opening a JSON URL \(url)")
let myUrl = NSURL(string: url);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
let postString = "";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
if ( error != nil )
{
print("Error open JSON url \n\(error)")
return
}
do
{
parseJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.MutableContainers) as? NSArray
}
catch
{
self.showAlert("Error", msg: "Error occurred while trying to process the product information data")
print("Error occured in JSON = \(error)")
}
}
task.resume()
return parseJSON!
}
You can probably add a method like below in any of your class
func post(url: String, info: String, completionHandler: (NSString?, NSError?) -> ()) -> NSURLSessionTask {
let URL = NSURL(string: url)!
let request = NSMutableURLRequest(URL:URL)
request.HTTPMethod = "GET"
let bodyData = info
request.HTTPBody = bodyData.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
dispatch_async(dispatch_get_main_queue()) {
guard data != nil else {
print("response String is nil")
completionHandler(nil, error)
return
}
if let dataNew = data {
completionHandler(NSString(data: (NSData(base64EncodedData: dataNew, options: NSDataBase64DecodingOptions([])))!, encoding: NSASCIIStringEncoding), nil)
}
}
}
task.resume()
return task
}
and access it anywhere like
let url = "your URL String"
let info = "The data you would like to pass"
yourClassName.post(url, info: info) { responseString, error in
guard responseString != nil else {
print("response String is nil")
print(error)
return
}
do {
if !(responseString as? String)!.isEmpty {
let json = try NSJSONSerialization.JSONObjectWithData((responseString as! String).data, options: NSJSONReadingOptions.init(rawValue: 0))
//process your json here
}
} catch {
print("Error\n \(error)")
return
}
}
Extend your string like follows
extension String {
var data:NSData! {
return dataUsingEncoding(NSUTF8StringEncoding)
}
}

iOS - calling Webservice and parsing JSON in Swift

I am using NSURLSession to call my own Webservice which returns JSON, works fine with this code:
func getJSONFromDatabase(){
let url = NSURL(string: "http://www.myurl/mysqlapi.php")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
self.json = NSString(data: data!, encoding: NSUTF8StringEncoding)
print(self.json)
}
task.resume()
}
However, it seems that this Task is not executed in the right order, because when i run the following function after the "getJSONFromDatabase" function, the "print" statement in the Task is executed after the "print" statement from the "parseJSON" function.
func parseJSON(){
let data = self.json.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as! NSArray
for event in json {
let name = event["name"]
let startDate = event["startDate"]
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = dateFormatter.dateFromString(startDate as! String)
if date != nil {
self.events.append(Event(name: name as! String, startDate: date!))
}
else {
print("Date is nil")
}
}
for event in self.events {
print(event.name)
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
}
My goal is to save the JSON Data in an Object Array of "Event", but this doesn't seem to work because when i iterate over my "self.events" array, it is empty.
Another problem is: When i split this 2 things like i posted here (2 functions), the "parseJSON" throws an error:
Failed to load: The data couldn’t be read because it isn’t in the correct format.
However, if i add the content of "parseJSON" into the Task of the "getJSONFromDatabase" function, there is no such error, but the array is still empty
dataTaskWithURL is asynchronous so you your code won't execute from the top down. Use a completion handler to work on the result from the asynchronous call.
func getJSONFromDatabase(success: ((json: String)->())){
let url = NSURL(string: "http://www.myurl/mysqlapi.php")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
let json = NSString(data: data!, encoding: NSUTF8StringEncoding)
success(json: json)
}
task.resume()
}
in use
getJSONFromDatabase(success: {json in
//do stuff with json
})

Return a string from a web scraping function in swift

Ok so I am scraping some basic data of a web page. I wanted to refactor out my code to another class and return a string from what I retrieved but it is difficult with the asynchronous function and I'm new with swift.
I now realize that this function is incapable of returning a string but I can't quite figure out how to configure the completion handler and how to call the function after from the main class using the completion handler.
Any help would be greatly appreciated, thanks.
func getNameFromProfileUrl(profileUrl: NSURL) -> String {
var playerName = ""
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl, completionHandler: { (data, response, error) -> Void in
if error == nil {
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding) as NSString!
var urlContentArray = urlContent.componentsSeparatedByString("<title>")
var statusArray = urlContentArray[1].componentsSeparatedByString("</title>")
playerName = statusArray[0] as! String
}
})
task.resume()
return playerName
}
Essentially, you'll want to provide a completion handler to this function from the main class that can handle just the return of the player name (or not). You'd change the function to not have a return value, but to accept a second parameter that is a completion handler:
func getNameFromProfileUrl(profileUrl: NSURL, completionHandler: (String?) -> Void) {
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl, completionHandler: { (data, response, error) -> Void in
if error == nil {
var urlContent = NSString(data: data, encoding: NSUTF8StringEncoding) as NSString!
var urlContentArray = urlContent.componentsSeparatedByString("<title>")
var statusArray = urlContentArray[1].componentsSeparatedByString("</title>")
let playerName = statusArray[0] as? String
completionHandler(playerName)
} else {
completionHandler(nil)
}
})
task.resume()
}
From your main class, you'd then call it with something like this:
myWebScraper.getNameFromProfileUrl(profileURL) { playerName in
// always update UI from the main thread
NSOperationQueue.mainQueue().addOperationWithBlock {
if let playerName = playerName {
playerNameField.text = playerName
} else {
playerNameField.text = "Player Not Found"
}
}
}

Resources