Borys Verebskyi
Borys Verebskyi

Reputation: 4278

What exactly +[NSURLProtocol setProperty:forKey:inRequest:] does

NSURLProtocol defines following methods:

/*! 
    @method propertyForKey:inRequest:
    @abstract Returns the property in the given request previously
    stored with the given key.
    @discussion The purpose of this method is to provide an interface
    for protocol implementors to access protocol-specific information
    associated with NSURLRequest objects.
    @param key The string to use for the property lookup.
    @param request The request to use for the property lookup.
    @result The property stored with the given key, or nil if no property
    had previously been stored with the given key in the given request.
*/
+ (nullable id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;

/*! 
    @method setProperty:forKey:inRequest:
    @abstract Stores the given property in the given request using the
    given key.
    @discussion The purpose of this method is to provide an interface
    for protocol implementors to customize protocol-specific
    information associated with NSMutableURLRequest objects.
    @param value The property to store. 
    @param key The string to use for the property storage. 
    @param request The request in which to store the property. 
*/
+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;

/*!
    @method removePropertyForKey:inRequest:
    @abstract Remove any property stored under the given key
    @discussion Like setProperty:forKey:inRequest: above, the purpose of this
        method is to give protocol implementors the ability to store 
        protocol-specific information in an NSURLRequest
    @param key The key whose value should be removed
    @param request The request to be modified
*/
+ (void)removePropertyForKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;

But, if I trying to associate NSURLAuthenticationChallenge with request, using following code

[NSURLProtocol setProperty:challenge forKey:@"challenge" inRequest:self.request];

I see next error in log:

2016-03-20 18:00:41.252 Web@Work[6084:586155] ERROR: createEncodedCachedResponseAndRequestForXPCTransmission - Invalid protocol-property list - CFURLRequestRef. protoProps=<CFBasicHash 0x7f98ec6dcca0 [0x10684e180]>{type = mutable dict, count = 3,
entries =>
    0 : <CFString 0x101f47938 [0x10684e180]>{contents = "challenge"} = <NSURLAuthenticationChallenge: 0x7f98ec4cd700>
    1 : <CFString 0x7f98e9fd03a0 [0x10684e180]>{contents = "Accept-Encoding"} = <CFBasicHash 0x7f98ec7c4490 [0x10684e180]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0x7f98ec78b930 [0x10684e180]>{contents = "Accept-Encoding"} = <CFString 0x106330828 [0x10684e180]>{contents = "gzip, deflate"}
}

    2 : <CFString 0x1063310e8 [0x10684e180]>{contents = "kCFURLRequestAllowAllPOSTCaching"} = <CFBoolean 0x10684ebf0 [0x10684e180]>{value = true}
}

From this message, I suspect that only property list supported objects may be associated with NSURLRequest using this API. But I do not completely understand why.

Also, my question is for which purpose this API should be used. How to use it correctly? I want to know what exactly setProperty:forKey:inRequest: does, because it seems to do more work, than simply associate objects with request.

UPD So, minimal verifiable example. Very simple NSURLProtocol subclass:

#import "MyURLProtocol.h"

#define kOurRecursiveRequestFlagProperty @"recursive"

@interface MyURLProtocol ()
@property NSURLConnection *connection;
@end

@implementation MyURLProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    return ![[[self class] propertyForKey:kOurRecursiveRequestFlagProperty inRequest:request] boolValue];
}

- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client {
    return [super initWithRequest:request cachedResponse:cachedResponse client:client];
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

- (void)startLoading {
    NSMutableURLRequest* mutableRequest = [[self request] mutableCopy];
    [[self class] setProperty:@YES forKey:kOurRecursiveRequestFlagProperty inRequest:mutableRequest];
    // Associate any non-property list object with mutableRequest to reproduce issue.
    [[self class] setProperty:[UIApplication sharedApplication] forKey:@"dummyKey" inRequest:mutableRequest];
    self.connection = [[NSURLConnection alloc] initWithRequest:mutableRequest delegate:self startImmediately:YES];
}

- (void)stopLoading {
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
    [[self client] URLProtocolDidFinishLoading:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [[self client] URLProtocol:self didLoadData:data];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}

- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse {
    return request;
}

@end

UPD2 For now, I see that caching is to blame. Even when I use NSURLCacheStorageNotAllowed, this code still tries to create cache dictionary, and write associated [UIApplication sharedApplication] into it.

2016-03-20 20:52:19.630 WebViewDemo[7102:663895] ADD: failed to create cache dictionary at path=/Users/xxx/Library/Developer/CoreSimulator/Devices/89D7C50F-939B-4360-A19F-4547AE4F7515/data/Containers/Data/Application/6F05411C-0261-4A33-9531-9E4E900C4910/Library/Caches/Test.WebViewDemo. key=0x7fe76af45f90

So, now next my question is - how can I disable this caching without implementing my own associated dictionary. Should I do that, or I simply should avoid setProperty:forKey:inRequest:.

And, most importantly, is that correct to use setProperty:forKey:inRequest: to store kOurRecursiveRequestFlagProperty, as Apple sample suggests? I'm still trying to figure out when I can use this methods, and when I can't.

Upvotes: 1

Views: 1441

Answers (1)

dgatwood
dgatwood

Reputation: 10417

IIRC, the reason why the data has to be plist-serializable is that in background tasks, URL requests are serialized and sent over XPC to a background daemon that actually performs the URL fetch. If the data weren't serializable, it would get lost in transit.

The objects you store here need to be small, self-contained pieces of data for the purposes of allowing your protocol to identify that request.

Typically, if you need to associate something more complex with a request, you would use this method to store a UUID or some other arbitrary string. Then you would store that same string as a key in a normal NSDictionary within your app, disposing of the dictionary key and associated data after you tell the client that the request has completed/failed.

Upvotes: 2

Related Questions