I'm trying to download a zip file to the user's phone storage in an iOS app. Is it possible to do this without NSURLSession?
Yes, there are multiple tools but you should still try and use URL session.
A very easy way to do this is using Data. But it blocks your thread so you need to work a bit with queues to make it work properly (Otherwise your app MAY crash).
A very simple, non-safe, thread-blocking way would be:
func saveFile(atRemoteURL remoteURL: URL, to localURL: URL) {
let data = try! Data(contentsOf: remoteURL)
try! data.write(to: localURL)
}
But doing it a bit more stable should look something like this:
private func downloadIteam(atURL url: URL, completion: ((_ data: Data?, _ error: Error?) -> Void)?) {
let queue = DispatchQueue(label: "downloading_file")
queue.async {
do {
let data = try Data(contentsOf: url)
completion?(data, nil)
} catch {
completion?(nil, error)
}
}
}
private func saveDataToDocuments(_ data: Data, to: String, completion: ((_ resultPath: URL?, _ error: Error?) -> Void)?) {
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(to)
let queue = DispatchQueue(label: "saving_file")
queue.async {
do {
let folderPath: String = path.deletingLastPathComponent().path
if !FileManager.default.fileExists(atPath: folderPath) {
try FileManager.default.createDirectory(atPath: folderPath, withIntermediateDirectories: true, attributes: nil)
}
try data.write(to: path)
completion?(path, nil)
} catch {
completion?(nil, error)
}
}
}
public func downloadFileAndSaveItToDocuments(urlToRemoteFile: URL, completion: #escaping (_ file: (relativePath: String, fullPath: URL)?, _ error: Error?) -> Void) {
let fileName = urlToRemoteFile.lastPathComponent
let relativePath = "downloads/" + fileName
func finish(fullPath: URL?, error: Error?) {
DispatchQueue.main.async {
if let path = fullPath {
completion((relativePath, path), error)
} else {
completion(nil, error)
}
}
}
downloadIteam(atURL: urlToRemoteFile) { (data, error) in
guard let data = data else {
completion(nil, error)
return
}
saveDataToDocuments(data, to: relativePath) { (url, saveError) in
finish(fullPath: url, error: saveError ?? error)
}
}
}
I hope the code is self-documented enough.
Related
I am using a REST API (https://restcountries.eu/) and want to download the flag image (which is an .svg) and show it as a UIImage. I tried the standard way with:
func requestData(at url: URL, success: #escaping (_ data: Data) -> Void, failure: ((_ error: NetworkError) -> Void)? = nil) {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) { (responseData, response, responseError) in
DispatchQueue.main.async {
if responseError != nil {
failure?(.failedRequest)
} else if let data = responseData {
success(data)
} else {
failure?(.corruptedData)
}
}
}
task.resume()
}
and the data downloads fine, but when I try to show the image with UIImage(data: data), the image is nil. Am I missing something?
I am downloading and then reading a json file. this json contains a list of files and their address on the server.
Everything works fine but I want to get the size of all files to download.
but I have some trouble to set up a completionblock that would indicate that everything is finished.
here is the code.
jsonAnalysis {
self.sum = self.sizeArray.reduce(0, +)
print(self.sum)
} here
func jsonAnalysis(completion: #escaping () -> ()) {
let urlString = "xxxxxxxxxxxxxxxxxxxxx"
let url = URL(string: urlString)
URLSession.shared.dataTask(with:url!) { (data, response, error) in
if error != nil {
print("error")
} else {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? [String: Any]
self.i = -1
guard let array = json?["Document"] as? [Any] else { return }
for documents in array {
self.i = self.i + 1
guard let VersionDictionary = documents as? [String: Any] else { return }
guard let DocumentName = VersionDictionary["documentname"] as? String else { return }
guard let AddressServer = VersionDictionary["addressserver"] as? String else { return }
self.resultAddressServer.append(AddressServer)
self.addressServer = self.resultAddressServer[self.i]
self.resultDocumentName.append(DocumentName)
self.documentName = self.resultDocumentName[self.i]
let url1 = NSURL(string: AddressServer)
self.getDownloadSize(url: url1! as URL, completion: { (size, error) in
if error != nil {
print("An error occurred when retrieving the download size: \(String(describing: error?.localizedDescription))")
} else {
self.sizeArray.append(size)
print(DocumentName)
print("The download size is \(size).")
}
})
}
} catch {
print("error")
}
}
completion()
} .resume()
}
func getDownloadSize(url: URL, completion: #escaping (Int64, Error?) -> Void) {
let timeoutInterval = 5.0
var request = URLRequest(url: url,
cachePolicy: .reloadIgnoringLocalAndRemoteCacheData,
timeoutInterval: timeoutInterval)
request.httpMethod = "HEAD"
URLSession.shared.dataTask(with: request) { (data, response, error) in
let contentLength = response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
completion(contentLength, error)
}.resume()
}
I would like to get the sum of the array at the end when everything is done, right now print(self.sum) is running before and shows 0.
I am not familiar with the completion and I am sure I am doing everything wrong.
You need DispatchGroup.
Before calling the inner asynchronous task enter, in the completion block of the inner asynchronous task leave the group.
Finally when the group notifies, call completion
let group = DispatchGroup()
for documents in array {
...
let url1 = URL(string: AddressServer) // no NSURL !!!
group.enter()
self.getDownloadSize(url: url1!, completion: { (size, error) in
if error != nil {
print("An error occurred when retrieving the download size: \(String(describing: error?.localizedDescription))")
} else {
self.sizeArray.append(size)
print(DocumentName)
print("The download size is \(size).")
}
group.leave()
})
}
group.notify(queue: DispatchQueue.main) {
completion()
}
i have downloaded the twilio quickstarter project and followed the docx and create the function for chat and pass the url to the given place in project, now when i run the app the app crashes with this error,Thread 10: EXC_BREAKPOINT (code=1, subcode=0x1048d85b0). This error comes in their code when it try to seralize the data from the request. How can i get out from this issue?. This is the code where issue is occurring,
struct TokenUtils {
static func retrieveToken(url: String, completion: #escaping (String?, String?, Error?) -> Void) {
if let requestURL = URL(string: url) {
let session = URLSession(configuration: URLSessionConfiguration.default)
let task = session.dataTask(with: requestURL, completionHandler: { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as! [String:String]
let token = json["token"]
let identity = json["identity"]
completion(token, identity, error)
}
catch let error as NSError {
completion(nil, nil, error)
}
} else {
completion(nil, nil, error)
}
})
task.resume()
}
}
}
This is what it shows in debugger,
I have this code:
struct ServerConnect {
enum Result<T> {
case succes(T)
case error(String)
}
typealias completionHandler = (Result<Data >) -> ()
func getJsonFromServer(parameters: String, completion: #escaping completionHandler) {
let fullUrlString = ApiConstans.fullPath + parameters
guard let url = URL(string: fullUrlString) else {
debugPrint("\(ErrorsLabels.ServerConnect01)")
return completion(.error("\(ErrorsLabels.ServerConnect01)"))
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
debugPrint("\(ErrorsLabels.ServerConnect02)")
return completion(.error("\(ErrorsLabels.ServerConnect02)"))
}
guard let data = data else {
debugPrint("\(ErrorsLabels.ServerConnect03)")
return completion(.error("\(ErrorsLabels.ServerConnect03)"))
}
debugPrint("R> \(fullUrlString)")
return completion(.succes(data))
}.resume()
}
func getJsonProducts(lang: String?, region: Int?, completion: #escaping completionHandler) {
self.getJsonFromServer(parameters: "?action=GET_PRODUCTS&lang=\(lang!)®ion=\(region!)", completion: completion)
}
}
I would like to save the downloaded data from the internet in the device's memory.
I'm trying this code:
getJsonProducts(lang: selectedLanguage, region: selectedRegion , completion: { (data) in
print("##### \(data)")
saveJsonFileToTheDisk(path: selectedLanguage + "/json/products.json", downloadData: data)
})
func saveJsonFileToTheDisk(path: String, downloadData: Data){
do {
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentsURL.appendingPathComponent(path)
try downloadData.write(to: fileURL, options: .atomic)
} catch { }
}
But unfortunately it does not work. How can I make it work?
Below is an error:
Cannot convert value of type 'ServerConnect.Result' to expected
argument type 'Data'
According to the definition of getJsonProducts method:
func getJsonProducts(lang: String?, region: Int?, completion: #escaping (Result<Data>) -> ()) {
The parameter data which is sent to completion block is actually of type Result containing the data, not the data itself. Thus, you should do:
getJsonProducts(lang: selectedLanguage, region: selectedRegion, completion: { result in
switch result {
case .succes(let data):
saveJsonFileToTheDisk(path: selectedLanguage + "/json/products.json", downloadData: data)
case .error(let errorMessage):
// Do something with the error message.
break
}
})
Your completionHandler from getJsonProducts returns a Result<Data>, not a simple Data, so you need to pattern match the Result instance, since it might not actually contain Data, it might contain a String error.
getJsonProducts(lang: selectedLanguage, region: selectedRegion , completion: { (data) in
switch data {
case let .succes(data):
print("##### \(data)")
saveJsonFileToTheDisk(path: selectedLanguage + "/json/products.json", downloadData: data)
case let .error(error):
print(error)
}
})
I am attempting to create a generic networking function I am looking to make an API handler which will download JSON from the network and convert theme to Swift structs that conform to the Decodable protocol. Currently I am using explicit types:
struct MyObject : Decodable {
let id : Int
let name : String
}
static fileprivate func makeNetworkRequest(url: URL, completionHandler: #escaping(_ error: Error?, _ myObject: MyObject?) -> ()) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
do {
let myNewObject = try JSONDecoder().decode(MyObject.self, from: data!)
completionHandler(nil, myNewObject)
}
catch let error {
completionHandler(error, nil)
return
}
}.resume()
}
I am hoping to create a generic function where I can specify any data type which confirms to Decodable and have the data object returned in the completion handler. Something along the lines of:
static fileprivate func makeNetworkRequest<T>(url: URL, type: <<<Decodable Type>>>, completionHandler: #escaping(_ error: Error?, _ myObject: <<<Deocable Object>>>?) -> ()) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
do {
let myNewObject = try JSONDecoder().decode(<<<Decodable Type>>>.self, from: data!)
completionHandler(nil, myNewObject)
}
catch let error {
completionHandler(error, nil)
return
}
}.resume()
}
However, I can't seem to get the function parameters correct. I do not have a lot of experience working with generics. Any help would be appreciated
You can mimic the declaration of the decode method of JSONDecoder:
open func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
Applying the patter above to your code, the definition should be something like this:
static fileprivate func makeNetworkRequest<T>(url: URL, type: T.Type, completionHandler: #escaping (_ error: Error?, _ myObject: T?) -> ())
where T: Decodable
{
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
do {
let myNewObject = try JSONDecoder().decode(T.self, from: data!)
completionHandler(nil, myNewObject)
} catch let error {
completionHandler(error, nil)
return
}
}.resume()
}
Or you can write it in this way:
static fileprivate func makeNetworkRequest<T: Decodable>(url: URL, type: T.Type, completionHandler: #escaping (_ error: Error?, _ myObject: T?) -> ()) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
// handle error
do {
let myNewObject = try JSONDecoder().decode(T.self, from: data!)
completionHandler(nil, myNewObject)
} catch let error {
completionHandler(error, nil)
return
}
}.resume()
}