带上传流的NSURLSession – 子类化NSInputStream – com.apple.NSURLConnectionLoaderexception

基本任务

我有一些多平台库,它使用一些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&) 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方法的CppInputStreamWrapper (除了我在构造对象时的调用)。

没有错误没有报告,没有报告!

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

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

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

我完全不解,也不知道我还能做些什么。

更新

我在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的正确对象,所以我不知道它为什么会崩溃。

未记录的私有桥接API不是自定义NSInputStream实现中的唯一问题,尤其是在CFNetworking集成的上下文中。 我想建议使用我的POSInputStreamLibrary作为基本构建块。 您应该实现更简单的POSBlobInputStreamDataSource接口,而不是实现大量NSInputStream方法并支持异步通知。 至少你可以查看POSBlobInputStream来查看你应该实现什么样的function来完全支持NSInputStream合约。

POSInputStreamLibrary用于最流行的俄罗斯云存储服务Cloud Mail.Ru ,每天上传> 1M文件,不会发生任何崩溃。

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

我看到你确实有未记录的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上声明的每个方法。 尝试在你的代码中调用自己打开(NSURLConnection最终会这样做) – 你会得到这个想法,因为它应该在那里崩溃。 您可能至少需要一些最小的状态处理,以便在调用-open之后-streamStatus不会返回NSStreamStatusNotOpen。 基本上,每个具体的子类都需要实现整个API。 它不像普通的类集群,只需要覆盖几个核心方法 – 甚至必须实现-delegate和-setDelegate:方法(超类没有实例变量存储,我很确定) 。

AFNetworking有一个内部AFMultipartBodyStream,它具有所需的最小实现 – 你可以在AFURLRequestSerialization.m中看到这个例子。 另一个代码示例是HSCountingInputStream 。