I'm creating asynchronous NSURLConnections
for images based off of an array of dictionaries, each with their own image URL:
var posts = [
["url": "url0", "calledIndex": 0],
["url": "url1", "calledIndex": 1],
["url": "url2", "calledIndex": 2],
["url": "url3", "calledIndex": 3]
]
Given the asynchronous nature of the connections (which is what I want, fastest images load first), the images might load in a different order, such as:
url0
url2
url3
url1
If the images are loaded out of order, however, the original posts
array needs to be reorganized according to when the images loaded. So, given the above example, posts
should now look like:
var posts = [
["url": "url0", "calledIndex": 0],
["url": "url2", "calledIndex": 2],
["url": "url3", "calledIndex": 3],
["url": "url1", "calledIndex": 1]
]
Is there any way in Swift to swap values of an array at a specific index with values from the same array at a different index? I first attempted this by using the swap
function:
// Index the images load
var loadedIndex = 0
func connectionDidFinishLoading(connection: NSURLConnection) {
// Index of the called image in posts
let calledIndex = posts["calledIndex"] as! Int
// Index that the image actually loaded
let loadedIndex = loadedIndex
// If the indicies are the same, the image is already in the correct position
if loadedIndex != calledIndex {
// If they're not the same, swap them
swap(&posts[calledIndex], &posts[loadedIndex])
}
}
I then attempted something similar without the swap
function:
// The post that was actually loaded
let loadedPost = posts[calledIndex]
// The post at the correct index
let postAtCorrectIndex = posts[loadedIndex]
posts[calledIndex] = postAtCorrectIndex
posts[loadedIndex] = loadedPost
In both scenarios, however, the array values are not correctly swapped. I realize this is a logic error, but I'm failing to see where the error actually lies.
As far as I can tell, it's swapping correctly the first time, but then the new dictionary has an incorrect calledIndex
value, causing it swap back to its original position.
This hypothesis might be completely wrong, and I realize I'm having a difficult time describing the situation, but I will attempt to provide as much clarification as possible.
I made a test case, you can download the source code here. The code for it is:
var allPosts:Array<Dictionary<String, AnyObject>> = [
["imageURL": "http://i.imgur.com/aLsnGqn.jpg", "postTitle":"0"],
["imageURL": "http://i.imgur.com/vgTXEYY.png", "postTitle":"1"],
["imageURL": "http://i.imgur.com/OXzDEA6.jpg", "postTitle":"2"],
["imageURL": "http://i.imgur.com/ilOKOx5.jpg", "postTitle":"3"],
]
var lastIndex = 0
var threshold = 4
var activeConnections = Dictionary<NSURLConnection, Dictionary<String, AnyObject?>>()
func loadBatchInForwardDirection(){
func createConnection(i: Int){
allPosts[i]["calledIndex"] = i
var post = allPosts[i]
let imageURL = NSURL(string: post["imageURL"] as! String)
if imageURL != nil {
let request = NSMutableURLRequest(URL: imageURL!, cachePolicy: .ReloadIgnoringLocalCacheData, timeoutInterval: 60)
let connection = NSURLConnection(request: request, delegate: self, startImmediately: true)
if connection != nil {
activeConnections[connection!] = post
}
}
}
let startingIndex = lastIndex;
for (var i = startingIndex; i < startingIndex + threshold; i++){
createConnection(i)
lastIndex++
}
}
func connection(connection: NSURLConnection, didReceiveData data: NSData) {
if activeConnections[connection] != nil {
let dataDict = activeConnections[connection]!["data"]
if dataDict == nil {
activeConnections[connection]!["data"] = NSMutableData(data: data)
} else {
(activeConnections[connection]!["data"] as! NSMutableData).appendData(data)
}
}
}
var loadedIndex = 0
func connectionDidFinishLoading(connection: NSURLConnection) {
let loadedPost = activeConnections[connection]!
activeConnections.removeValueForKey(connection)
let data = loadedPost["data"] as? NSData
let calledIndex = loadedPost["calledIndex"] as! Int
println(calledIndex)
swap(&allPosts[calledIndex], &allPosts[loadedIndex])
//(allPosts[calledIndex], allPosts[loadedIndex]) = (allPosts[loadedIndex], allPosts[calledIndex])
loadedIndex++
done(loadedIndex)
}
func done(index: Int){
if index == 4 {
println()
println("Actual: ")
println(allPosts[0]["postTitle"] as! String)
println(allPosts[1]["postTitle"] as! String)
println(allPosts[2]["postTitle"] as! String)
println(allPosts[3]["postTitle"] as! String)
}
}
func applicationDidFinishLaunching(aNotification: NSNotification) {
loadBatchInForwardDirection()
println("Loaded: ")
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
The output is:
Loaded: 1 0 2 3
Actual: 0 1 2 3
However the expected, "Actual" output should be:
1 0 2 3
It's worth noting that using the tuples code results in slightly wonky results, but nothing that matches the actual order. You can see what I mean by uncommenting that line.
The built-in swap() function can swap two values in an array . template <class T> void swap (T& a, T& b); The swap() function takes two arguments of any data type, i.e., the two values that need to be swapped.
To swap elements of two arrays you have to swap each pair of elemenets separatly. And you have to supply the number of elements in the arrays. Otherwise the arrays need to have a sentinel value. Here is a demonstrative program that shows how the function swap can be defined.
To change the position of an element in an array:Use the splice() method to insert the element at the new index in the array. The splice method changes the original array by removing or replacing existing elements, or adding new elements at a specific index.
Array indexing starts at zero in C; you cannot change that.
You can just assign via tuples:
var xs = [1,2,3]
(xs[1], xs[2]) = (xs[2], xs[1])
But what problem are you actually having with swap
? The following should work fine:
swap(&xs[1], &xs[2])
If you can change the changeIndex's value type to String, then below code should work
var posts = [
["url": "url0", "calledIndex": "0"],
["url": "url2", "calledIndex": "2"],
["url": "url3", "calledIndex": "3"],
["url": "url1", "calledIndex": "1"]
]
posts = sorted(posts, { (s1: [String:String], s2: [String:String]) -> Bool in
return s1["calledIndex"] < s2["calledIndex"]
})
You can just use sort on the calledIndex value:
var posts = [
["url": "url0", "calledIndex": 0],
["url": "url2", "calledIndex": 2],
["url": "url1", "calledIndex": 1],
["url": "url3", "calledIndex": 3]
]
var sortedPosts = sorted(posts) { ($0["calledIndex"] as! Int) < ($1["calledIndex"] as! Int)}
The problem is that swapping the values doesn't give you the right order, as you may swap an already swapped value. As an example, if you receive in the order 3,0,1,2, then you'll swap:
array_before called_index loaded_index array_after
0, 1, 2, 3 0 3 3, 1, 2, 0
3, 1, 2, 0 1 0 1, 3, 2, 0
1, 3, 2, 0 2 1 1, 2, 3, 0
1, 2, 3, 0 3 2 1, 2, 0, 3
So this will give you 1,2,0,3 even though you received (and correctly swapped) 3,0,1,2.
If you want the swapping to work you'll have to keep track of what's been swapped so you know which index to swap into. It's probably going to be easier to add to a new array as you get the data back, or to add a new field to storing the loaded index and sort on that at the very end.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With