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 ?
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