【问题标题】:NSURLSession with upload stream - subclassing NSInputStream - com.apple.NSURLConnectionLoader exception带有上传流的 NSURLSession - 子类化 NSInputStream - com.apple.NSURLConnectionLoader 异常
【发布时间】:2015-08-05 16:22:30
【问题描述】:

基本任务

我有一些使用 C++ 流接口的多平台库。我必须使用这个流接口通过NSURLSession上传数据。我的实现应该可以在 OS X 和 iOS 上运行(目前我正在 OS X 上测试)

我做了什么

任务看起来很简单,我确信我会很快实现它。 我已经配置了NSURLSession,如果我使用NSURLRequest 和简单的NSData,它工作正常。 我正在尝试像这样使用流:

        NSURLSessionDataTask *dataTask = [m_Private.session uploadTaskWithStreamedRequest: request];
        HTTPDownoadTaskProxy *dataTaskProxy = [HTTPDownoadTaskProxy new];
        // store data to properly handle delegate
        dataTaskProxy.coreTask = dataTask;
        dataTaskProxy.cppRequest= req;
        dataTaskProxy.cppResponseHandler = handler;
        dataTaskProxy.cppErrorHandler = errorHandler;

        m_Private.streamedDataTasks[dataTask] = dataTaskProxy;

        [dataTask resume];

到目前为止一切顺利。根据uploadTaskWithStreamedRequest 的文档,我应该收到代表的通知,我确实收到了:

- (void)URLSession: (NSURLSession *)session
              task: (NSURLSessionTask *)task
 needNewBodyStream: (void (^)(NSInputStream *bodyStream))completionHandler
{
    HTTPDownoadTaskProxy *proxyTask = self.streamedDataTasks[task];
    CppInputStreamWrapper *objcInputStream = [[CppInputStreamWrapper alloc] initWithCppInputStream:proxyTask.cppRequest.GetDataStream()];
    completionHandler(objcInputStream);
}

现在我应该在NSInputStream 的子类中接听电话,在我的情况下是CppInputStreamWrapper,而且非常简单:

@implementation CppInputStreamWrapper

- (void)dealloc {
    NSLog(@"%s", __PRETTY_FUNCTION__);
}

- (instancetype)initWithCppInputStream: (const std::tr1::shared_ptr<IInputStream>&) cppInputStream
{
    if (self = [super init]) {
        _cppInputStream = cppInputStream;
    }
    return self;
}

#pragma mark - overrides for NSInputStream
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
    return (NSInteger)self.cppInputStream->Read(buffer, len);
}

- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len {
    return NO;
}

- (BOOL)hasBytesAvailable {
    return !self.cppInputStream->IsEOF();
}

#pragma mark - this methods are need to be overridden to make stream working
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
                  forMode:(__unused NSString *)mode
{}

- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
                  forMode:(__unused NSString *)mode
{}

#pragma mark - Undocumented CFReadStream Bridged Methods
- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop
                     forMode:(__unused CFStringRef)aMode
{}

- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop
                         forMode:(__unused CFStringRef)aMode
{}

- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags
                 callback:(__unused CFReadStreamClientCallBack)inCallback
                  context:(__unused CFStreamClientContext *)inContext {
    return NO;
}

@end

所以我在子类化NSInputStream 时使用了所需的解决方法。

问题

现在这应该可以了。但是我没有收到任何CppInputStreamWrapper 方法的调用(除了我在构造对象时的调用)。

没有错误没有警告报告,什么都没有!

当我添加异常断点时,我正在捕捉

thread #8: tid = 0x155cb3, 0x00007fff8b770743 libobjc.A.dylib`objc_exception_throw, name = 'com.apple.NSURLConnectionLoader', stop reason = breakpoint 1.1

这来自我没有创建的线程com.apple.NSURLConnectionLoader

我完全困惑,不知道我还能做什么。

更新

我使用了link in comment 的代码形式,即hosted on github。 现在框架至少调用了我的类的某些部分,但我看到了奇怪的崩溃。

崩溃位于此方法中:

- (BOOL)_setCFClientFlags:(CFOptionFlags)inFlags
                 callback:(CFReadStreamClientCallBack)inCallback
                  context:(CFStreamClientContext *)inContext {

    if (inCallback != NULL) {
        requestedEvents = inFlags;
        copiedCallback = inCallback;
        memcpy(&copiedContext, inContext, sizeof(CFStreamClientContext));

        if (copiedContext.info && copiedContext.retain) {
            copiedContext.retain(copiedContext.info);
        }

        copiedCallback((__bridge CFReadStreamRef)self, kCFStreamEventHasBytesAvailable, &copiedContext); // CRASH HERE
    } else {
        requestedEvents = kCFStreamEventNone;
        copiedCallback = NULL;
        if (copiedContext.info && copiedContext.release) {
            copiedContext.release(copiedContext.info);
        }

        memset(&copiedContext, 0, sizeof(CFStreamClientContext));
    }

    return YES;

}

崩溃是EXC_BAD_ACCESS(在 OS X 上运行测试时)。当我看到这段代码时,一切看起来都很好。它应该工作! self 指向正确的对象,保留计数为 3,所以我不知道它为什么会崩溃。

【问题讨论】:

  • 子类化 NSStreams 很有趣。声明的超类基本上不提供实际功能;你需要实现这一切。 Apple 的私有子类具有您无法重复使用的功能。更有趣的是,有些地方(NSURLConnection)假设你使用的是 Apple 的类,并调用私有方法。有关正在发生的事情的提示,请参阅 blog.bjhomer.com/2011/04/subclassing-nsinputstream.html

标签: ios objective-c macos nsurlsession nsinputstream


【解决方案1】:

未记录的私有桥接 API 并不是自定义 NSInputStream 实现中的唯一问题,尤其是在 CFNetworking 集成的上下文中。我想推荐使用我的POSInputStreamLibrary 作为基本构建块。与其实现很多 NSInputStream 方法并支持异步通知,不如实现更简单的POSBlobInputStreamDataSource 接口。至少你可以看看POSBlobInputStream 来咨询你应该实现什么样的功能来完全支持 NSInputStream 契约。

POSInputStreamLibrary 用于最流行的俄罗斯云存储服务Cloud Mail.Ru,每天上传超过 100 万个文件而没有任何崩溃。

祝你好运,如有任何问题,请随时提出。

【讨论】:

  • 一开始你的代码看起来和我读过的博客一样(以及评论中提供的链接)。仔细检查显示差异。现在上传作品谢谢。现在我必须弄清楚如何处理响应。
  • 当您实现自己的自定义流时,请注意 CFNetwork 框架使用已弃用的 CFReadStreamGetError 方法从流中获取错误描述。由于 NSInputStream 的“免费桥接”实现中的错误,此操作会使应用程序崩溃。这就是为什么 POSBlobInputStream 的 streamStatus 方法从不返回 NSStreamStatusError 的原因。
【解决方案2】:

我看到您确实实现了未记录的 CFReadStream 桥接方法——这是更常见的问题之一。但是...请注意 NSStream 类的 NSStream.h 标头中的注释:

// NSStream is an abstract class encapsulating the common API to NSInputStream and NSOutputStream.
// Subclassers of NSInputStream and NSOutputStream must also implement these methods.

这意味着您还需要实现 -open、-close、-propertyForKey:、-streamStatus 等——基本上,在 NSStream 和 NSInputStream 上声明的每个方法。尝试在你的代码中调用 -open (NSURLConnection 最终会这样做)——你会明白的,因为它应该在那里崩溃。例如,您可能至少需要一些最小的状态处理,以便在调用 -open 之后 -streamStatus 不会返回 NSStreamStatusNotOpen 。基本上,每个具体的子类都需要实现整个 API。它不像一个只需要重写几个核心方法的普通类集群——甚至必须实现 -delegate 和 -setDelegate: 方法(超类没有实例变量存储,我很确定) .

AFNetworking 有一个内部 AFMultipartBodyStream,它具有所需的最少实现——您可以在 AFURLRequestSerialization.m 中看到该示例。另一个代码示例是HSCountingInputStream

【讨论】:

  • 在comet中阅读链接后,我添加了这些方法。现在至少调用了一些方法。但我有一些奇怪的崩溃。有时间我会更新问题(目前我正忙于其他任务)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-27
  • 2011-04-02
  • 2022-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多