Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subclass Object with pre implemented delegate method

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

like image 518
arnoapp Avatar asked May 26 '14 13:05

arnoapp


2 Answers

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

like image 65
Antonio E. Avatar answered Nov 15 '22 00:11

Antonio E.


What you can do is write an NSProxy subclass that implements respondsToSelector:. Something like this:

URLConnectionProxyDelegate.h:

#import <Foundation/Foundation.h>

@interface URLConnectionProxyDelegate : NSProxy <NSURLConnectionDelegate>

- (instancetype)initWithDelegate:(id<NSURLConnectionDelegate>)delegate;

@end

URLConnectionProxyDelegate.m:

#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:

SomeViewController.m:

#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.

like image 24
TylerP Avatar answered Nov 15 '22 02:11

TylerP