1

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.

Borys Verebskyi
  • 4,160
  • 6
  • 28
  • 42
  • Do you have an actual problem to be solved? SO is not the place for speculation on the thought processes of Apple engineers. – Avi Mar 20 '16 at 17:03
  • 1
    @Avi, I'm not trying to solve specific problem in code. Rather I trying to understand **what** code that I wrote does. Although "Apple engineers' thoughts" is interesting topic too, my question is not about them. I ask exactly what happens under the hood of specific methods and **why** I receive unexpected errors when I try to use those methods. After double-checking FAQ and meta discussions regarding good questions, I still see no reason why you consider my question as inappropriate. Maybe, you can point me to corresponding topic? – Borys Verebskyi Mar 20 '16 at 18:09
  • Ultimately, unless an Apple engineer with access to the source for Foundation chimes in, this question can only generate speculation. As such, it's not appropriate for SO. – Avi Mar 20 '16 at 18:16
  • As an aside, the error isn't coming from a call to `[NSURLProtocol setProperty:forKey:inRequest]`. You are doing something with the request after that point which is giving you the error. – Avi Mar 20 '16 at 18:23
  • @Avi, I'm not sure that this point is not described in documentation/headers somewhere. Also, I suspect that people, who used `NSURLProtocol` extensively may share their opinions and experience. Regarding last comment - I will try to isolate issue and provide more specific code example. – Borys Verebskyi Mar 20 '16 at 18:26
  • BTW, I tried it myself, which is how I know it's not the call to `setProperty`. – Avi Mar 20 '16 at 18:35
  • OK, now I understand. The trouble is with the caching mechanism. The request is serialized as part of caching, and it probably uses `NSCoding`. That protocol can only handle property-list objects. – Avi Mar 20 '16 at 19:10
  • @Avi, yes, problem with caching. Actually, you are wrong regarding `NSCoding`, - looks like serialization uses `NSPropertyListSerialization` and therefore only supports property list objects. I've receive same error when I use custom object, that implements `NSCoding`. – Borys Verebskyi Mar 20 '16 at 19:16
  • This topic has been bugging me for quite some time now and it's hard to find any documentation. It's good that SO is a place to talk about those inner workings of proprietary APIs! I want to add the following speculation: The NSURLLoading System of iOS is implemented using XPCService API which in not publicly available in iOS APIs (error name, debugger stack traces suggests that and some blogs say so). So object's travel across processes and this is where serialization becomes an issue. – Jakob Apr 06 '16 at 13:33

1 Answers1

2

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.

dgatwood
  • 10,129
  • 1
  • 28
  • 49