基于 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。 (NSURLSessionNSURLSessionTask 工厂。)如果您的所有操作都使用相同的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 子类来实现这一点?

您所描述的是NSURLRequestNSURLSessionNSURLConnection 都使用 NSURLRequests 来执行网络连接(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

创建与上述类似的内容以满足您的需求。然后可以使用生成的NSURLRequests 来创建连接。在 Cocoa 中,这是一种非常典型的自定义对象模式,而不是子类化。子类化非常罕见——相反,Cocoa 有其他机制来定制类行为,从工厂方法(像上面的“便利方法”)到委托(一个对象被赋予另一个对象的行为责任)。

【讨论】:

【参考方案4】:

我所做的 - 只是允许 OperationURLSessionTask 保持分开。为此,我进行了通用异步块操作,用于实例化、恢复、取消URLSessionTask

因此,在 ViewController 或数据层中,我仍在使用链式 OperationsURLSessionTasks 内部。下面的示例代码可以通过继承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 的网络模式的主要内容,如果未能解决你的问题,请参考以下文章

NSURLSession -- 备忘

NSURLSession学习笔记简介

NSURLSession学习笔记简介

搞定 Kubernetes 基于flannel 的集群网络

基于成熟网管平台的网管软件开发模式

NSURLSession 后台上传 - 需要启用后台模式?