objective-ccocoa-touchdelegatesobjective-c-category

Obj-C, How do I use a category to supply methods which I will use in delegate methods?


I want to provide methods used in several view controllers called in my delegate methods.

For example, I have some CloudKit functionality (I've added this to my own framework, but I don't think thats important), where I want to provide some crash logging. Previosuly I had a crashLog function in each of my view controllers, which worked fine, but I have a lot of duplicate code.

Therefore I'd like to produce a category with these methods instead.

However I'm having difficulty getting my delegate methods to see these category methods.

Here's my code..

UIViewController+CloudKitDelegates.h

@interface UIViewController (CloudKitDelegates) <iCloudDBDelegate>

@property (weak,nonatomic) id<iCloudDBDelegate>iCloudDBDelegate;

-(void)crashLog:(NSString*)message, ...;

@end

UIViewController+CloudKitDelegates.m

#import "UIViewController+CloudKitDelegates.h"

@implementation UIViewController (CloudKitDelegates)
@dynamic iCloudDBDelegate;

-(void)crashLog:(NSString*)message, ...
{
    va_list args;
    va_start(args, message);

    NSLog(@"%@", [[NSString alloc] initWithFormat:message arguments:args]);

    va_end(args);
}

@end

h file - my calling view controller (e.g. My View Controller)

#import "UIViewController+CloudKitDelegates.h"

m file - delegate method

-(NSString*)getDBPath
{
    [self.iCloudDBDelegate crashLog: @"testing"];

From this call I'm getting an error ...

'NSInvalidArgumentException', reason: '-[MyViewController crashLog:]: 
    unrecognized selector sent to instance

The error is showing that my calling view controller called MyViewController doesn't have the crashLog method, which I have in my category.

Any ideas where I'm going wrong ?


Solution

  • The problem: identical method crashLog: in multiple classes, for example

    @interface ViewController : UIViewController
    @end
    
    @implementation ViewController
    
    - (void)someMethod {
        [self crashLog:@"error"];
    }
    
    -(void)crashLog:(NSString *)message {
        NSLog(@"%@", message);
    }
    
    @end
    

    Solution A: move crashLog: to a common superclass (or a category on superclass UIViewController)

    @interface CommonViewController : UIViewController
    
    -(void)crashLog:(NSString *)message;
    
    @end
    
    @implementation CommonViewController
    
    -(void)crashLog:(NSString *)message {
        NSLog(@"%@", message);
    }
    
    @end
    
    
    @interface ViewController : CommonViewController
    @end
    
    @implementation ViewController
    
    - (void)someMethod {
        [self crashLog:@"error"];
    }
    
    @end
    

    Solution B: move crashLog: to a delegate and protocol

    @protocol ICloudDBDelegate
    
    -(void)crashLog:(NSString *)message;
    
    @end
    
    
    @interface DelegateClass : AnyClass <ICloudDBDelegate>
    @end
    
    @implementation DelegateClass
    
    -(void)crashLog:(NSString *)message {
        NSLog(@"%@", message);
    }
    
    @end
    
    
    @interface ViewController : UIViewController
    @end
    
    @implementation ViewController
    
    @property (weak, nonatomic) id <ICloudDBDelegate> iCloudDBDelegate;
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
        self.iCloudDBDelegate = appDel.iCloudDBDelegate;
    }
    
    - (void)someMethod {
        [self.iCloudDBDelegate crashLog:@"error"];
    }
    
    @end
    
    
    @interface AppDelegate : UIResponder <UIApplicationDelegate, AppDelProtocolDelegate, iCloudDBDelegate>
    
    @property (strong, nonatomic) id<iCloudDBDelegate>iCloudDBDelegate;
    
    @end
    
    @implementation AppDelegate
    
    - (id<iCloudDBDelegate>)iCloudDBDelegate {
        if (!_iCloudDBDelegate) {
            _iCloudDBDelegate = [[DelegateClass alloc] init];
        }
        return _iCloudDBDelegate;
    }
    
    @end
    

    Now we have new problem: property iCloudDBDelegate in multiple classes

    Solution B + A: move crashLog to a delegate, move iCloudDBDelegate property to a superclass

    @protocol ICloudDBDelegate
    
    -(void)crashLog:(NSString *)message;
    
    @end
    
    
    @interface DelegateClass : AnyClass <ICloudDBDelegate>
    @end
    
    @implementation DelegateClass
    
    -(void)crashLog:(NSString *)message {
        NSLog(@"%@", message);
    }
    
    @end
    
    
    @interface CommonViewController : UIViewController
    
    @property (weak, nonatomic) id <ICloudDBDelegate> iCloudDBDelegate;
    
    @end
    
    @implementation CommonViewController
    @end
    
    
    @interface ViewController : CommonViewController
    @end
    
    @implementation ViewController
    
    - (void)someMethod {
        [self.iCloudDBDelegate crashLog:@"error"];
    }
    
    @end
    

    Solution C: Another approach is a singleton object like NSUserDefaults.standardUserDefaults or NSFontManager.sharedFontManager: CloudDBManager.sharedCloudDBManager. No category or protocol required, just include CloudDBManager.h and use CloudDBManager.sharedCloudDBManager from everywhere.

    @interface CloudDBManager : NSObject
    
    @property(class, readonly, strong) CloudDBManager *sharedCloudDBManager;
    
    -(void)crashLog:(NSString *)message;
    
    @end
    
    @implementation CloudDBManager
    
    + (CloudDBManager *)sharedCloudDBManager {
        static CloudDBManager *sharedInstance = nil;
        static dispatch_once_t onceToken = 0;
        dispatch_once(&onceToken, ^{
            sharedInstance = [[CloudDBManager alloc] init];
            // Do any other initialisation stuff here
        });
        return sharedInstance;
    }
    
    -(void)crashLog:(NSString *)message {
        NSLog(@"%@", message);
    }
    
    @end
    
    
    @interface ViewController : CommonViewController
    @end
    
    @implementation ViewController
    
    - (void)someMethod {
        [CloudDBManager.sharedCloudDBManager crashLog:@"error"];
    }
    
    @end