【问题标题】:Networking pattern based on NSURLSession基于 NSURLSession 的网络模式
【发布时间】:2014-01-15 20:56:03
【问题描述】:

我一直使用 NSOperation 子类创建和管理自己的 NSURLConnection 的模式。 NSOperation 子类由视图控制器实例化,并且在完成之前不会打扰控制器的工作。当它完成检索数据时,它会执行视图控制器提供的完成块。

  1. ViewController 实例化 NSOperation 子类(封装了 URL、参数等)
  2. NSOperation 子类实例化 NSURLConnection(执行同步请求并检索数据)
  3. NSURLConnection 将数据转储到 NSOperation 子类
  4. NSOperation 子类执行视图控制器提供的完成块。

我现在正在尝试使用 NSURLSession 实现相同的模式。我希望能够将发出网络请求所需的 url 和参数封装在单个对象中。我是否使用 NSURLSession 子类或 NSURLSessionTask 子类来实现这一点?

我喜欢根据参与者设计模式为每个网络操作创建单独的类。

【问题讨论】:

  • 你有没有尝试看看 AFNetworking ?我认为没有理由再次发明轮子 :)
  • AFNetworking 适用于一次性请求。这里我基本上是在尝试将请求封装到单独的类中。
  • @GrzegorzKrukowski 在 AFN 中,如果它使用底层 NSURLSessionTask,则 AFN 类不是 NSOperation 的子类。只有当它使用底层NSURLConnection时,对应的AFN类才是NSOperation的子类。如果需要 NSOperation 子类,则不能将 AFN 与 NSURLSessionTask 一起使用。
  • @CouchDeveloper "如果你需要一个 NSOperation 子类" 它实际上是相反的。我试图取消创建 NSOperation 子类。但我希望将所有请求封装到单独的类中。我希望我的视图控制器使用参数实例化 GetSomeResourceFromNetwork 并将其传递给完成黑色。
  • @NSExplorer,请参阅我的更新答案。你不能继承NSURLSessionTask,但你可以把它包装在一个瘦对象中。

标签: ios objective-c nsurlsession


【解决方案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
         }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-11
        • 2014-05-11
        • 1970-01-01
        • 1970-01-01
        • 2020-05-24
        • 2017-01-09
        相关资源
        最近更新 更多