I'm trying to create a subclass of NSURLConnection
which already has one delegate method pre implemented.
My current approach is to use a "proxy" delegate which has this method pre filled and calls the other methods like this:
-(BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection{
if ([self.delegate respondsToSelector:@selector(connectionShouldUseCredentialStorage:)]) {
return [self.delegate connectionShouldUseCredentialStorage:connection];
}
else{
return NULL;
}
}
Where delegate is the actual user defined delegate. This causes somehow a problem because returning NULL in some cases causes the action to stop.
What is the correct way to do this?
My class should have in the end one preconfigured method called and the other stuff should be implemented by the dev.
edit: Another addition what is the correct approach for a void
delegate method?
Edit2: another requirement is that the subclass should work like its parent but it must have one delegate method pre implemented. So the dev can additionally implement another delegates of NSURLConnection. Can't see how do this with a custom protocol
In C the definition of NULL is 0, and in Objective-C NO is aliased to FALSE that is aliased to 0, so basically returning NULL is the same thing as returning NO.
The problem is that, as per the documentation:
This method is called before any attempt to authenticate is made.
If you return NO, the connection does not consult the credential storage automatically, and does not store credentials. However, in your connection:didReceiveAuthenticationChallenge: method, you can consult the credential storage yourself and store credentials yourself, as needed.
Not implementing this method is the same as returning YES.
Instead of returning NULL, return YES as per the default implementation
EDIT: NO
is aliased to (BOOL)0
, not to false
that is a true boolean type
Specifically the definition of YES
/NO
are in objc.h
typedef signed char BOOL;
// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
// even if -funsigned-char is used.
#define OBJC_BOOL_DEFINED
#define YES (BOOL)1
#define NO (BOOL)0
EDIT: As pointed out in the comments below by @AminNegm-Awad mine is just a (probably over)simplification of the NULL value, since 0 is how it is finally evaluated but it's not its real value.
/*
* Type definitions; takes common type definitions that must be used
* in multiple header files due to [XSI], removes them from the system
* space, and puts them in the implementation space.
*/
#ifdef __cplusplus
#ifdef __GNUG__
#define __DARWIN_NULL __null
#else /* ! __GNUG__ */
#ifdef __LP64__
#define __DARWIN_NULL (0L)
#else /* !__LP64__ */
#define __DARWIN_NULL 0
#endif /* __LP64__ */
#endif /* __GNUG__ */
#else /* ! __cplusplus */
#define __DARWIN_NULL ((void *)0)
#endif /* __cplusplus */
In fact by looking in <sys/_types.h>
you can find out that __DARWIN_NULL
, for objective-c code, is evaluated to ((void *)0)
(verifiable by writing __DARWIN_NULL
in xcode and cmd+clicking it) thus from @AminNegm-Awad comment:
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.55) If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function." As an integral it is 0 (null pointer constant). If it is a pointer, it is the casting 0 to a pointer.
In a C++ application, instead, __DARWIN_NULL
evaluates to __null
, a compiler internal.
BACK TO THE QUESTION:
The proxy delegate method to me seems a clean approach specially if you want to hide some of the NSURLConnectionDelegate
methods. The approach is more or less the same for -(void)
methods, the difference is that you don't need to return anything but just to call the delegated method. Now I'm not able to provide you with a full example, but this evening I'll post something
What you can do is write an NSProxy
subclass that implements respondsToSelector:
. Something like this:
#import <Foundation/Foundation.h>
@interface URLConnectionProxyDelegate : NSProxy <NSURLConnectionDelegate>
- (instancetype)initWithDelegate:(id<NSURLConnectionDelegate>)delegate;
@end
#import "URLConnectionProxyDelegate.h"
@implementation URLConnectionProxyDelegate
{
__weak id<NSURLConnectionDelegate> _realDelegate;
}
#pragma mark - Object Lifecycle
- (instancetype)initWithDelegate:(id<NSURLConnectionDelegate>)delegate
{
if (self) {
_realDelegate = delegate;
}
return self;
}
#pragma mark - NSProxy Overrides
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
return [(id)_realDelegate methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
invocation.target = _realDelegate;
[invocation invoke];
}
#pragma mark - NSObject Protocol Methods
- (BOOL)respondsToSelector:(SEL)sel
{
// replace @selector(connection:didFailWithError:) with your actual pre-implemented method's selector
if (sel == @selector(connection:didFailWithError:)) {
return YES;
}
return [_realDelegate respondsToSelector:sel];
}
#pragma mark - NSURLConnectionDelegate Methods
// Since I don't know which method your pre-implemented method is,
// I just chose connection:didFailWithError: as an example. Replace this
// with your actual pre-implemented method.
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"Connection failed: This gets called only when the proxy delegate is used");
}
@end
And then to use this class in, say, a view controller class of yours, you can do something like this:
#import "SomeViewController.h"
#import "URLConnectionProxyDelegate.h"
@interface SomeViewController () <NSURLConnectionDelegate>
@end
@implementation SomeViewController
#pragma mark - Button actions
- (IBAction)testSuccessURLWithNormalDelegate:(id)sender
{
NSURL *url = [NSURL URLWithString:@"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Using self as the delegate
[NSURLConnection connectionWithRequest:request delegate:self];
}
- (IBAction)testFailURLWithNormalDelegate:(id)sender
{
NSURL *url = [NSURL URLWithString:@"not a real url"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Using self as the delegate
[NSURLConnection connectionWithRequest:request delegate:self];
}
- (IBAction)testSuccessURLWithProxyDelegate:(id)sender
{
NSURL *url = [NSURL URLWithString:@"http://example.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Using a proxy delegate, with self as the real delegate
URLConnectionProxyDelegate *proxy = [[URLConnectionProxyDelegate alloc] initWithDelegate:self];
[NSURLConnection connectionWithRequest:request delegate:proxy];
}
- (IBAction)testFailURLWithProxyDelegate:(id)sender
{
NSURL *url = [NSURL URLWithString:@"not a real url"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// Using a proxy delegate, with self as the real delegate
URLConnectionProxyDelegate *proxy = [[URLConnectionProxyDelegate alloc] initWithDelegate:self];
[NSURLConnection connectionWithRequest:request delegate:proxy];
}
#pragma mark - NSURLConnectionDelegate Methods
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"Connection failed: This gets called only when the view controller is used as the delegate");
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
NSLog(@"Connection success: This gets called when the view controller OR the proxy delegate is used as the delegate");
return YES;
}
@end
The important thing to note about all this is that URLConnectionProxyDelegate
overrides respondsToSelector:
and passes it along to its _realDelegate
object instead of calling super
, and it also always returns YES
if the selector is your "pre-implemented" method's selector. This means you don't even have to implement any of the other methods in the NSURLConnectionDelegate
protocol – you just need to implement the "pre-implemented" one.
You could even have several pre-implemented methods, as well. This is easily done by just adding more checks for the selector in respondsToSelector:
of the proxy class:
[...]
if (sel == @selector(connection:didFailWithError:)) {
return YES;
}
if (sel == @selector(connectionShouldUseCredentialStorage:)) {
return YES;
}
[...]
... and then just making sure to implement all of those methods in the proxy class as well, of course:
[...]
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(@"pre-implemented connection:didFailWithError:");
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
NSLog(@"pre-implemented connectionShouldUseCredentialStorage:");
return YES;
}
[...]
Hope that makes sense and is of some help to you.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With