OK, here's the deal:
I have a URL call I'm making in a Swift app, sort of like so:
/*!
@brief Tests a given Root Server URL for validity
@discussion What we do here, is append "/client_interface/serverInfo.xml"
to the given URI, and test that for validity.
@param inURIAsAString This contains a string, with the URI.
@param inCompletionBlock This is the completion block supplied by the caller. It is to be called upon receipt of data.
@returns an implicitly unwrapped optional String. This is the given URI, "cleaned up."
*/
class func testRootServerURI(inURIAsAString:String, inCompletionBlock:requestCompletionBlock!) -> String! {
// First, trim off any trailing slashes.
var ret:String! = inURIAsAString.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: "/"))
// Next, make the string entirely lowercase.
ret = ret.lowercaseString
// Add the "http://" if necessary.
if(!ret.beginsWith ("http")) {
ret = "http://" + ret
}
// This is the URI we will actually test.
let testURIString = ret + "/client_interface/serverInfo.xml"
#if DEBUG
print("Testing \(testURIString).")
#endif
let url:NSURL! = NSURL(string: testURIString)
// We can't have the URL already in play. That would be bad.
if(nil == self.urlExtraData[testURIString]) {
// Assuming we have a completion block and a URI, we will actually try to get a version from the server (determine its validity).
if((nil != inCompletionBlock) && (nil != ret)) {
// Store the completion block for recall later.
self.urlExtraData.updateValue(inCompletionBlock, forKey: testURIString)
let dataTask:NSURLSessionTask = BMLTAdminAppDelegate.connectionSession.dataTaskWithURL(url)!
dataTask.resume()
}
}
else {
ret = nil
}
return ret
}
Actually, exactly like that, as it's the function that I'm using (a static class function).
The problematic line is this one:
self.urlExtraData.updateValue(inCompletionBlock, forKey: testURIString)
"urlExtraData" is a Dictionary that I declare earlier:
/*!
This is a dictionary of callbacks for open requests. It keys on the URL called.
@discussion I hate pulling crap like this, as it's clumsy and thread-unfriendly. Unfortunately,
there doesn't seem to be much choice, as there's no way to attach a refCon to a URL
task.
*/
static var urlExtraData:Dictionary<String,requestCompletionBlock!>! = nil
and assign it here:
// Set up an empty dictionary for the URL refCon data.
BMLTAdminAppDelegate.urlExtraData = Dictionary<String,requestCompletionBlock!>()
The completion block typedef is here:
/*!
@brief This is the definition for the testRootServerURI completion block.
@discussion The routine is called upon completion of a URL connection. When the
connection ends (either successfully or not), this routine is called.
If it is successful, then the inData parameter will be non-nil.
If it failed, then the parameter will be nil.
@param inData the Data returned.
*/
typealias requestCompletionBlock = (inData: NSData!)->Void
The session setup is here:
BMLTAdminAppDelegate.connectionSession = NSURLSession(configuration: config, delegate:self, delegateQueue: NSOperationQueue.mainQueue())
The delegate response handler is is here:
/*!
@brief Called when a task receives data.
@param session The NSURLSession that controls this task.
@param dataTask The task responsible for this callback.
@param data The data returned.
*/
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
let url = dataTask.currentRequest?.URL
// We don't do squat if there is no callback in our table.
if(nil != BMLTAdminAppDelegate.urlExtraData.indexForKey((url?.absoluteString)!)) {
// Fetch the stored comnpletion block from our dictionary.
let callback:requestCompletionBlock! = BMLTAdminAppDelegate.urlExtraData[(url?.absoluteString)!]
// If one was provided (always check), then call it.
if(nil != callback) {
BMLTAdminAppDelegate.urlExtraData.removeValueForKey((url?.absoluteString)!) // Remove the callback from the dictionary.
callback(inData: data)
}
}
}
I think using a dictionary to hold the second callback is a nasty, smelly hack. However, I have found no way to attach a refCon to the task. I'd much rather attach the secondary completion block directly to the task, as opposed to using a separate dictionary.
I'd love to be told my methodology is bad, as long as I am given something better.
Any takers?
Thanks!