I'm trying to retrieve the XML from an rss feed, get the links for each article, and then extract info from those articles. I'm using AEXML to get the xml, and ReadabilityKit for link extraction.
I'm successfully pulling the links from the XML, but the parser call on Readability is never executing. I don't want this on the main thread as it blocks all UI, but so far that's the only way I've made it work. Code is below (removed that dispatch get main queue):
func retrieveXML() {
let request = NSURLRequest(URL: NSURL(string: "<XML URL HERE>")!)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
(data, response, error) in
if data == nil {
print("\n\ndataTaskWithRequest error: \(error)")
return
}
do {
let xmlDoc = try AEXMLDocument(xmlData: data!)
for child in xmlDoc.root.children {
if let postURL = child["id"].value {
let url = NSURL(string: postURL)
let parser = Readability(url: url!)
let title = parser.title()
print("TITLE: \(title)")
}
}
} catch {
print(error)
}
}
task.resume()
}
Thanks for reporting. The new version is available in cocoa pods and cartage with a new aync API. Sync API is removed from the project.
Readability.parse(url: articleUrl, { data in
let title = data?.title
let description = data?.description
let keywords = data?.keywords
let imageUrl = data?.topImage
let videoUrl = data?.topVideo
})
Thanks for your contribution! For more info please check README https://github.com/exyte/ReadabilityKit
The problem is that Readability is deadlocking. You're calling it from a NSURLSession completion block (which defaults to a serial queue), but Readability blocks that queue with a semaphore until its own network request is completed. So Readability is deadlocking because it's blocking a thread waiting for a semaphore signal that is supposed to be sent from the same thread it is blocking.
You can fix this by asynchronously dispatching the code that instantiates Readability to a separate queue (e.g. a global queue).
dispatch_async(dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)) {
let url = NSURL(string: postURL)
let parser = Readability(url: url!)
let title = parser.title()
print("TITLE: \(title)")
}
It looks like that API has been updated to run asynchronously, so get the latest version and this deadlocking issue is eliminated and the above asynchronous dispatch will not be needed. You'll obviously have to employ the completion handler pattern of the updated API.
Related
I’m trying to convert escaping closure code to async-await code in a certain file. I’m stuck with implementing the async-await part, specifically whether to still use a do block, with catch and resume, or to not use a do block, and assign the line of code with “try await URLSession.shared.dataTask(with: request)” (that's commented out in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift in this post below, and meant to be used in the solution attempts to this file) to a variable, then use that variable later similar to how the file File2-GettingRestaurantBusinessDataUsingAsync-AwaitAndUsingCodable.swift does, which is posted further below in this post.
*Note: I used async-await and codable to get the restaurant business data for a certain searched city (thats searched by the user) which is done in a different file (and function). The file I’m having trouble with though is for getting the selected restaurant business’s detail info (name, address, business hours, etc.), and I’m not using codable in this file because some of the data I get when doing this URL request, I get by using NSDictionary; not codable.
How do I correctly implement this async-await concept in my file? The file I’m implementing this in is File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift which is posted further below in this post.
*Update: Where I thought my problem lied when first writing up this question post: At the line of code “URLSession.shared.dataTask(with: request) { (data, response, error) in” in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift, specifically I thought that I should use the form of code that didn’t use the completionHandler (which is commented as V2 in that file), and whether to use a do block after it, and catch, and resume after the do block.
I’ve posted some attempted solutions so far, which are incomplete, since I’m having the problems mentioned in the first paragraph of this post. I know they don’t work, but this is my thought process so far. These solution attempts are below the code files that I’m working with which are posted further below.
I used the following article for learning more about async-await before attempting to make this current implementation: https://www.avanderlee.com/swift/async-await/.
My code:
Code files that I’m working with:
File that I’m attempting to implement this escaping closure to async-await concept in:
File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift:
import Foundation
import UIKit
import CoreLocation
extension UIViewController {
func retrieveSelectedRestaurantDetailViewInfo(
selected_restaurant_business_ID: String) async throws -> SelectedRestaurantDetailViewInfoNotUsingCodable? {
// MARK: Make API Call
let apiKey = "API key"
/// Create URL
let baseURL =
"https://api.yelp.com/v3/businesses/\(selected_restaurant_business_ID)"
let url = URL(string: baseURL)
/// Creating Request
var request = URLRequest(url: url!)
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
///Initialize session and task
//V1: Code for using escaping closure version code.
URLSession.shared.dataTask(with: request) { (data, response, error) in
//This version commented out right now, to show where I'm at with this proboem for clarity. This version is included in the solution attempts; both SoultionAttempt1 and SolutionAttempt2.
if let error = error {
completionHandler(nil, error)
}
//V2: Code for what I think is correct for using async-await version code. Not using the completionHandler here.
// URLSession.shared.dataTask(with: request)
do {
/// Read data as JSON
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let responseDictionary = json as? NSDictionary else {return}
//Code for accessing restaraunt detail view info, and assigning it to selectedRestarauntDetailViewInfo view model thats a struct.
var selectedVenue = SelectedRestaurantDetailViewInfoNotUsingCodable()
//Rest of code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
selectedVenue.name = responseDictionary.value(forKey: "name") as? String
//*Rest of code for getting business info including address, hours, etc..*
//Commented out for now, because am going with version 2 below.
//V1: Uses escaping closure code version.
// completionHandler(selectedVenue, nil)
//V2: Used for Async/Await code version.
return selectedVenue
} catch {
print("Caught error")
}
}.resume()
}
}
*Update: Below is the new file that shows the code that calls the function with the URLRequest in it in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift, that uses the async-await concept, as mentioned in a response to a comment in this post:
File0.5-FileWithJustCodeThatCallsTheFunctionForMakingTheURLRequestInFile1.swift:
async let responseSelectedVenueDetailViewInfoNotUsingCodable = try await retrieveSelectedRestaurantDetailViewInfo(selected_restaurant_business_ID: venues.id)
File (file I'm referring to is below; is File2-GettingRestaurantBusinessDataUsingAsync-AwaitAndUsingCodable.swift) that uses async-await for getting the initial restaurant business data, after the user selects a city, which I’m using for reference for making the stated change from the escaping closure code to the async-await code in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift above:
File2-GettingRestaurantBusinessDataUsingAsync-AwaitAndUsingCodable.swift:
import UIKit
import Foundation
import CoreLocation
class YelpApiSelectedRestaurantDetailViewInfo {
private var apiKey: String
init(apiKey: String) {
self.apiKey = apiKey
}
func searchBusinessDetailViewInfo(selectedRestaurantBusinessID: String) async throws -> SelectedRestaurantDetailViewInfo {
var resultsForTheSelectedRestaurantDetailViewInfo: SelectedRestaurantDetailViewInfo
// MARK: Make URL Request.
var urlComponents = URLComponents(string: "https://api.yelp.com/v3/businesses/\(selectedRestaurantBusinessID)")
guard let url = urlComponents?.url else {
throw URLError(.badURL)
}
var request = URLRequest(url: url)
request.setValue("Bearer \(self.apiKey)", forHTTPHeaderField: "Authorization")
let (data, _) = try await URLSession.shared.data(for: request)
let businessDetailViewInfoResults = try JSONDecoder().decode(SelectedRestaurantDetailViewInfo.self, from: data)
resultsForTheSelectedRestaurantDetailViewInfo = businessDetailViewInfoResults
return resultsForTheSelectedRestaurantDetailViewInfo
}
}
Solution Attempts:
*Note:
-Both of the below solution attempt code snippets start at the line of code: URLSession.shared.dataTask(with: request) { (data, response, error) in in File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift, and goes through the rest of that file, and ends at the same end point as that file (as file File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift).
-Also, the file that these solution attempts are to be implemented in is File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift.
SolutionAttempt1-DoesntUseDoBlock.swift:
///Initialize session and task
try await URLSession.shared.dataTask(with: request)
/// Read data as JSON
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let responseDictionary = json as? NSDictionary else {return}
//Code for accessing restaraunt detail view info, and assigning it to selectedRestarauntDetailViewInfo view model thats a struct.
var selectedVenue = SelectedRestaurantDetailViewInfoNotUsingCodable()
//Rest of code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
selectedVenue.name = responseDictionary.value(forKey: "name") as? String
//*Rest of code for getting business info including address, business hours, etc..*
return selectedVenue
} catch {
print("Caught error")
}
}.resume()
}
}
*Note about the following solution attempt file: The solution attempt here in my opinion (SolutionAttempt2-DoesUseDoBlock.swift) may not have to include indentation for the do block, where the do block is within the scope of the “try await URLSession.shared.dataTask(with: request)” line of code, but I included the below solution attempt to have this indentation, as it would seem that the do block would need to be within the scope of the “try await URLSession.shared.dataTask(with: request)” line of code, and the original file version of File1-GettingSelectedRestaurantBusinessDataUsingAsync-AwaitAndNotUsingCodable.swift, that uses the escaping closure (not the file being edited/worked on here) had the do block within the “URLSession.shared.dataTask(with: request) { (data, response, error) in” line of code’s scope, which is at the same position as the “try await URLSession.shared.dataTask(with: request)” line of code in this SolutionAttempt2-DoesUseDoBlock.swift file below.
SolutionAttempt2-DoesUseDoBlock.swift:
///Initialize session and task
try await URLSession.shared.dataTask(with: request)
do {
/// Read data as JSON
let json = try JSONSerialization.jsonObject(with: data!, options: [])
/// Main dictionary
guard let responseDictionary = json as? NSDictionary else {return}
//Code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
var selectedVenue = SelectedRestaurantDetailViewInfoNotUsingCodable()
//Rest of code for accessing restaraunt detail view info, and assigning it to seelctedRestarauntDetailViewInfo view model thats a struct.
selectedVenue.name = responseDictionary.value(forKey: "name") as? String
//*Rest of code for getting business info including address, business hours, etc.*
return selectedVenue
} catch {
print("Caught error")
}
}.resume()
}
}
Thanks!
Say we have multiple threads issuing prints.
Typically when downloading stuffs as follows:
let url = self.url
print("loadPreview(\(source) for \(url)): ↝start loading \(self.url")
let task = session.downloadTask(with: url) {
(localUrl, response, error) in
print("loadPreview(\(source) for \(url)): == \(self.url")
}
Is there any way to make print atomic and prevent output as follows?
loadPreview(WSJ for www.wsj.co⟷TloadPreview(9loadPreview(appleins for appleinsid⟷n-messages): ↝start loading http://app⟷n-messages
A quick hack is just to use a NSLock around your print. For instance, you could define an atomic print function as:
private let printLock = NSLock()
func aprint(_ message: String){
printLock.lock()
defer { printLock.unlock() }
print(message)
}
and use it like the standard print function:
aprint(“This will print atomically!”)
You could also achieve a similar result using a serial DispatchQueue to serialize your print calls as well. For instance:
private let printQueue = DispatchQueue(label: "aprint", qos: .utility)
func aprint(_ message: String){
printQueue.async {
print(message)
}
}
This solution provides better performance mainly thanks to the async call. This ensures the calling thread will not block until the lock is acquired (and the corresponding print is completely performed). But, to be clear, this solution is also perfectly atomic as well.
(For the record, simply using DispatchQueue.main might behave weirdly if the calling code is also running on the main queue.)
I would recommend picking the second solution ;)
You can use DispatchIO to automatically serialise the output to either stdout or even a file in a non-blocking fashion. It's a bit more work and you may want to wrap the example below into a class:
import Foundation
// Open stdout with DispatchIO
let queue = DispatchQueue.global()
let fd = FileHandle.standardOutput.fileDescriptor
let writer = DispatchIO(type: .stream, fileDescriptor: fd, queue: queue) { (errno) in
if errno != 0 {
print("Cannot open stdout: \(errno)")
}
}
// Encode string
if let data = string.data(using: .utf8) {
data.withUnsafeBytes { (buffer) in
// Produce DispatchData with encoded string
let dispatchData = DispatchData(bytes: buffer)
// Print string to stdout
writer.write(offset: .zero, data: dispatchData, queue: queue) { (done, data, errno) in
if errno != 0 {
print("Write error: \(errno)")
}
}
}
}
// Close the DispatchIO when you don't need it anymore
writer.close()
I make few NSURLSession requests in a loop. I'd like to store results from responses in the same order as tasks are created. But since completion handler runs in a separate thread it sometimes happens that the response to the second task gets received before the response to the first task.
How to make sure that I get responses in same order as tasks are being started?
var recivedData = [String]()
for index in 0 ... urlsAsString.count-1 {
let myUrl = NSURL(string: urlsAsString[index])
var request = NSMutableURLRequest(URL: myUrl!)
// here I also set additional parameters (HTTPMethod, ...)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
responseData, response, error in
// here I handle the response
let result = ...
dispatch_async(dispatch_get_main_queue()) {
self.recivedData.append("\(result)") // save the result to array
}
}
task.resume()
}
While I'd discourage behaviour that requires responses to be received in a specific order, you can collate the responses (regardless of the order they are received) into a known order.
The receivedData array should be initialised with a capacity that matches the number of requests that will be made:
var receivedData = [String](count: urlsAsString.count, repeatedValue: "")
Then when you receive the response, since you're in a block that has access to the index of the request you can add the response data directly into the index of the receivedData array:
receivedData[index] = result as (String)
The full code is as follows:
var receivedData = [String](count: urlsAsString.count, repeatedValue: "")
for index in 0 ... urlsAsString.count-1 {
let myUrl = NSURL(string: urlsAsString[index])
var request = NSMutableURLRequest(URL: myUrl!)
// here I also set additional parameters (HTTPMethod, ...)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
responseData, response, error in
// here I handle the response
let result = ...
dispatch_async(dispatch_get_main_queue()) {
// Insert the result into the correct index
receivedData[index] = result
}
}
task.resume()
}
Because you know the exact number of http requests.
You can create an array to the size of urls.count, and then set the result in completion handler, corresponding to the index in each loop.
receivedData = [String](count: urls.count, repeatedValue: "No Data")
for (index,url) in enumerate(urls){
let url = NSURL(string: url)!
let task = NSURLSession.sharedSession().dataTaskWithURL(url){ data, response, error in
if error != nil{
self.receivedData[index] = "error: \(error.localizedDescription)"
return
}
let result = ...
dispatch_async(dispatch_get_main_queue()) {
self.recivedData[index] = "\(result)"
}
}
task.resume()
}
Actually, can not make sure the order of responses.
There are two workarounds for this:
Send the requests one after another, which means send the next request after the previous response is returned. You can use ReactiveCocoa to make your codes elegant. Or use the networking library I wrote STNetTaskQueue, there is a STNetTaskChain which can handle the requests one after another.
Send the requests parallel(is what you are doing now), and use a NSDictionary to keep track on the request and response, after all requests is finished, combine the response in the original order.
Hope this helps!
I've created a script/api which is suppose to add a record to my database when running a specific url. However i'm not sure how to run this url. I do not expect anything back just to run this url? how can i do this?
var identifier = UIDevice.currentDevice().identifierForVendor.UUIDString
var addViewUrl = "http://url/addview.php?type=ios&identifier=\(identifier)&newsid=\(newsObject?.id)"
Based on my comment:
You should get a response and check for errors.
Also there is always the possibility to call a URL asynchronously to avoid blocking the GUI if the request takes a long time.
This can be made using delegate patterns or with completions handlers like in Objective-C.
Example:
var url = NSURL.URLWithString(addViewUrl)// Creating URL
var request = NSURLRequest(URL: url)// Creating Http Request
var queue: NSOperationQueue = NSOperationQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{(response:NSURLResponse!, responseData:NSData!, error: NSError!) -> Void in
if error != nil
{
println(error.description)
}
else
{
var responseStr:NSString = NSString(data:responseData, encoding:NSUTF8StringEncoding)
//Everything went fine
}
})
I need to run some code only after requesting multiple HTTP resources for gathering some data.
I've read a lot of documentation and I've found out I should use GCD and dispatch groups:
Create a group with dispatch_group_create()
For each request:
Enter the dispatch group with dispatch_group_enter()
Run the request
When receiving a response, leave the group with dispatch_group_leave()
Wait with dispatch_group_wait()
Release the group with dispatch_release()
Yet I'm not sure if this practice could have some pitfalls – or is there a better way to wait for parallels requests being finished?
The code below looks working well:
// Just send a request and call the when finished closure
func sendRequest(url: String, whenFinished: () -> Void) {
let request = NSMutableURLRequest(URL: NSURL(string: url))
let task = NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: {
(data, response, error) -> Void in
whenFinished()
})
task.resume()
}
let urls = ["http://example.com?a",
"http://example.com?b",
"http://example.com?c",
"http://example.com?d",
"http://invalid.example.com"]
var fulfilledUrls: Array<String> = []
let group = dispatch_group_create();
for url in urls {
dispatch_group_enter(group)
sendRequest(url, {
() in
fulfilledUrls.append(url)
dispatch_group_leave(group)
})
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
for url in fulfilledUrls { println(url) }
Yup, this is the basic idea, although you would ideally use dispatch_group_notify instead of dispatch_group_wait since dispatch_group_wait blocks the calling thread until the group completes, whereas dispatch_group_notify will call a block when the group completes without blocking the calling thread in the interim.