NSURLSession с потоком загрузки - наследование NSInputStream - исключение com.apple.NSURLConnectionLoader
Основная задача
У меня есть мультиплатформенная библиотека, которая использует некоторый интерфейс потока 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
который я не создал.
Я полностью озадачен и понятия не имею, что еще я могу сделать.
Обновить
Я использовал ссылку кода формы в комментарии, который размещен на github. Теперь, по крайней мере, некоторые части моего класса вызываются фреймворком, но я вижу странный сбой.
Crash находится в этом методе:
- (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, поэтому я понятия не имею, почему он падает.
2 ответа
Недокументированный API частного моста - не единственная проблема в пользовательской реализации NSInputStream, особенно в контексте интеграции CFNetworking. Я хотел бы рекомендовать использовать мой POSInputStreamLibrary в качестве основного строительного блока. Вместо того, чтобы реализовывать множество методов NSInputStream и поддерживать асинхронные уведомления, вы должны реализовать гораздо более простой интерфейс POSBlobInputStreamDataSource. По крайней мере, вы можете посмотреть на POSBlobInputStream, чтобы узнать, какую функциональность вы должны реализовать для полной поддержки контракта NSInputStream.
POSInputStreamLibrary используется в самом популярном российском облачном хранилище Cloud Mail.Ru и загружает более 1 млн файлов в день без сбоев.
Удачи и не стесняйтесь задавать любые вопросы.
Я вижу, у вас есть реализации недокументированных методов моста CFReadStream - это одна из наиболее распространенных проблем. Однако... обратите внимание на комментарий в заголовке NSStream.h для класса NSStream:
// 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) - вы поймете, что идея должна произойти сбой прямо здесь. Вероятно, вам понадобится хотя бы минимальная обработка статуса, чтобы -streamStatus не возвращал NSStreamStatusNotOpen после вызова, например, -open. По сути, каждый конкретный подкласс должен реализовывать весь API. Это не похоже на обычный кластер классов, где нужно переопределить только пару основных методов - даже должны быть реализованы методы -delegate и -setDelegate: (у суперкласса нет хранилища переменных экземпляра для него, я вполне уверен),
AFNetworking имеет внутренний AFMultipartBodyStream, который имеет минимальные необходимые реализации - вы можете увидеть этот пример внутри AFURLRequestSerialization.m. Другой пример кода - https://github.com/bjhomer/HSCountingInputStream.