在 Swift 中是不是有一种简洁的方法可以将完成块附加到 NSURLSessionDataDelegate 回调?

Posted

技术标签:

【中文标题】在 Swift 中是不是有一种简洁的方法可以将完成块附加到 NSURLSessionDataDelegate 回调?【英文标题】:Is There A Neat Way to Attach a Completion Block to an NSURLSessionDataDelegate Callback in Swift?在 Swift 中是否有一种简洁的方法可以将完成块附加到 NSURLSessionDataDelegate 回调? 【发布时间】:2015-06-14 21:12:18 【问题描述】:

好的,这是交易:

我在 Swift 应用程序中进行 URL 调用,有点像这样:

/*!
    @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 附加到任务的方法。我宁愿将辅助完成块直接附加到任务,而不是使用单独的字典。

我很乐意被告知我的方法很糟糕,只要我得到更好的东西。

有接受者吗?

谢谢!

【问题讨论】:

棘手的,你考虑过扩展吗? 是的,但扩展不允许您添加数据存储;只是功能。我可以添加一个“属性”,但它只会作为 setter/getter 存在。 【参考方案1】:

我过去所做的是创建某种事务对象。我设置了一个下载管理器(作为一个单例)来创建一个事务对象数组。我使事务对象成为 URL 会话的代表(实际上这早于 NSURLSession - 我用 NSURLConnection 做的,但想法是一样的。)

事务对象也有一个完成块参数。 然后当下载完成时,事务对象调用它的完成块,然后通知下载管理器单例它已经完成并准备好被清理。

【讨论】:

好主意!我会看看如何实施。 嗯...仔细想想,我怀疑我可能仍然使用字典,但将它附加到会话而不是使用静态。我需要保持会话打开,并在多次调用中重复使用它。 当然。因此,让下载管理器拥有会话,并使您的字典成为下载管理器类的实例变量。 嗯,我已经以这种方式实现了它,但可能要再过一天左右才能报告它的有效性。我还做了一些其他需要更多工作的重构。谢谢! 初始测试很好。我需要在有很多快速射击请求的射击场试一试,但那还有很长的路要走。

以上是关于在 Swift 中是不是有一种简洁的方法可以将完成块附加到 NSURLSessionDataDelegate 回调?的主要内容,如果未能解决你的问题,请参考以下文章

是否有一种更简洁的方法可以在 c++(11) 中复制具有多类型值的 unordered_map

是否有一种简洁的方法可以为同一个扩展定义两个或多个 bash 别名?

在 Visual Studio 2017 中,是不是有一种快速方法可以将“std::”添加到缺少它的名称中?

如何使用 Swift 从 URL 获取 HTML 源代码

是否有一种更简洁的 Dapper 方法来仅更新随 Dapper 更改的列?

在 Swiftui 中是不是有一种简单的方法可以通过捏合来放大图像?