Reputation: 11787
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.
Upvotes: 9
Views: 2495
Reputation: 11587
array.swapAt(i, j) // will swap elements at i and j
protocol MutableCollection {
/// Exchange the values at indices `i` and `j`.
///
/// Has no effect when `i` and `j` are equal.
public mutating func swapAt(_ i: Index, _ j: Index)
}
See SE-0173 Add MutableCollection.swapAt(::) for more information about swapAt.
Upvotes: 1
Reputation: 11787
This actually ended up being much easier than expected. Since the currently loaded post is being held in activeConnections
, I can simply replace the allPosts
value at the loaded index with the currently loaded post:
allPosts[loadedIndex] = loadedPost
I can't think of any downsides to this method, but if anyone thinks of any, please let me know.
Upvotes: 2
Reputation: 939
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.
Upvotes: 2
Reputation: 1941
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)}
Upvotes: 2
Reputation: 2042
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"]
})
Upvotes: 3
Reputation: 299703
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])
Upvotes: 14