Есть ли аккуратный способ присоединить блок завершения к обратному вызову NSURLSessionDataDelegate в Swift?

Хорошо, вот сделка:

У меня есть URL-вызов, который я делаю в приложении Swift, примерно так:

/*!
    @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
}

На самом деле, именно так, как я использую функцию (функция статического класса).

Проблемная черта такая:

self.urlExtraData.updateValue(inCompletionBlock, forKey: testURIString)

"urlExtraData" - это словарь, который я объявляю ранее:

/*!
    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

и назначьте это здесь:

// Set up an empty dictionary for the URL refCon data.
BMLTAdminAppDelegate.urlExtraData = Dictionary<String,requestCompletionBlock!>()

Блок завершения typedef находится здесь:

/*!
    @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

Настройка сеанса здесь:

BMLTAdminAppDelegate.connectionSession = NSURLSession(configuration: config, delegate:self, delegateQueue: NSOperationQueue.mainQueue())

Обработчик ответа делегата находится здесь:

/*!
    @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)
        }
    }
}

Я думаю, что использование словаря для удержания второго обратного вызова - отвратительный, вонючий взлом. Однако я не нашел способа прикрепить refCon к задаче. Я бы предпочел прикрепить дополнительный блок завершения непосредственно к задаче, а не использовать отдельный словарь.

Я хотел бы, чтобы мне сказали, что моя методология плохая, если мне дают что-то лучшее.

Любой берущий?

Спасибо!

1 ответ

Решение

То, что я делал в прошлом, - это создание какого-либо объекта транзакции. Я установил менеджер загрузок (как одиночный) для создания массива объектов транзакций. Я делаю объект транзакции делегатом сеанса URL (на самом деле это предшествует NSURLSession - я сделал это с NSURLConnection, но идея та же.)

У объекта транзакции также есть параметр блока завершения. Затем, когда загрузка завершается, объект транзакции вызывает свой блок завершения, а затем уведомляет синглтон диспетчера загрузки о том, что он завершен и готов к очистке.

Другие вопросы по тегам