// Apple iOS Mac OSX Objective-C
// Copyright (c) 2017 ASi All Rights Reserved. #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface ASiDataURL : NSURL /** see initWithData method */ + (instancetype) URLWithData:(NSData*)data path:(NSString*)path; /** * @param path * e.g.) "/resource.ext" "/subpath/resource.ext". * It should be consist of valid characters as URL. * path will be used to identify NSData passed so it should be unique if the * data has to be distinguished each other. */ - (instancetype) initWithData:(NSData*)data path:(NSString*)path NS_DESIGNATED_INITIALIZER; @end NS_ASSUME_NONNULL_END
// Copyright (c) 2017 ASi All Rights Reserved. #import "ASiDataURL.h" #define Scheme @"ASiData" static NSMapTable<NSString*,ASiDataURL*>* asiDataURLInstances_; static dispatch_semaphore_t sem_; @interface ASiDataURLProtocol : NSURLProtocol @end @implementation ASiDataURL { @package NSData* data_; } + (void)initialize{ if (self != [ASiDataURL self]){ return; } asiDataURLInstances_ = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableWeakMemory capacity:1]; sem_ = dispatch_semaphore_create(1); [NSURLProtocol registerClass:[ASiDataURLProtocol class]]; } + (instancetype) URLWithData:(NSData*)data path:(NSString*)path{ return [[[ASiDataURL alloc] initWithData:data path:path] autorelease]; } - (instancetype) initWithData:(NSData*)data path:(NSString*)path{ if (path.length == 0){ [NSException raise:NSInvalidArgumentException format:@"path shoudn't be 0 length or nil"]; } if (data.length > NSIntegerMax){ // Because the type of expectedContentLength is NSInteger in the method // NSURLResponse-initWithURL:MIMEType:expectedContentLength:textEncodingName: return nil; } NSMutableString* urlStr = [[NSMutableString alloc] initWithCapacity: Scheme.length + 1 + path.length]; // +1 for ':' [urlStr appendString:Scheme@":"]; [urlStr appendString:path]; self = [super initWithString:urlStr relativeToURL:nil]; [urlStr release]; if (!self){ return nil; } data_ = [data retain]; dispatch_semaphore_wait(sem_, DISPATCH_TIME_FOREVER); [asiDataURLInstances_ setObject:self forKey:self.path]; dispatch_semaphore_signal(sem_); return self; } - (oneway void)release{ dispatch_semaphore_wait(sem_, DISPATCH_TIME_FOREVER); [super release]; dispatch_semaphore_signal(sem_); } - (void)dealloc{ [asiDataURLInstances_ removeObjectForKey:self.path]; [data_ release]; [super dealloc]; } #define NOIMP \ [NSException raise:NSGenericException format:@"%@:%@-%@", @"not supported", \ NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; \ return [self initWithData:[NSData data] path:@""]; - (instancetype)initFileURLWithPath:(NSString *)path isDirectory:(BOOL)isDir relativeToURL:(nullable NSURL *)baseURL{NOIMP} - (instancetype)initFileURLWithPath:(NSString *)path relativeToURL:(nullable NSURL *)baseURL{NOIMP} - (instancetype)initFileURLWithPath:(NSString *)path isDirectory:(BOOL)isDir{NOIMP} - (instancetype)initFileURLWithPath:(NSString *)path{NOIMP} - (instancetype)initFileURLWithFileSystemRepresentation:(const char *)path isDirectory:(BOOL)isDir relativeToURL:(nullable NSURL *)baseURL{NOIMP} - (nullable instancetype)initWithString:(NSString *)URLString relativeToURL:(nullable NSURL *)baseURL{NOIMP} - (instancetype)initWithDataRepresentation:(NSData *)data relativeToURL:(nullable NSURL *)baseURL{NOIMP} - (instancetype)initAbsoluteURLWithDataRepresentation:(NSData *)data relativeToURL:(nullable NSURL *)baseURL{NOIMP} #undef NOIMP @end #pragma mark - @interface ASiDataURLProtocol () @end @implementation ASiDataURLProtocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { NSString *scheme = [[request URL] scheme]; return [Scheme isEqual:scheme]; } + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { return request; } - (void)startLoading { id<NSURLProtocolClient> client = [self client]; NSURLRequest* request = [self request]; // The URL obtained from request is not a instance of ASiDataURL. // Therfore we lookup data in MapTable using a path. NSString* path = [request URL].path; NSURLResponse* response; NSData* data; dispatch_semaphore_wait(sem_, DISPATCH_TIME_FOREVER); ASiDataURL* dataURL = [[asiDataURLInstances_ objectForKey:path] retain]; if (!dataURL){ dispatch_semaphore_signal(sem_); [client URLProtocol:self didFailWithError: [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorResourceUnavailable userInfo:nil]]; return; } data = [dataURL->data_ retain]; dispatch_semaphore_signal(sem_); response = [[NSURLResponse alloc] initWithURL:dataURL MIMEType:@"application/octet-stream" expectedContentLength:(NSInteger)data.length textEncodingName:nil]; [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [client URLProtocol:self didLoadData:data]; [client URLProtocolDidFinishLoading:self]; [response release]; [data release]; [dataURL release]; } - (void)stopLoading{ // nop } @end