I am new at Alamofire framework. I try to download data file. The code is :
Alamofire.download(.GET, urlStr, { (temporaryURL, response) in
if let directoryURL = NSFileManager.defaultManager()
.URLsForDirectory(.DocumentDirectory,
inDomains: .UserDomainMask)[0]
as? NSURL {
let pathComponent = response.suggestedFilename
return directoryURL.URLByAppendingPathComponent(pathComponent!)
}
return temporaryURL
})
File downloading successfully. However all process doing with memory. As you see the problem is, if i try to download big file(I mean over 50mb), i got didReceiveMemoryWarning and app closed itself. How i can prevent that?
In test I try to download a movie (size is 220mb) and in simulator, memory usage goes up to 500mb. and when i try my phone. it closed itself after showin memory warning.
If you want to download large files, you may consider another lib called TCBlobDownloadSwift by thibaultCha. It is a Swift version of TCBlobDownload, which was tested with files from ~150MB to ~1.2GB, mostly videos.
Its usage is similar to Alamofire:
import TCBlobDownloadSwift
// Here is a simple delegate implementing TCBlobDownloadDelegate.
class DownloadHandler: NSObject, TCBlobDownloadDelegate {
init() {}
func download(download: TCBlobDownload, didProgress progress: Float, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
println("\(progress*100)% downloaded")
}
func download(download: TCBlobDownload, didFinishWithError error: NSError?, atLocation location: NSURL?) {
println("file downloaded at \(location)")
}
}
let fileURL = NSURL(string: "http://some.huge/file.mp4")
let download = TCBlobDownloadManager.sharedInstance
.downloadFileAtURL(fileURL!, toDirectory: nil, withName: nil, andDelegate: DownloadHandler())
Related
I'm new to swift and iOS in general, please keep that in mind.
I get this error when opening the CFReadStream. It does not matter if I open the read or write streams, the app always fails.
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
let host: CFString = NSString(string: hostIP)
let port: UInt32 = UInt32(self.VNCport)
self.password = password
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, host, port, &readStream, &writeStream)
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()
if readStream == nil {
print("Erro read")
}
if writeStream == nil {
print("Erro write")
}
inputStream!.delegate = self
outputStream!.delegate = self
inputStream!.schedule(in: RunLoop.main, forMode: RunLoop.Mode.default)
outputStream!.schedule(in: RunLoop.main, forMode: RunLoop.Mode.default)
inputStream!.open()
outputStream!.open()
I made a clean project with just this function and a Button, the result is the same. No quic lib is used in the project.
Can someone help?
I faced the same error in a different context, in XCode 12.0.1 too. It might not be related, but I suspect its an issue with accessing the run loop of the main thread. I was just trying out some introductory code I found online, and faced the same issue, so this is a bug, rather than a problem with your code. Here's how you can get a piece of code that has the same issue:
git clone git#github.com:BestKora/CS193P-Fall-2017-Demo-iOS12.git
cd "CS193P-Fall-2017-Demo-iOS12/Cassini L10"
xed . # this opens XCode (CLI tool stands for XCode editor)
Having said that, by rewriting the code, I was able to prevent this issue. Maybe you can find something amongst the code below to fix your specific issue:
Specifically, instead of using the following (DispatchQueue.global)
private func fetchImage() {
if let url = imageURL {
spinner.startAnimating()
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
let urlContents = try? Data(contentsOf: url)
DispatchQueue.main.async {
if let imageData = urlContents, url == self?.imageURL {
self?.image = UIImage(data: imageData)
}
}
}
}
}
I use URLSession.shared.dataTask, and this error no longer happens:
private func fetchImage() {
print("fetching image")
if let url = imageURL {
let task = URLSession.shared.dataTask(with: url) {(data, response, error) in
guard let data = data else {
return
}
// maybe try dispatch to main
DispatchQueue.main.async {
self.imageView.image = UIImage(data: data)
}
}
task.resume()
}
}
I had same issue in an Widget Target, but solved it by adding "public" to the variables.
Fixed struct is shown below.
struct LastCommitEntry: TimelineEntry {
public let date: Date
public let commit: Commit
}
This is just a Simulator message. It won't appear on a real device, and it doesn't affect the behavior of your app, so ignore it.
I was getting this issue in url session.
Thank you
Restarting my simulator did the trick.
For me, it started working when I encoded the data using String(data: safeData, encoding: .utf8) line:
if let safeData = data {
let dataString = String(data: safeData, encoding: .utf8)
print(dataString!)
}
In my case it was a problem with the model. I was working with Codable Model but it wasn't parsing the data properly. When I used the simulator the error was there but when I used my device it disappeared, neverthelesss, the collection view wasn't showing. When I change my model the error was corrected.
You'll need to adopt that specific delegate you're using in your class.
Like in below example:
class ViewController: UIViewController, UITextFieldDelegate
And don't forget to set your current class as the delegate, like below (for my own case):
IBOutlet weak var searchTextField: UITextField!
searchTextField.delegate = self
I am currently saving images a user selects from their photo library & saving the url to User Defaults.
The issue I am currently facing is whenever my app reboots, such as an Xcode re-launch the URL I am saving to User Defaults is no longer valid. From my understanding/researching I need to save a relative URL for the image; however I am not having any luck doing this correctly. This occurs with both simulator & actual device.
From my UIImagePicker here are my steps:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let image = info[.originalImage] as? UIImage else { return }
let savingImage : SavingImage = SavingImage()
let ourStringedUrl : String = savingImage.saveImageDocumentDirectory(image: image, imageName: image.description)
}
From SavingImage, here is the saving of the photo itself
func getDirectoryPath() -> NSURL {
let path = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("AllImages")
let url = NSURL(string: path)
return url!
}
func saveImageDocumentDirectory(image: UIImage, imageName: String) -> String {
let fileManager = FileManager.default
let path = (NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString).appendingPathComponent("AllImages")
if !fileManager.fileExists(atPath: path) {
try! fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
}
let url = NSURL(string: path)
let imagePath = url!.appendingPathComponent(imageName)
let urlString: String = imagePath!.absoluteString
let imageData = image.jpegData(compressionQuality: 1)
fileManager.createFile(atPath: urlString as String, contents: imageData, attributes: nil)
return urlString as String
}
& Finally here is how I am loading the image itself:
func getImageFromDocumentDirectory(imageName : String) -> UIImage {
let fileManager = FileManager.default
var ourImage : UIImage!
let imagePath = "\(imageName)"
let urlString: String = imagePath
if fileManager.fileExists(atPath: urlString) {
ourImage = UIImage(contentsOfFile: urlString)
} else {
if ourImage == nil {
ourImage = UIImage(named: "defaultImage.png")
}
}
return ourImage
}
Where am I going wrong with the loading aspect? How exactly should I be saving the URL?
The URL that is being saved looks something similar to the following:
//var/mobile/Containers/Data/Application/17C16D0D-1BFA-44F5-A6BD-18DFAEA051E0/Documents/AllImages/%3CUIImage:0x280a393b0%20anonymous%20%7B3024,%204032%7D%3E
But will come up as nil upon loading the image on reboot.
The new directory will be created every time you reboot your simulator or every time you install new build to device. (Uninstall previous and install new). iOS does not give us permission to select the directory. As you install new application it will create its sandbox. Please make sure you check your directory on the same build.
An alternative might be to use iCloud Drive and upload your image there. The iCloud drive is linked to an user's iCloud account, and therefore should persist in case of an app update.
Here's a great tutorial to get started: https://theswiftdev.com/2018/05/17/how-to-use-icloud-drive-documents/
Here's a link to the docs: https://developer.apple.com/icloud/icloud-drive/
iOS will purge assets after being downloaded as soon as it needs to free up some space.
Changing the preservation priorities of the assets will not prevent the system from purging them as stated in the "Setting Preservation Priority" section here.
My relevant code to download the On-demand resources is the following:
func requestResourceWith(tag: [String],
onSuccess: #escaping () -> Void,
onFailure: #escaping (NSError) -> Void) {
currentRequest = NSBundleResourceRequest(tags: Set(tag))
guard let request = currentRequest else { return }
request.endAccessingResources()
request.loadingPriority =
NSBundleResourceRequestLoadingPriorityUrgent
request.beginAccessingResources { (error: Error?) in
if let error = error {
onFailure(error as NSError)
return
}
onSuccess()
}
}
After downloading the On-Demand resources, they can be accessed from the main bundle.
Is there anyway to make audios persist, and hence prevent the system from purging them?
In response to #RJB comment above, I will answer my question :)
As soon as the On-demand resources are downloaded, you need to save them in hard disk (The documents directory for example) in order to persist them. Otherwise, iOS will retain the right to purge them as soon as it needs more free space.
Something like the following:
request.beginAccessingResources { (error: Error?) in
if let error = error {
DispatchQueue.main.async {
onFailure(error as NSError)
}
return
}
// Move ODR downloaded assets to Documents folder for persistence
DispatchQueue.main.async {
let path: String! = Bundle.main.path(forResource: "filename", ofType: "mp3")
let sourceURL = URL(fileURLWithPath: path)
let destinationURL = // Build a destination url in the Documents directory or any other persistent Directory of your choice
do {
try FileManager.default.copyItem(at: sourceURL, to: destinationURL)
}catch {
// Handle error accordingly
}
onSuccess()
}
}
I was hoping to resolve this without help but I seem to be going in circles even though I know it is not a difficult issue. So any help is much appreciated.
I have set up my App to populate a map and a TableView from data in a plist. All works well. But now I am downloading the plist from a server which will, of course, place the file in a document directory. My question is, what is the Swift code for reading this from the directory rather than from the bundle?
This is some of the code for reading from the App bundle (note it all works, but I am not providing ALL the code here. Please don't point me to Apple Developer site - I've looked):
if let path = NSBundle.mainBundle().pathForResource("testData", ofType: "plist"){
if let arrayOfDictionaries = NSArray(contentsOfFile: path){
for dict in arrayOfDictionaries {
tableData.append(dict.objectForKey("title") as! String)
stateData.append(dict.objectForKey("state") as! String)
codeData.append(dict.objectForKey("code") as! String)
infoData.append(dict.objectForKey("info") as! String)
}
}
This is the code for downloading the plist from the server:
func URLSession(session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didFinishDownloadingToURL location: NSURL){
let path = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
let documentDirectoryPath:String = path[0]
let fileManager = NSFileManager()
let destinationURLForFile = NSURL(fileURLWithPath: documentDirectoryPath.stringByAppendingString("/testData.plist"))
if fileManager.fileExistsAtPath(destinationURLForFile.path!){
showFileWithPath(destinationURLForFile.path!)
}
else{
do {
try fileManager.moveItemAtURL(location, toURL: destinationURLForFile)
// show file
showFileWithPath(destinationURLForFile.path!)
}catch{
print("An error occurred while moving file to destination url")
}
}
}
Managed to sort this. Odd how one can spend hours mulling over something. Then the next morning it seems obvious - and not without a little help from other SO postings :-) !!!
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let sourcePath = documentsPath.stringByAppendingPathComponent("testData.plist")
let dict = NSDictionary(contentsOfFile: sourcePath as String)
I'm downloading and writing ~200mb podcasts into the Documents directory with the following code:
var podcastRequest = NSURLRequest(URL: audioUrl)
NSURLConnection.sendAsynchronousRequest(podcastRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
if let myPodcastDataFromUrl = NSData(contentsOfURL: audioUrl) {
if myPodcastDataFromUrl.writeToURL(destinationUrl, atomically: true) {
// add to the array to track the download
var tempDic = self.posts[theRow] as! NSMutableDictionary as NSMutableDictionary
tempDic["downloaded"] = "true"
self.posts[theRow] = tempDic
} else {
println("Error saving file")
}
}
})
The sendAsynchronousRequest call prevents the lockup from happening during the download, but the app still freezes when it starts actually writing it to the directory.
Is there a way to prevent the lockup from happening at all, or am I going to have to write smaller chunks at a time?
You won't be able to store 200MB in memory before trying to write it to disk, but you can use downloadTaskWithURL method, it writes the file to a temporary folder and you can move it when it finishes to the documents folder as follow.
let documentsDirectoryURL = NSFileManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first as! NSURL
NSURLSession.sharedSession().downloadTaskWithURL(audioUrl, completionHandler: {
(location, response, error) -> Void in
if let error = error {
println(error.description)
}
else {
if NSFileManager().moveItemAtURL(location, toURL: documentsDirectoryURL.URLByAppendingPathComponent(response.suggestedFilename!), error: nil) {
println("saved")
} else {
println("not saved.")
}
}
}).resume()