Background process listening to variable changes? - ios

I'm new to background operations in iOS, so I'm wondering what is the best way to solve such problem:
I have data, coming from one webservice#1 that needed to be parsed and sent to webservice#2 in background.
I need a background thread, which will be listening for changes in array that stores data from webserivce#1 and when it's not empty, the thread will start uploadOperation, which will process the array and send processed data to webservice#2
Literally, how I see it:
I have DataManager class, presented by a Singleton sharedInstance.
let sharedInstance = DataManager()
It has
public var data: [String]? {
didSet {
processData()
}
}
private var uploadToWebSerivice2Queue: NSOperationQueue?
private override init() {
uploadToWebSerivice2Queue = NSOperationQueue()
uploadToWebSerivice2Queue.maxConcurentOperations = 1
getCachedAndNotSentDataFromDatabase()
}
private func getCachedAndNotSentDataFromDatabase() {
data = ("string 1", "string 2", "string 3", "string 4")
}
private func processData() {
while let lastElement = data!.removeLast {
let processedData = process(lastElement)
let uploadOperation = UploadOperation(processedData) // Data upload opeation
uploadToWebSerivice2Queue!.addOperation(uploadOperation)
}
}
Some data may come from Database where they are cached if they couldn't be send to webservice#2 during the last try. And some data may come from webservice#1 in runtime.
So in other class, let's call it Webservice1DataHandler, I'd do:
DataManager.sharedInstance.data.append("New string to process and upload to webservice#1")
To sum up,
var data will be set after first init() and uploadQueue will start to process that data. Then new string will be appended to var data, that means that processData() method will be invoked and concurecny problems with data array access may occur.
I'm not sure if my algorithm is OK.

Related

How can I make multiple calls of NSBatchUpdateRequest within DB transaction so that either all rows is updated or none is updated?

Is there a way, to make multiple NSBatchUpdateRequest calls executed within a DB transaction, so that either all DB rows is updated or none is updated (When exception thrown)?
The following code illustrate the problem.
func debug() {
let coreDataStack = CoreDataStack.INSTANCE
let backgroundContext = coreDataStack.backgroundContext
backgroundContext.perform {
let fetchRequest = NSTabInfo.fetchSortedRequest()
do {
var objectIDs: [NSManagedObjectID] = []
let nsTabInfos = try fetchRequest.execute()
//
// QUESTION: We are updating multiple rows of data directly in a persistent store.
// How can we ensure either all rows is updated, or none row is updated is exception
// happens in between?
//
for nsTabInfo in nsTabInfos {
let batchUpdateRequest = NSBatchUpdateRequest(entityName: "NSTabInfo")
batchUpdateRequest.predicate = NSPredicate(format: "self == %#", nsTabInfo.objectID)
batchUpdateRequest.propertiesToUpdate = ["name": nsTabInfo.name! + "XXX"]
batchUpdateRequest.resultType = .updatedObjectIDsResultType
let batchUpdateResult = try backgroundContext.execute(batchUpdateRequest) as? NSBatchUpdateResult
guard let batchUpdateResultX = batchUpdateResult else { return }
guard let managedObjectIDs = batchUpdateResultX.result else { return }
if let nsManagedObjectIDs = managedObjectIDs as? [NSManagedObjectID] {
objectIDs.append(contentsOf: nsManagedObjectIDs)
}
//
// Simulate some exception
// We notice the first row is updated & rest of the rows are unchanged.
// This leaves our data in inconsistent state.
//
throw "Custom error!!!"
}
if !objectIDs.isEmpty {
let changes = [NSUpdatedObjectsKey : objectIDs]
coreDataStack.mergeChanges(changes)
}
} catch {
backgroundContext.rollback()
error_log(error)
}
}
}
class CoreDataStack {
static let INSTANCE = CoreDataStack()
private init() {
}
private(set) lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "wenote")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
// So that when backgroundContext write to persistent store, container.viewContext will retrieve update from
// persistent store.
container.viewContext.automaticallyMergesChangesFromParent = true
return container
}()
private(set) lazy var backgroundContext: NSManagedObjectContext = {
let backgroundContext = persistentContainer.newBackgroundContext()
backgroundContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
return backgroundContext
}()
func mergeChanges(_ changes: [AnyHashable : Any]) {
NSManagedObjectContext.mergeChanges(
fromRemoteContextSave: changes,
into: [persistentContainer.viewContext, backgroundContext]
)
}
}
We write a demo code to illustrate the following
Performing NSBatchUpdateRequest multiple times within a loop.
An exception happens in between.
We wishes none of the row in persistent store is updated. However, a row is already updated before the exception thrown.
May I know what technique I can use, which is similar to SQLite transaction feature, so that either all rows is updated, or none of the row is updated when exception happens?
CoreData.framework doesn't open up SQLite level controls to the user, it provides you NSManagedObjectContext.
How does it work in a similar manner?
You pull as many objects in many as you need and do your changes on them.
When you are done with your changes, you do context.save().
In that way, you save all of your changes in one shot.
In all cases, pulling all objects in memory might not be possible or a good idea, so then you need to implement your own solution around how to send all of these changes to disk.
From the NSBatchUpdateRequest docs -
A request to Core Data to do a batch update of data in a persistent store without loading any data into memory.
When you execute this, you are doing the changes in store that you can't roll back. For a large data-set, you can do following -
Say you have to perform a series of updates (5 different steps) on 100k records as an operation.
Start in a background thread, pull objects in memory in batches of 1k at a time.
You can load 1k objects easily in memory, mutate them - go through all of your changes/steps one by one and save these changes on this batch. If this is successful, you move on to the next batch.
In case one intermediate step fails on a batch, you can then use either NSManagedObjectContext.rollback() or NSManagedObjectContext.reset() depending on your implementation.
Here's a popular SO post on the differences between the two in case official docs don't provide enough clarity.

Where should I place Callkit Reload Extension code in my IOS project

I am making an app like TrueCaller. I am able to block a number at first but whenever I am updating/adding more number then I need to manually disable and enable call directory plugin in Settings -> Phone -> Call Blocking & Identification to get list updated. I have seen many answers on StackOverflow where people say to call CXCallDirectoryManager.reloadExtension to reload extension via code. But I don't know where to write this code or from where should I call this reloadExtension from my project.
Update -1
Here is how I approached.
Below is my code of view controller.
#IBAction func add(_ sender: Any) {
//let defaults = UserDefaults.standard
let userDefaults = UserDefaults(suiteName: "group.com.number1")
var finArray = userDefaults!.object(forKey: "attribute") as? [Int64] ?? [Int64]()
let number1 = number.text!
finArray.append(Int64(number1)!)
print("from main add")
print(finArray)
//let userDefaults = UserDefaults(suiteName: "group.com.number1")
userDefaults!.set(finArray, forKey: "attribute")
CXCallDirectoryManager.sharedInstance.reloadExtension(withIdentifier: "com.akshat.Blocking-array.Callblockarray", completionHandler: {(error) -> Void in if let error = error {
print("akshat"+error.localizedDescription)
}})
}
Here is my code of callkit extension
// CallDirectoryHandler.swift
import Foundation
import CallKit
import CoreData
class CallDirectoryHandler: CXCallDirectoryProvider {
override func beginRequest(with context: CXCallDirectoryExtensionContext) {
context.delegate = self
print("inside beginrequest")
// let defaults = UserDefaults(suiteName: "group.com.number1")
// //(suiteName: "group.tag.number")
// let array = defaults!.object(forKey: "attribute") as? [Int64] ?? [Int64]()
// print(array)
// Check whether this is an "incremental" data request. If so, only provide the set of phone number blocking
// and identification entries which have been added or removed since the last time this extension's data was loaded.
// But the extension must still be prepared to provide the full set of data at any time, so add all blocking
// and identification phone numbers if the request is not incremental.
if context.isIncremental {
print("insideif")
addOrRemoveIncrementalBlockingPhoneNumbers(to: context)
print("insideif")
addOrRemoveIncrementalIdentificationPhoneNumbers(to: context)
} else {
addAllBlockingPhoneNumbers(to: context)
print("inside else")
addAllIdentificationPhoneNumbers(to: context)
}
context.completeRequest()
}
private func addAllBlockingPhoneNumbers(to context: CXCallDirectoryExtensionContext) {
// Retrieve all phone numbers to block from data store. For optimal performance and memory usage when there are many phone numbers,
// consider only loading a subset of numbers at a given time and using autorelease pool(s) to release objects allocated during each batch of numbers which are loaded.
//
// Numbers must be provided in numerically ascending order.
print("func addAllBlockingPhoneNumbers")
let defaults = UserDefaults(suiteName: "group.com.number1")
//(suiteName: "group.tag.number")
var array = defaults!.object(forKey: "attribute") as? [Int64] ?? [Int64]()
array.sort()
print(array)
let allPhoneNumbers: [CXCallDirectoryPhoneNumber] = array
for phoneNumber in allPhoneNumbers {
context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber)
}
}
private func addOrRemoveIncrementalBlockingPhoneNumbers(to context: CXCallDirectoryExtensionContext) {
// Retrieve any changes to the set of phone numbers to block from data store. For optimal performance and memory usage when there are many phone numbers,
// consider only loading a subset of numbers at a given time and using autorelease pool(s) to release objects allocated during each batch of numbers which are loaded.
print("addOrRemoveIncrementalBlockingPhoneNumbers")
let defaults = UserDefaults(suiteName: "group.com.number1")
//(suiteName: "group.tag.number")
var array = defaults!.object(forKey: "attribute") as? [Int64] ?? [Int64]()
array.sort()
print(array)
let phoneNumbersToAdd: [CXCallDirectoryPhoneNumber] = array
for phoneNumber in phoneNumbersToAdd {
print(phoneNumber)
context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber)
}
let phoneNumbersToRemove: [CXCallDirectoryPhoneNumber] = [ 1_877_555_5555 ]
for phoneNumber in phoneNumbersToRemove {
context.removeBlockingEntry(withPhoneNumber: phoneNumber)
}
// Record the most-recently loaded set of blocking entries in data store for the next incremental load...
}
private func addAllIdentificationPhoneNumbers(to context: CXCallDirectoryExtensionContext) {
// Retrieve phone numbers to identify and their identification labels from data store. For optimal performance and memory usage when there are many phone numbers,
// consider only loading a subset of numbers at a given time and using autorelease pool(s) to release objects allocated during each batch of numbers which are loaded.
//
// Numbers must be provided in numerically ascending order.
print("addAllIdentificationPhoneNumbers")
let allPhoneNumbers: [CXCallDirectoryPhoneNumber] = [ 1_877_555_5555, 1_888_555_5555 ]
let labels = [ "Telemarketer", "Local business" ]
for (phoneNumber, label) in zip(allPhoneNumbers, labels) {
context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label)
}
}
private func addOrRemoveIncrementalIdentificationPhoneNumbers(to context: CXCallDirectoryExtensionContext) {
print(addOrRemoveIncrementalIdentificationPhoneNumbers)
// Retrieve any changes to the set of phone numbers to identify (and their identification labels) from data store. For optimal performance and memory usage when there are many phone numbers,
// consider only loading a subset of numbers at a given time and using autorelease pool(s) to release objects allocated during each batch of numbers which are loaded.
let phoneNumbersToAdd: [CXCallDirectoryPhoneNumber] = [ 1_408_555_5678 ]
let labelsToAdd = [ "New local business" ]
for (phoneNumber, label) in zip(phoneNumbersToAdd, labelsToAdd) {
context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label)
}
let phoneNumbersToRemove: [CXCallDirectoryPhoneNumber] = [ 1_888_555_5555 ]
for phoneNumber in phoneNumbersToRemove {
context.removeIdentificationEntry(withPhoneNumber: phoneNumber)
}
// Record the most-recently loaded set of identification entries in data store for the next incremental load...
}
}
extension CallDirectoryHandler: CXCallDirectoryExtensionContextDelegate {
func requestFailed(for extensionContext: CXCallDirectoryExtensionContext, withError error: Error) {
print("CXCallDirectoryExtensionContext")
// An error occurred while adding blocking or identification entries, check the NSError for details.
// For Call Directory error codes, see the CXErrorCodeCallDirectoryManagerError enum in <CallKit/CXError.h>.
//
// This may be used to store the error details in a location accessible by the extension's containing app, so that the
// app may be notified about errors which occured while loading data even if the request to load data was initiated by
// the user in Settings instead of via the app itself.
}
}
But still, this is only working sometimes. Call Directory Extension is taking too long to update new numbers whenever I add them. For eg: If I write number 'A' and press add button then this add function will be called and number 'A' will get blocked. Now if I add one more number let say 'B' then call directory extension is not able to add it and B will not get blocked. And now if add one more number 'C' then all will get blocked. I am really not getting this. I hope someone could help me.

Swift Background Sync Manager

I want to make an Upload Manager Singleton that uploads multipart data every 10 minutes. The upload itself is clear but how can i make a class that uploads the data in this time interval in background?
Just to specify:
The data that I want to upload is a model that has an array of objects. Each of the objects has a flag and when this flag is set the object is ready for the upload. That whole "Sync-function" should be called once and repeat itself every 10 minutes, no matter on which ViewController I am. Does anyone know how I can do this?
The code uses some external frameworks. It is based on recursion.
Alamofire // for networking
*The above framework is not important. I just used it to fasten the development process.
Sync Manager
import Foundation
import Alamofire
let SyncMangerIdentifier = "com.example.background.syncmanger"
class SyncManager: Alamofire.Manager{
static let instance = SyncManager()
private var pendingTasks = [SyncTask]() // SyncTask is a class with 3 variables [image,audio,[tags]] that are being uploading to server
private var request: Request?
private var isSyncing = false
private init(){
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(SyncMangerIdentifier)
configuration.allowsCellularAccess = Config.cellularAccess
super.init(configuration: configuration)
}
// CALL THIS FUNCTION TO START THE SYNC
// variable isSyncing guards multiple execution of syncManager
func start(){
guard !isSyncing else {
// WE ARE ALREADY SYNCING
return
}
// CALL TO PREPARE FUNCTION TO EVALUATE WHETHER WE CAN SYNC OR NOT
prepare()
}
/*
initialize the syncItem variable with the first entry from SyncTask
if we are stopping return
if syncTask isEmpty stop
if there are no items in first syncTask remove the task and restart the process.
*/
private func prepare(){
// I use a database query to store & retrieve pendingTasks
guard !pendingTasks.isEmpty else{
// todo no more data to sync
isSyncing = false // syncing process ended
// Notify app that your long running task has finished
(UIApplication.sharedApplication().delegate as? AppDelegate)?.endBackgroundSyncTask()
return
}
isSyncing = true // we are in syncing process
// Notify app that our long running task has begun
(UIApplication.sharedApplication().delegate as? AppDelegate)?.beginBackgroundRestoreTask()
// Call function to start the first upload
uploadFileOrData()
}
}
/**
upload the files & data from array recursively
*/
private func uploadFileOrData(){
var task = pendingTasks[0]
let imageUrl = task.imageUrl
let audioUrl = task.audioUrl
let tags = task.tags.reduce(""){ prev, next in
if prev.isEmpty{
return next.text
}
return "\(prev),\(next.text)"
}
let form : (MultipartFormData) -> () = { data in
if imageUrl.checkResourceIsReachableAndReturnError(nil){
data.appendBodyPart(fileURL: imageUrl, name: "image")
}
if audioUrl.checkResourceIsReachableAndReturnError(nil){
data.appendBodyPart(fileURL: audioUrl, name: "audio")
}
data.appendBodyPart(data: tags.dataUsingEncoding(NSUTF8StringEncoding,allowLossyConversion: false)!, name: "tags")
}
upload(.POST, Api.fileUploadUrl, multipartFormData: form ,encodingCompletion: {
// Call function to process the response
self.processUploadFileResponse($0)
})
}
private func processUploadFileResponse(result: Manager.MultipartFormDataEncodingResult){
switch result {
case .Success(let upload, _, _):
// PERFORM ACTION ON SUCCESS
// MOVE TO NEXT LOCATION
self.moveToNextTask()
case .Failure(_):
// PERFORM ACTION ON FALIURE
// MOVE TO NEXT LOCATION
self.moveToNextTask()
}
}
private func moveToNextTask(){
// DELETE pendingTasks[0] & CALL prepare() function
// If you want to repeat after every 10 MINUTE
// Then wrap your function call 'prepare()' inside dispatch_after
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(10 * 60 * Double(NSEC_PER_SEC)) // 10 * 60 to convert seconds into minute
),
dispatch_get_main_queue(), {
self.prepare()
})
}
AppDelegate class
// bind the alamofire backgroundCompletionHandler
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
// NSLog("handle events for background: \(identifier)")
if identifier == SyncMangerIdentifier{
SyncManager.instance.backgroundCompletionHandler = completionHandler
}
}
// Identifier for long running background task for SyncManager class
var backgroundSyncTask: UIBackgroundTaskIdentifier?
// Call this at the beginning of syncing
func beginBackgroundSyncTask() {
backgroundRestoreTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundRestoreTask()
})
}
// Call this when syncing process ends
func endBackgroundSyncTask() {
guard backgroundSyncTask != nil else {
return
}
UIApplication.sharedApplication().endBackgroundTask(self.backgroundSyncTask!)
self.backgroundSyncTask = UIBackgroundTaskInvalid
}
Note
In order to continue request when your app enters background you may need to enable BackGroundFetchMode from app's Capabilities section
Since you want the method of upload to be called in all ViewControllers(VCs) One approach would be :-
extension UIViewController
{
func uploadData(parameters) ->Bool
{
return true/false;
}
}
Then in all ViewControllers(VCs) you can call uploadData method in viewDidLoad or in specific function like :-
if(self.uploadData(parameters)) // if true method is called i.e. new objects available to upload or 10mins have passed as per your requirement
{
}
Second Approach would be to define the NSTimer part which checks whether 10 mins have passed in AppDelegate and create a empty Swift file which does Upload and call the method in didFinishLaunchingWithOptions within AppDelegate.
There are many ways to go about it but it depends on how flow needs to happen in your app.
Note:- Use
NSURLSessionUploadTask - > To upload and
NSTimer -> To check whether 10 mins have passed

Swift dispatch_async from function

How can i wait until function get all data from alamofire get request?
GetData.swift file:
import Foundation
import Alamofire
import SwiftyJSON
import ObjectMapper
func getStartData() -> Void {
let sharedBranch = BranchSingleton.sharedInstance
let sharedArticle = ArticleSingleton.sharedInstance
Alamofire.request(.GET, Config().apiBranch)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let branch = Mapper<Branch>().map(obj.1.rawString()!)
sharedBranch.addBranch(branch!)
}
}
Alamofire.request(.GET, Config().apiArticle)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let article = Mapper<Article>().map(obj.1.rawString()!)
sharedArticle.addArticle(article!)
}
}
}
ViewController.swift file:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getStartData() // need to wait until all requests are finished then do print
print(sharedArticle.articleList)
}
}
SingletonObj.swift file:
import Foundation
class BranchSingleton {
var branchList: [Branch] = []
class var sharedInstance: BranchSingleton {
struct Static {
static let instance: BranchSingleton = BranchSingleton()
}
return Static.instance
}
func addBranch(branch: Branch) {
branchList.append(branch)
}
}
class ArticleSingleton {
var articleList: [Article] = []
class var sharedInstance: ArticleSingleton {
struct Static {
static let instance: ArticleSingleton = ArticleSingleton()
}
return Static.instance
}
func addArticle(article: Article) {
articleList.append(article)
}
}
i need to wait until getStartData() finish, then pring singleton array..
How can i do that?
This getStartData contains more than 2 requests, but i just gave example with 2..
You're asking a non-question. There is no reason to "wait". Nor can you. You just do what you do, asynchronously. Meanwhile the interface must stay active; the user must be able to continue to work. Thus there is nothing to "wait" for.
Now, if the question is, how can you send a signal in some elegant way to the rest of your app when all of the requests are done, one good answer is to use NSProgress. All the different requests can contribute to a common NSProgress object. The nice thing is that its fractionCompleted is observable with KVO, so when it comes greater-than-or-equal-to 1.0, you're done.
But you don't actually need the NSProgress; you could just increment or decrement an instance variable that's KVO-observable (being careful about threading, of course). If you know there are n processes, then you could just start a variable at n and have each process decrement it when it completes; a didSet observer on the variable can then take action when we hit zero.
The point is: you don't "wait": you just have all the different activities contribute to some common central value that "knows" when this means we've "finished" and can then take action.
As #Matt says, you can't, and shouldn't, try to wait until Alamofire is done with your request. That's like hiring somebody to run an errand for so you can work and then stopping everything and sitting by the door until they get back. You might as well have run the errand yourself.
Dropping the analogy, you might as well have performed the task synchronously. However, synchronous networking is a very bad idea. It freezes the UI until the network request is complete, which can be a very long wait if something goes wrong.
An async method like Alamofire's request method takes a completion block, a block of code that should be run when the work is finished.
The request method returns immediately, before the request has even been sent to the server, much less completed.
Instead of waiting around for the request to complete, you should refactor your getStartData method to take a completion handler, and use that to respond once the work is done:
func getStartData(completion: () -> void) -> Void {
let sharedBranch = BranchSingleton.sharedInstance
let sharedArticle = ArticleSingleton.sharedInstance
Alamofire.request(.GET, Config().apiBranch)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let branch = Mapper<Branch>().map(obj.1.rawString()!)
sharedBranch.addBranch(branch!)
}
}
Alamofire.request(.GET, Config().apiArticle)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let article = Mapper<Article>().map(obj.1.rawString()!)
sharedArticle.addArticle(article!)
}
//At this point the Alamofire .GET request for Config().apiArticle
//is complete. Call our completion block (passed in as a parameter)
completion()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getStartData()
{
//This is a "trailing closure", a block of code passed to getStartData
print("At this point, we've finished getting our data from Alamofire.")
print(sharedArticle.articleList)
}
}
}
Note that your getStartData method makes 2 Alamofire.request() commands in a row. If the second request requires that the first request be finished then you will need to restructure that code so that the second Alamofire request is inside the completion block for the first call. (That's more editing than I'm in the mood to do at the moment.)

NSURLConnection Asynchronous Request inside a for-loop does not properly download NSData (Swift)

I've written a class called Movie whose initializer takes an integer "id" to retrieve data from the Rotten Tomatoes API:
init(id: Int) {
let movieURL = NSURL(string: "http://api.rottentomatoes.com/api/public/v1.0/movies/\(id).json?apikey=\(apiKey)")!
NSURLConnection.sendAsynchronousRequest(NSURLRequest(URL: movieURL), queue: NSOperationQueue()) { (response, movieData, error) -> Void in
var movieJson = NSJSONSerialization.JSONObjectWithData(movieData, options: NSJSONReadingOptions.MutableContainers, error: nil) as? [String: AnyObject]
self.id = self.idFromMovieJson(movieJson)
self.title = self.titleFromMovieJson(movieJson)
// ...
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.delegate!.movieDidDownload(self)
})
}
}
If I instantiate a Movie object with a correct id, everything goes as expected. This is what I've written in another class:
var movie = Movie(id: 771351912)
movie.delegate = self
func movieDidDownload(movie: Movie) {
println(movie.title)
}
And this is the output:
Optional("Interstellar")
However, when I try to instantiate a Movie object inside a for-loop like this:
let ids = [771351912, 771380953, 771041011, 13863, 12490, 771311818, 771321699, 11691]
for id in ids {
var movie = Movie(id: id)
movie.delegate = self
}
The print results are not very encouraging:
Optional("Super 8")
Optional("Interstellar")
Optional("Pulp Fiction")
nil
Optional("The Nightmare Before Christmas")
nil
nil
Optional("Nightcrawler")
Worst of all, the println() output is different each time I build and run my code:
Optional("Interstellar")
Optional("Pulp Fiction")
Optional("Super 8")
Optional("Nightcrawler")
Optional("The Nightmare Before Christmas")
nil
nil
nil
I even tried to create different NSOperationQueues with different names to use in the NSURLConnection.sendAsynchronousRequest() method but that didn't work out too:
var queue = NSOperationQueue()
queue.name = "\(id)"
I guess that the problem is related to the fact I'm sending too many requests at the same time. I've placed sleep(1) in the ids for-loop and it actually prints the movie titles properly.
Does anybody know how to asynchronously make multiple requests inside a for-loop?
The swift println() function is asynchronous, and the order of delivery isn't guaranteed, so you may get all kinds of confusing results if you use it like this.
I'd suggest switching to using NSLog() instead, as that will give more consistent results.
Also, creating a new throwaway NSOperationQueue() for each request seems like a bad idea, as the queue may get released before the operation executes. Try using NSOperationQueue.mainQueue() instead.
As an added benefit, using a single queue instead of a different queue for each request should ensure that the movies get downloaded in the order you've requested them.

Resources