基于 NSURLSession 的网络模式
Posted
技术标签:
【中文标题】基于 NSURLSession 的网络模式【英文标题】:Networking pattern based on NSURLSession 【发布时间】:2014-01-15 20:56:03 【问题描述】:我一直使用 NSOperation 子类创建和管理自己的 NSURLConnection 的模式。 NSOperation 子类由视图控制器实例化,并且在完成之前不会打扰控制器的工作。当它完成检索数据时,它会执行视图控制器提供的完成块。
-
ViewController 实例化 NSOperation 子类(封装了 URL、参数等)
NSOperation 子类实例化 NSURLConnection(执行同步请求并检索数据)
NSURLConnection 将数据转储到 NSOperation 子类
NSOperation 子类执行视图控制器提供的完成块。
我现在正在尝试使用 NSURLSession 实现相同的模式。我希望能够将发出网络请求所需的 url 和参数封装在单个对象中。我是否使用 NSURLSession 子类或 NSURLSessionTask 子类来实现这一点?
我喜欢根据参与者设计模式为每个网络操作创建单独的类。
【问题讨论】:
你有没有尝试看看 AFNetworking ?我认为没有理由再次发明*** :) AFNetworking 适用于一次性请求。这里我基本上是在尝试将请求封装到单独的类中。 @GrzegorzKrukowski 在 AFN 中,如果它使用底层NSURLSessionTask
,则 AFN 类不是 NSOperation
的子类。只有当它使用底层NSURLConnection
时,对应的AFN类才是NSOperation
的子类。如果需要 NSOperation 子类,则不能将 AFN 与 NSURLSessionTask
一起使用。
@CouchDeveloper "如果你需要一个 NSOperation 子类" 它实际上是相反的。我试图取消创建 NSOperation 子类。但我希望将所有请求封装到单独的类中。我希望我的视图控制器使用参数实例化 GetSomeResourceFromNetwork 并将其传递给完成黑色。
@NSExplorer,请参阅我的更新答案。你不能继承NSURLSessionTask
,但你可以把它包装在一个瘦对象中。
【参考方案1】:
NSURLSessionTask
类(及其子类)看起来有点像操作,但它们不是。因此,您可以在转换到NSURLSession
时从代码中删除操作,但如果这样做,您将失去某些NSOperation
功能(依赖关系、控制并发度等)。我不确定为什么在转换到NSURLSession
时要从代码中删除操作。就我个人而言,我曾经在操作中包装 NSURLConnection
的任何地方,现在都使用操作包装 NSURLSessionTask
。
顺便说一句,NSURLSession
的一大烦恼是 task 委托设置在 session 对象中。我们可以猜测苹果为什么这样做,但它有各种不幸的含义。显然,您可以通过使用基于块的工厂方法来创建任务来解决这个问题,但是如果您碰巧需要,您会失去委托 API 的丰富性。
这意味着如果使用基于块的任务工厂方法,将任务包装在并发NSOperation
子类中是相当明显的。但是,如果使用基于委托的任务,但是,如果您想要任务的自定义处理程序,您必须通过维护任务标识符和适当的完成块(我个人将其放入会话管理器对象)之间的映射来进行一些愚蠢的操作我用来包装NSURLSession
)。 (仅供参考,我相信在即将到来的 AFNetworking 更新中也有望实现这样的实现。请参阅 AFNetworking github 站点上Issue 1504 讨论的后半部分。)
无论如何,其他人已经回答了如何将基于操作的 NSURLConnection
代码替换为非基于操作的 NSURLSession
代码的问题,但我个人建议继续使用操作。
顺便说一句,我在 github 上上传了一个基于操作的 NSURLSession
实现的示例实现:https://github.com/robertmryan/NetworkManager
这不是一个完整的解决方案,而是说明如何使用NSOperation
子类实现基于委托的NSURLSession
。
【讨论】:
【参考方案2】:您可以使用相同的模式,将NSURLConnection
替换为NSURLSessionTask
子类(例如NSURLSessionDataTask
)。
正如@CouchDeveloper 在 cmets 中建议的那样,另一种方法是将NSURLSessionTask
包装在具有异步语义(取消、恢复等)的非NSOperation
对象中。这个包装器对象只会对参数进行编码和解码,将大部分操作委托给包装的任务。
无论哪种情况,要实例化NSURLSessionTask
,您都需要NSURLSession
。 (NSURLSession
是NSURLSessionTask
工厂。)如果您的所有操作都使用相同的configuration(cookies、代理、缓存等),您可以简单地使用共享会话(+[NSURLSession sharedSession]
)。如果他们需要不同的配置,您必须给他们一个NSURLSession
或足够的信息来创建他们自己的。
【讨论】:
我想完全取消 NSOperation 的子类化。有没有一种方法可以继承 NSURLSessionTask,在 init 中设置默认参数,然后简单地为每个资源分配一个,例如 GetPersonDataSessionTask、PostPersonDataSessionTask? 由于多种原因,无法继承NSURLSessionTask
并使其正常工作。但是,您可以使用具有与NSOperation
类似的 API 和行为的 custom 类,但它不是它的子类。它应该采用异步风格,即它应该有一个start
方法。它也应该是可取消的,也就是说它应该有一个cancel
方法,并且它应该有一个完成信号的方法,一个完成处理程序或某种承诺或未来。
@CouchDeveloper 您可能无法轻松地将NSURLSessionTask
子类化,但您可以轻松地将这些任务包装在NSOperation
子类中(我已经使用基于块的任务(微不足道)和基于委托的任务(不平凡,但也不错))。所以,我不是在追随你为什么不认为你不能做到这一点,以及通过使用像NSOperation
这样的 API 制作你自己的非 NSOperation 类来获得什么。也许另一种方法对后台会话有用,但对于标准会话,基于操作的方法似乎并没有那么糟糕。
@Rob 你当然可以创建NSOperation
的子类并包装NSURLSessionTask
。 OP 只是不想想要从NSOperation
继承。由于NSOperation
的 API 非常适合,因此我建议 OP 的自定义类具有“相似”的 API。
对不起,是的。很好。我必须承认,我愉快地忽略了问题的那一部分。有一种很自然的误解,认为“哦,会话任务看起来就像操作,所以我现在可以在我的代码中取消操作。”但我相信您知道,情况并非如此,任务不是操作,如果有人使用NSURLConnection
进行操作,您可能仍然会使用NSURLSessionTask
这样做(后台会话除外) .但我想如果 OP 想要失去带来的功能操作,我想他可以,虽然我不确定他为什么想要。【参考方案3】:
我希望能够将发出网络请求所需的 url 和参数封装在单个对象中。我是否使用 NSURLSession 子类或 NSURLSessionTask 子类来实现这一点?
您所描述的是NSURLRequest
。 NSURLSession
和 NSURLConnection
都使用 NSURLRequest
s 来执行网络连接(NSURLRequest
是它正在执行的操作)。您似乎想要的是一组用于生成不同的、专门的NSURLRequests
的工厂方法。例如,描述“获取我的邮件”请求的请求。
您可以通过在NSURLRequest
本身上创建一个类别来轻松做到这一点。示例:
@implementation NSURLRequest (Mail)
+ (instancetype) mailRequestForUser:(NSString *)user
NSURLRequest *result = nil;
result = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"someServer""]];
// set user in a header, whatever
// return immutable copy
return [result copy];
@end
创建与上述类似的内容以满足您的需求。然后可以使用生成的NSURLRequest
s 来创建连接。在 Cocoa 中,这是一种非常典型的自定义对象模式,而不是子类化。子类化非常罕见——相反,Cocoa 有其他机制来定制类行为,从工厂方法(像上面的“便利方法”)到委托(一个对象被赋予另一个对象的行为责任)。
【讨论】:
【参考方案4】:我所做的 - 只是允许 Operation
和 URLSessionTask
保持分开。为此,我进行了通用异步块操作,用于实例化、恢复、取消URLSessionTask
。
因此,在 ViewController 或数据层中,我仍在使用链式 Operations
和 URLSessionTasks
内部。下面的示例代码可以通过继承AsynchronousBlockOperation
来扩展。
文件 1. 通用异步操作。
open class AsynchronousOperation: Operation
private var lockOfProperties = NonRecursiveLock.makeDefaultLock()
private var mFinished = false
private var mExecuting = false
public override init()
super.init()
/// Subclasses must launch job here.
///
/// **Note** called between willChangeValueForKey and didChangeValueForKey calls, but after property mExecuting is set.
open func onStart()
/// Subclasses must cancel job here.
///
/// **Note** called immediately after calling super.cancel().
open func onCancel()
/// Subclasses must release job here.
///
/// **Note** called between willChangeValueForKey and didChangeValueForKey calls,
/// but after properties mExecuting and mFinished are set.
open func onFinish()
extension AsynchronousOperation
public final override var isAsynchronous: Bool
return true
public final override var isExecuting: Bool
return lockOfProperties.synchronized mExecuting
public final override var isFinished: Bool
return lockOfProperties.synchronized mFinished
extension AsynchronousOperation
public final override func start()
if isCancelled || isFinished || isExecuting
return
willChangeValue(forKey: #keyPath(Operation.isExecuting))
lockOfProperties.synchronized mExecuting = true
onStart()
didChangeValue(forKey: #keyPath(Operation.isExecuting))
public final override func cancel()
super.cancel()
if isExecuting
onCancel()
finish()
else
onCancel()
lockOfProperties.synchronized
mExecuting = false
mFinished = true
public final func finish()
willChangeValue(forKey: #keyPath(Operation.isExecuting))
willChangeValue(forKey: #keyPath(Operation.isFinished))
lockOfProperties.synchronized
mExecuting = false
mFinished = true
onFinish()
didChangeValue(forKey: #keyPath(Operation.isExecuting))
didChangeValue(forKey: #keyPath(Operation.isFinished))
文件2.基于异步块的操作:
open class AsynchronousBlockOperation: AsynchronousOperation
public typealias WorkItemType = OperationCancelationType & OperationResumingType
public typealias FinaliseBlock = () -> Void
public typealias WorkItemBlock = (@escaping FinaliseBlock) -> WorkItemType?
private var executionBlock: WorkItemBlock
private var blockExecutionQueue: DispatchQueue?
private var workItemToken: WorkItemType?
public init(blockExecutionQueue: DispatchQueue? = nil, executionBlock: @escaping WorkItemBlock)
self.blockExecutionQueue = blockExecutionQueue
self.executionBlock = executionBlock
super.init()
open override func onStart()
if let queue = blockExecutionQueue
queue.async
self.execute()
else
execute()
open override func onCancel()
workItemToken?.cancelOperation()
private func execute()
workItemToken = executionBlock [weak self] in
self?.finish()
if var token = workItemToken
token.resumeOperation()
else
finish()
文件 3. 协议
public protocol OperationCancelationType
mutating func cancelOperation()
public protocol OperationResumingType
mutating func resumeOperation()
extension URLSessionTask: OperationCancelationType
public func cancelOperation()
cancel()
extension URLSessionTask: OperationResumingType
public func resumeOperation()
resume()
用法:
let operation = AsynchronousBlockOperation [weak self] finalise in
return session.dataTask(with: url)
...
finalise() // This will finish operation
【讨论】:
以上是关于基于 NSURLSession 的网络模式的主要内容,如果未能解决你的问题,请参考以下文章