ASi

alternative implementation of NSURL that can load from NSData(memory)

// 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