NSXMLParser contentsOfURL Vs Data - swift

When parsing an XML file from a server I have always used "init(data: Data)"
let url = URL(string: urlString)
let data = NSData(contentsOf: url!)
let parser = XMLParser(data: data as! Data)
I read the docs and see this method "init?(contentsOf: URL)"
let url = URL(string: urlString)
let parser = XMLParser(contentsOf: url!)
Question is then am I unnecessarily converting to a data object from the URL contents, and I may as well just initialise from the URL? Or, is there any merit to creating the data object and parsing from that?

There is no semantic difference between the two variants.
For http(s) urls you should prefer init(data:) or XMLParser(data:), but you should not load you data with NSData(contentsOf:). The reason is that the contentsOf: variants will block the current thread with a synchronous request. You should instead load the data asynchronously with NSURLSession.
You should use XMLParser(contentsOf:) and Data(contentsOf:) only for file urls.

Related

Can timeout function be added when getting a string from a URL?

Sometimes I fetch information from a specific site.
But when the response is slow I would like to add a timeout function. I would like to know how.
Can I add a timeout function to the code below?
html = try String(contentsOf: url, encoding: String.Encoding.ascii)
You are not really supposed to use init(contentsOf:encoding:) to read from a remote URL. This initialiser is synchronous so while it is doing that your app's UI will freeze and the user won't be able to do anything, as you may have noticed.
You are supposed to use URLSession and URLRequest to fetch data from remote URLs. They are asynchronous so you get your data in a completion handler.
You can set a timeout in seconds when you create the URLRequest, and you will get an NSError in the completion handler if it timed out (among other reasons).
var request = URLRequest(url: URL(string: "https://example.com")!,timeoutInterval: 10)
request.addValue("text/plain", forHTTPHeaderField: "Content-Type")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
let result = String(data: data, encoding: .ascii)
// do something with result
}
task.resume()

pho.to API Request Failing in Swift

Im currently trying to work with the pho.to API in my iOS application. I am experimenting with making simple requests according to the documentation, however I cannot seem to get the request to go through successfully. Inside my API client file, I have this code:
let dataStr = """
<image_process_call>
<image_url>http://developers.pho.to/img/girl.jpg</image_url>
<methods_list>
<method order="1">
<name>desaturation</name>
</method>
<method order="2">
<name>caricature</name>
<params>type=1;crop_portrait=true</params>
</method>
</methods_list>
<thumb1_size>100</thumb1_size>
</image_process_call>
"""
let encodedStr = dataStr.replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: " ", with: "")
let signData = encodedStr.hmac(key: key)
let urlStr = "https://opeapi.ws.pho.to/addtask/?app_id=\(appId)&key=\(key)&sign_data=\(signData)&data=\(encodedStr.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!))"
The HMAC encoding is being done according to this Stack Overflow post. Unfortunately when making a request to this URL using URLSession I get this response:
<?xml version=\"1.0\"?>\n<image_process_response><status>SecurityError</status><err_code>614</err_code><description>Error in POST parameters: one or more parameters (DATA , SIGN_DATA or APP_ID) are empty</description></image_process_response>
I feel like my issue is more related to actually forming the request rather than something specific to the API itself. I know my code is a little messy, however I was hoping that somebody could point me in the right direction in terms of making a request like this. Thanks!
As per their documentation you can see that data sent over from POST requests are in body (In cURL calls -d specifies the body of the request)
You are sending params/data in query, which the pho.to API doesn't accept, hence the error.
Here's a sample on how you can do:
let defaultSessionConfiguration = URLSessionConfiguration.default
let defaultSession = URLSession(configuration: defaultSessionConfiguration)
// Setup the request with URL
let url = URL(string: "https://opeapi.ws.pho.to/addtask")!
var urlRequest = URLRequest(url: url)
// Convert POST string parameters to data using UTF8 Encoding
let postData = yourXMLString.data(using: .utf8)
// Set the httpMethod and assign httpBody
urlRequest.httpMethod = "POST"
urlRequest.httpBody = postData
// Create dataTask
let dataTask = defaultSession.dataTask(with: urlRequest) { (data, response, error) in
// Handle your response here
}
// Fire the request
dataTask.resume()

contentsof:url loads url content of a truncated URL

When I use contentsof:url it truncates the url before retrieving the content, resulting in different html content than the displayed in the WKWebView.
For example
contents = try String(contentsOf: https://www.amazon.com/gp/aw/d/B00BECJ4R8/ref=mp_s_a_1_1?ie=UTF8&qid=1531620716&sr=8-1-spons&pi=AC_SX236_SY340_QL65&keywords=cole+haan&psc=1)
returns the contents of this page: https://www.amazon.com/gp/aw/d/B00BECJ4R8/
Why is this happening? Is there an alternative method that allow you to read the content of the actual URL not the truncated URL?
Any advice if very much appreciated.
Thank you.
You shouldn't be using String(contentsOf:) to load a website. You should use the URL Loading System for this work then passing that object back to your webView.load(_:) method in viewDidLoad()
let urlString = "https://www.amazon.com/dp/B00BECJ4R8/?tag=stackoverflow17-20"
// URL construct may fail in case of the String not being a properly formatted URL, so unwrap it.
if let url = URL(string: urlString) {
// Create a URLRequest instance with the new url.
let request = URLRequest(url: url)
// Load the request.
webView.load(request)
}

data from base64 url

I have a URL in the form of
foo://?data:application/x-foo;base64,OjAyMDAwMDA0MDAwMEZBDQo6MTAwMDA...
and now need to extract the base64 data into a Data object.
Unfortunately it seems the Data object does not support this yet as
let data = try Data(contentsOf: url)
returns NSURLConnection finished with error - code -1002 when trying.
While I could decode the URL manually I am wondering if I am missing a simple standard way of doing this. How would you do this?
Actually you can decode Base64 data from an URL (see for
example Base64 Decoding in iOS 7+ where this is demonstrated in Objective-C). The format is a bit different from what
you have:
let url = URL(string: "data:application/octet-stream;base64,SGVsbG8gd29ybGQh")!
let data = try! Data(contentsOf: url)
print(String(data: data, encoding: .utf8)!) // Hello world!
(Error checking omitted for brevity.)
You have to separate the base64 encoded part of the URL from the other parts, decode it, then join the original non-encoded part with the decoded part and get the data from there.
extension URL {
init?(partialBase64Url: String){
guard let base64part = base64Url.components(separatedBy: "base64,").last, let base64Data = Data(base64Encoded: base64part), let decodedString = String(data: base64Data, encoding: .utf8) else {
return nil
}
let decodedUrl = base64Url.components(separatedBy: "base64,").dropLast().joined() + decodedString
self.init(string: decodedUrl)
}
}
let decodedUrl = URL(partialBase64Url: "foo://?data:application/x-foo;base64,dGVzdFVybFN0cmluZw==")
Value of decodedUrl: "foo://?data:application/x-foo;testUrlString", as expected, since dGVzdFVybFN0cmluZw== is the base64 encoded value of testUrlString.

Content Blocker extension with a String instead of a file

I'm using the function NSItemProvider(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json") in a content blocker extension.
The thing is that all my rules are stored in a few dictionaries and, when I'm using this function, it's always because the rules have changed. I'm currently creating a String from these dictionaries that looks like "[{\"trigger\": {\"url-filter\": \"webiste.com\"},\"action\": {"\type\": \"css-display-none\",\"selector\":\".testContentBlocker\"}}]"and I have to transform it in a JSON file to finally be able to use it in the function written previously described.
Instead of having to put the String in a JSON file to be able to use it, could I do something simpler to use NSItemProvider()?
By loading the extension up in the debugger (and by using Hopper), you can see that NSItemProvider(contentsOfURL:) is simply registering to provide data from the file's contents with type public.json.
(lldb) po attachment
<NSItemProvider: 0x7fd4c250f2a0> {types = (
"public.file-url",
"public.json"
)}
It's roughly equivalent to this:
// possible implementation of NSItemProvider.init(contentsOfURL:)
convenience init?(contentsOfURL fileURL: NSURL!)
{
self.init(item: fileURL, typeIdentifier: (fileURL.fileURL ? kUTTypeFileURL : kUTTypeURL) as String)
let type = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, fileURL.pathExtension!, nil)?.takeRetainedValue() as! String
registerItemForTypeIdentifier(type) { completionHandler, expectedValueClass, options in
let data = try! NSData(contentsOfURL: fileURL, options: .DataReadingMappedAlways)
completionHandler(data, nil)
}
}
So you can do this yourself, in memory:
// get the data
let data = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json")!)
// put the data in an item provider
let attachment = NSItemProvider(item: data, typeIdentifier: kUTTypeJSON as String)
// send the item to Safari
let item = NSExtensionItem()
item.attachments = [attachment]
context.completeRequestReturningItems([item], completionHandler: nil);
If you want to provide content dynamically, you can use NSJSONSerialization to transform a dictionary into NSData at runtime.