Reputation: 5567
I have an array of 5 links which contain either jpg
or mov
files. I want to download them into my iOS Swift app. I use the following code.
var index = 0
while (index < myLinks.count) {
if (myLinks[index].resource_type == "image") {
let stringURL = "http://www.blablabla" + ".jpg"
let url = NSURL(string : stringURL)
getDataFromUrl(url!) { (data, response, error) in
guard let data = data where error == nil else { return }
print(response?.suggestedFilename ?? url!.lastPathComponent ?? "")
print("Download Finished")
myLinks[index].image = UIImage(data: data)
}
}
else if (myLinks[index].resource_type == "video") {
let stringURL = "http://www.blablabla" + ".mov"
let url = NSURL(string : stringURL)
getDataFromUrl(url!) { (data, response, error) in
guard let data = data where error == nil else { return }
print(response?.suggestedFilename ?? url!.lastPathComponent ?? "")
print("Download Finished")
dispatch_async(dispatch_get_main_queue(), {
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let documentsDirectory : NSString = paths[0]
let appFile = documentsDirectory.stringByAppendingPathComponent(myLinks[index].id! + ".m4v")
do {
try data.writeToFile(appFile, options: .AtomicWrite)
myLinks[index].videoURL = NSURL(fileURLWithPath: appFile)
}
catch _ {
print("error saving video")
}
})
}
}
index = index + 1
}
func getDataFromUrl(url: NSURL, completion: ((data: NSData?, response: NSURLResponse?, error: NSError? ) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(url) {
(data, response, error) in
completion(data: data, response: response, error: error)
}.resume()
}
Now the only problem is that, after downloading these resources, I want to display them to the user in a form of slideshow, hence I can only proceed knowing that all 5 downloads have been completed. The problem with the code above is that since the getDataFromUrl
function is executed asynchronously, this while loop will finish executing before all of the content has actually been downloaded.
EDIT
Here is a code adapting the accepted answer to uploading as well. The problem is that when more than one value is in the queue, only the first task gets executed and then nothing happens.
var uploadQueue:[UploadMessage]?
let session = NSURLSession.sharedSession()
let lockQueue = dispatch_queue_create("com.dsdevelop.lockQueue", nil)
func getRemainingActiveUploads() -> Int {
return (self.uploadQueue != nil) ? self.uploadQueue!.count : 0
}
func removeMessageFromUploadQueue(messageToBeRemoved : UploadMessage) {
if (uploadQueue != nil) {
dispatch_sync(lockQueue) {
self.uploadQueue = self.uploadQueue?.filter({$0.date!.compare(messageToBeRemoved.date!) == NSComparisonResult.OrderedSame})
}
}
}
var uploadTimer : NSTimer?
func finishedUploading() {
uploadTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(uploadAllLinks), userInfo: nil, repeats: false)
if (needToRefetch) {
needToRefetch = false
newMessageReceived()
}
}
func uploadAllLinks()
{
print("uploading test")
uploadTimer?.invalidate()
uploadTimer = nil
// suspending queue so they don't all finish before we can show it
session.delegateQueue.suspended = true
session.delegateQueue.maxConcurrentOperationCount = 1
let myUrl = NSURL(string: "http://****")
// create tasks
if (uploadQueue != nil) {
if (uploadQueue?.count > 0) {
for message in uploadQueue!
{
let request = NSMutableURLRequest(URL:myUrl!)
request.HTTPMethod = "POST"
request.timeoutInterval = 10
request.HTTPShouldHandleCookies=false
var postString = "sender=" + message.sender!
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let dltask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) in
if data != nil
{
do {
let jsonArray = try NSJSONSerialization.JSONObjectWithData(data_fixed!, options:[])
dispatch_async(dispatch_get_main_queue(), {
if let errorToken = jsonArray["error"] as! Bool? {
if !errorToken {
self.uploadQueue = self.uploadQueue!.filter({$0.date!.compare(message.date!) != NSComparisonResult.OrderedSame})
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
else {
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
}
else {
let remaining = self.getRemainingActiveUploads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0) {
self.finishedUploading()
}
}
})
}
catch {
print("Error: \(error)")
}
}
})
print("Queuing task \(dltask)")
dltask.resume()
}
session.delegateQueue.suspended = false
}
else {
finishedUploading()
}
// resuming queue so all tasks run
}
}
Upvotes: 0
Views: 2007
Reputation: 7426
You should add a var downloadsCompletedCounter = 0
and in your completion handler you should increment this counter:
self.downloadsCompletedCounter =+ 1
Use this in the block of dispatch_async(dispatch_get_main_queue()
to avoid multithreading issues.
You can also implement a property observer for downloadsCompletedCounter so that you know exactly when the downloads complete:
var downloadsCompletedCounter = 0 {
didSet{
if downloadsCompletedCounter == 4 {
print("Downloads completed")
// call the function you want to execute after downloads complete
}
}
}
Upvotes: 0
Reputation: 6705
Using the built in abilities of NSURLSession
and its underlying NSOperationQueue
you can do this easily enough.
import UIKit
class ViewController: UIViewController {
let MyDownloadsCompleteNotification:String = "MyDownloadsCompleteNotification"
var myLinks:[NSURL]?
var downloadQueue:[NSURL]?
let session = NSURLSession.sharedSession()
let lockQueue = dispatch_queue_create("com.dsdevelop.lockQueue", nil)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
myLinks = [ NSURL(string: "https://pbs.twimg.com/profile_images/447374371917922304/P4BzupWu.jpeg")!,
NSURL(string: "http://www.telegraph.co.uk/content/dam/pets/2016/03/18/bunny-large_trans++qVzuuqpFlyLIwiB6NTmJwfSVWeZ_vEN7c6bHu2jJnT8.jpg")!,
NSURL(string: "http://4.bp.blogspot.com/-HTvSYzA-pO4/UgQb4Zh_u0I/AAAAAAAAEuI/XwhtogT_1tA/s1600/3+cute2.jpg")!,
NSURL(string: "http://cdn.shopify.com/s/files/1/0224/1915/files/bunny.jpg?22110")!,
NSURL(string: "http://vignette1.wikia.nocookie.net/hare/images/1/1f/Bunnies-bunny-rabbits-16437969-1280-800.jpg/revision/latest?cb=20130831183111")!
]
// Register to know when all downloads are done
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(allDownloadsComplete), name: MyDownloadsCompleteNotification, object: nil)
downloadAllLinks(myLinks!)
}
func allDownloadsComplete(n:NSNotification)
{
print("Awesome all downloads are done!")
}
func getRemainingActiveDownloads() -> Int
{
return (self.downloadQueue != nil) ? self.downloadQueue!.count : 0
}
func removeUrlFromDownloadQueue(url:NSURL)
{
if self.downloadQueue != nil
{
dispatch_sync(lockQueue) {
self.downloadQueue = self.downloadQueue!.filter({$0.absoluteString == url.absoluteString})
}
}
}
func downloadAllLinks(links:[NSURL])
{
// suspending queue so they don't all finish before we can show it
session.delegateQueue.suspended = true
session.delegateQueue.maxConcurrentOperationCount = 1
downloadQueue = links
// create tasks
for link in links
{
let dltask = session.downloadTaskWithURL(link, completionHandler: { (url, response, error) in
if let urlString = response?.URL?.absoluteString
{
self.downloadQueue = self.downloadQueue!.filter({$0.absoluteString != urlString})
let remaining = self.getRemainingActiveDownloads()
print("Downloaded. Remaining: \(remaining)")
if (remaining == 0)
{
NSNotificationCenter.defaultCenter().postNotificationName(self.MyDownloadsCompleteNotification, object: nil)
}
}
})
print("Queuing task \(dltask)")
dltask.resume()
}
// resuming queue so all tasks run
session.delegateQueue.suspended = false
}
}
Upvotes: 3
Reputation: 3599
You could create a timer to check whether all 5 resources have been completely downloaded. Set the interval to 1ms and you will know immediately once all 5 are done.
Upvotes: 0