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

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

(I've added this to my own framework, but I don't think thats important)
Yep, that's the typical problem. You've failed to include -ObjC in the link flags.
See Building Objective-C static libraries with categories. This applies to frameworks as well.
ObjC does not create linker symbols for methods. It can't, they're not resolved until runtime. So the category methods aren't seen by the linker as "missing" and it doesn't bother linking the relevant compile unit. This is an important optimization that keeps you from linking all of a massive C library just because you use one function in it, but Objective-C categories break some of the linker's assumptions. The compiler saw the definition (via the header), but the linker didn't care, so there's no error until runtime.
The -ObjC flag says "this C-looking compile unit is actually Objective-C; link all of it even if you don't think you need to."

Related

Subclass inherit Superclass Delegate

I have a hierarchy of UIViews. They are all handled differently but if nested I can not get my setDelegate of super to fire. I receive a crash exception [ThirdClass setDelegate:] unrecognized selector sent to instance. This actually happens no matter what (subclass) i use SecondClass or ThirdClass, but If I use (FirstClass) everything works as it should but any subclassing of the delegate it does not recognize the call. I have simplified what I am doing below which if I call out my first class separately inside my MainControlInterface everything works as it should. Im sure Im doing something wrong here but can't determine what that is, If anyone could help that would be greatly appreciated, thank you.
#protocol FirstClassDataSource, FirstClassDelegate;
#interface FirstClass : UIView
#property (nonatomic, weak_delegate) __nullable id<FirstClassDataSource> dataSource;
#property (nonatomic, weak_delegate) __nullable id<FistClassDelegate> delegate;
#end
#protocol FirstClassDataSource <NSObject>
- (NSInteger)doSomething:(FirstClass *)class;
#optional
- (NSInteger)doSomethingElse:(FirstClass *)class;
#end
#protocol FirstClassDelegate <NSObject>
#optional
- (void)handleMoreDelegateMethods:(FirstClass *)class;
#end
#implementation FirstClass
- (void)setDataSource:(id< FirstClassDataSource >)dataSource
{
if (_dataSource != dataSource)
{
_dataSource = dataSource;
if (_dataSource)
{
[self reloadData];
}
}
}
- (void)setDelegate:(id< FirstClassDelegate>)delegate
{
if (_delegate != delegate)
{
_delegate = delegate;
if (_delegate && _dataSource)
{
[self setNeedsLayout];
}
}
}
#end
#interface SecondClass : FirstClass
-(id)sencondClassesPrivateMethods;
#end
#interface ThirdClass : secondClass
-(id)thirdClassPrivateMethods;
#end
#interface MainControlInterface : UIView <FirstClassDataSource, FirstClassDelegate>
-(ThirdClass *)thirdClass;
#end
#implementation MainControlInterface
-(void)didMoveToSuperview{
ThirdClass *mythirdSubClass = [self thirdClass];
mythirdSubClass.delegate = self;
mythirdSubClass.dataSource = self;
}
#end
I can't tell what you're doing wrong either. But, your sample code will not compile. (It's full of typos.) I have tried to recreate what you're talking about, simplifying it further. (I've used CodeRunner, a macOS app which facilitates this sort of thing.)
#import <Foundation/Foundation.h>
#protocol FirstClassHandling <NSObject>
- (void)doTheThing;
#end
#interface FirstClass : NSObject
#property (nonatomic, weak) id<FirstClassHandling> delegate;
- (void)doSomething;
#end
#implementation FirstClass
- (void)doSomething
{
NSLog(#"First class.");
if ([[self delegate] respondsToSelector:#selector(doTheThing)]) {
[[self delegate] doTheThing];
}
}
#end
#interface SecondClass : FirstClass
#end
#implementation SecondClass
- (void)doSomething
{
NSLog(#"Second class");
[super doSomething];
}
#end
#interface Handler : NSObject <FirstClassHandling>
#end
#implementation Handler
- (void)doTheThing
{
NSLog(#"Doing my thing!!!");
}
#end
int main(int argc, char *argv[])
{
#autoreleasepool {
Handler* handler = [[Handler alloc] init];
SecondClass* sc = [[SecondClass alloc] init];
sc.delegate = handler;
[sc doSomething];
}
}
The above does not crash. Please fix your example code.

Custom delegate method usage

I am trying to use custom delegate between NSObject (MenuComponent) and UIViewController(fullVC) class. Delegate is declared in UIViewController, because UIViewController need to send data (title and url) to my NSObject class.
My protocol methods is in UIViewController(FullVC)
FullVC.h file
#class FullVC;
#protocol MenuComponentDelegate <NSObject>
-(void)shareToView:(NSString *)titleString inUrl:(NSString *)urlString;
#end
#interface FullVC:UIViewController<UIScrollViewDelegate,UITextViewDelegate>
#property (nonatomic,assign) id delegate;
#end
I am trying to call the delegate method in My FullVC. m File by
FullVC.m
#interface FullVC ()
#end
#implementation FullVC
- (void)viewDidLoad
{
[super viewDidLoad];
}
-(void)scrollViewInit
{
_title= #“Title of a Link”;
_urlString = #“Link of the Url”;
if ([delegate respondsToSelector:#selector(sharetoView:inUrl:)])
{
NSLog(#“Checking urlString in scrollView method: %#", _urlString);
[self.delegate shareToView:_title inUrl:_urlString];
}
}
My shareToView Method is implemented in my NSObject Class
MenuComponent.m
#interface MenuComponent() <MenuComponentDelegate>
#property (nonatomic, weak) NSString *titleToShare;
#property (nonatomic, weak) NSString *urlToShare;
#end
#implementation MenuComponent
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.nav_controller.topViewController isKindOfClass:[FullVC class]])
{
if (indexPath.row==0)
{
[(FullVC *)self.vav_controller.topViewController setDelegate:self];
NSLog(#“The value of title to share: %#“,titleToShare);
}
}
}
-(void)shareToView:(NSString *)titleString inUrl:(NSString *)urlString
{
NSLog(#"title string after passing is: %#", titleString);
NSLog(#"url string after passing is: %#", urlString);
}
#end
I am having problem to delegate the data. My shareToView method seems out of scope. My NSLogs are not Printing. The only NSLog printing is the one that is showing value for titleToShare. But the value is printing as Null.
The value of title to share:(null)
Could some one help me how to call delegate method since it looks like controller is not calling and not sure it is in scope? Thank you

How to change a property class in sub class in Objective-C

I am trying to re-write some code of old project, and ideally I want to achieve below code style, but I got a lot of compile error saying dataModel don't have getLineColor method.
The abstract question is, can I change an inherited object A's class in sub view controller to a sub class A2, while the object A in parent view controller is class A1 that is the super class of A2, and how can I do it correctly? Thank in advance.
Update: I compile it, but I have met another run time error, I tried to overwrite the setter of dataModel in sub view controller. How to correctly write the setDataModel in sub class?
#implementation SubViewController
#pragma mark - setter of dataModel
- (void)setDataModel:(ChartModel *)dataModel { // it stucks at this func name
#end
error trace is
[SubChartViewController setDataModel:](self=0x00000000, _cmd=0x00000000, dataModel=0x00000031) + 12 at BDPAxisChartViewController.m:295, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0xbf774ffc)
EDIT:
#interface ChartModel : NSObject
-(BOOL)chartModelhasData;
#end
#interface LineChartModel : chartModel
-(void)getLineColor;
#property (nonatomic, strong) NSArray* seriesNameArray;
#end
#interface ChartViewController: UIViewController
#property (nonatomic, strong) ChartModel *dataModel;
-(void)layoutChartCanvas;
#end
#implementation ChartViewController
-(void)layoutChartCanvas {
if ([self.dataModel chartModelhasData]) {
self.view.hidden = NO;
}
}
#end
#interface LineChartViewController : ChartViewController
// pay attension here, same name but a sub class of chartModel
#property (nonatomic, strong) LineChartModel *dataModel;
-(void)drawLine;
#end
#implementation LineChartViewController
-(void)drawLine {
UIColor *color = [self.dataModel getLineColor];
[self drawLine];
NSArray *tempArray = [self.dataModel.seriesNameArray copy];
}
#end
Probably you want to say:
Can I change the class C of a property already declared in a base class in a subclass to a subclass of C?
This is the set-up
#interface PropertyClass : NSObject
#end
#interface PropertySubclass : PropertyClass
- (void)method; // Additional method
#end
#interface HolderClass : NSObject
#property PropertyClass *property; // Property is of base class
#end
#implementation HolderClass
#end
#interface HolderSubclass : HolderClass
#property PropertySubclass *property; // Property is of subclass
#end
I have no problem to access the property subclass' method in the holder's subclass:
#implementation HolderSubclass
- (void)useIt
{
[self.property method]; // No error or warning
}
#end
In addition to my comment below, I suspect that something like this happens:
// Create a subclass' object
HolderSubclass *object1 = [HolderSubclass new];
…
// Refer this object from a reference that is typed to HolderClass
// **This applies to all usages of self inside #implementation HolderClass**
HolderClass *object2 = object1; // Of course more complex
id value = [object2 method]; // Error
This error is correct. If this is the error, it is solvable. But first we have to clarify that.
BTW: This has nothing to do with better OOP. First of all this is a problem of class based programming languages, not of object orientated programming languages. Second I cannot see that this set-up will break a rule of class based or object orientated programming, esp. it fulfills Liskov's rule.
http://en.wikipedia.org/wiki/Liskov_substitution_principle
One thing you could do is declare a LineChartModel variable and not property and #synthesize dataModel to that ivar:
#interface LineChartViewController: ChartViewController {
LineChartModel *_lineChartModel;
}
-(void)drawLine;
#end
#implementation LineChartViewController
synthesize dataModel = _lineChartModel;
....
So from the outside it looks like you have a ChartModel, but inside the class you have LineChartModel. You will have to apply changes to _lineCharModel directly inside your class.
HOWEVER this is NOT my definition of better OOP!!! There's clearly a fault with the design if you need to cast your variables in subclass.
Another option I discovered after pasting this code into the editor is just use self.variable (which by the way, you should've already been doing).
#interface ChartModel : NSObject
- (BOOL)chartModelhasData;
#end
#interface LineChartModel : ChartModel
- (UIColor *)getLineColor;
#end
#interface ChartViewController: UIViewController
#property (nonatomic, strong) ChartModel *dataModel;
- (void)layoutChartCanvas;
#end
#implementation ChartViewController
- (void)layoutChartCanvas {
if ([self.dataModel chartModelhasData]) {
self.view.hidden = NO;
}
}
#end
#interface LineChartViewController : ChartViewController
// pay attension here, same name but a sub class of chartModel
#property (nonatomic, strong) LineChartModel *dataModel;
- (void)drawLine;
#end
#implementation LineChartViewController
- (void)drawLine {
UIColor *color = [self.dataModel getLineColor];
[self drawLine];
}
#end

IOS application - define protocol in separate file

I have defined a protocol in a separate file (myProtocol.h). Here is the code for it:
#import <Foundation/Foundation.h>
#protocol myProtocol <NSObject>
-(void) loadDataComplete;
#end
Now I want to call this method so I have done the following code:
firstViewController.h:
#import "myProtocol.h"
#interface firstViewController : UIViewController{
id <myProtocol> delegate;
}
#property (retain) id delegate;
-(void) mymethod;
firstViewController.m
#implementation firstViewController
#synthesize delegate;
- (void)viewDidLoad {
[self mymethod];
}
-(void) mymethod {
//some code here...
[delegate loadDataComplete];
}
I have another file where the protocol is also utilized:
secondViewController.h:
#import "myProtocol.h"
#interface secondViewController : UIViewController<myProtocol>{
}
secondViewController.m:
-(void) loadDataComplete{
NSLog(#"loadDataComplete called");
}
but my secondViewController is not calling the protocol methad. Why is it so? Any suggestion will be appreciated.
First, as #Abizern suggested, try to reformat your code a little bit. Use capital letter for classes. Said this here the solution for your answer.
This is the protocol. I would name it like FirstViewControllerDelegate since the class that implements the object is a delegate for FirstViewController.
#import <Foundation/Foundation.h>
#protocol MyProtocol <NSObject>
- (void)doSomething;
#end
This is SecondViewController.
#import <UIKit/UIKit.h>
#import "MyProtocol.h"
#interface SecondViewController : UIViewController <MyProtocol>
#end
#implementation SecondViewController
// other code here...
- (void)doSomething
{
NSLog(#"Hello FirstViewController");
}
#end
This is FirstViewController.
#import <UIKit/UIKit.h>
#interface FirstViewController : UIViewController
// it coud be better to declare these properties within a class extension but for the sake of simplicity you could leave here
// the important thing is to not declare the delegate prop with a strong/retain property but with a weak/assign one, otherwise you can create cycle
#property (nonatomic, strong) SecondViewController* childController;
#property (nonatomic, weak) id<MyProtocol> delegate;
#end
#implementation FirstViewController
// other code here...
- (void)viewDidLoad
{
[super viewDidLoad];
self.childController = [[SecondViewController alloc] init];
self.delegate = self.childController; // here the central point
// be sure your delegate (SecondViewController) responds to doSomething method
if(![self.delegate respondsToSelector:#selector(doSomething)]) {
NSLog(#"delegate cannot respond");
} else {
NSLog(#"delegate can respond");
[self.delegate doSomething];
}
}
#end
For the sake of completeness, be sure to understand the delegate pattern means. Apple doc is your friend. You could take a look at the-basics-of-protocols-and-delegates to have a basic intro on the argument. Furthermore, SO search allows you to find a lot of answers on the topic.
Hope that helps.

EXC_BAD_ACCESS when synthesizing a 'global' object

this is a follow-up question to my last one here: iOS: Initialise object at start of application for all controllers to use .
I have set my application up as follows (ignore the DB Prefix):
DBFactoryClass // Built a DataManaging Object for later use in the app
DBDataModel // Is created by the factory, holds all data & access methods
DBViewControllerA // Will show some of the data that DBDataModel holds
moreViewControllers that will need access to the same DBDataModel Object
i will go step by step through the application, and will then in the end post the error message i get when building.
AppDelegate.h
#import "DBFactoryClass.h"
AppDelegate.m
- (BOOL)...didFinishLaunching...
{
DBFactoryClass *FACTORY = [[DBFactoryClass alloc ]init ];
return YES;
}
DBFactoryClass.h
#import <Foundation/Foundation.h>
#import "DBDataModel.h"
#interface DBFactoryClass : NSObject
#property (strong) DBDataModel *DATAMODEL;
#end
DBFactoryClass.m
#import "DBFactoryClass.h"
#implementation DBFactoryClass
#synthesize DATAMODEL;
-(id)init{
self = [super init];
[self setDATAMODEL:[[DBDataModel alloc]init ]];
return self;
}
#end
ViewControllerA.h
#import <UIKit/UIKit.h>
#import "DBDataModel.h"
#class DBDataModel;
#interface todayViewController : UIViewController
#property (strong)DBDataModel *DATAMODEL;
#property (weak, nonatomic) IBOutlet UILabel *testLabel;
#end
ViewControllerA.m
#import "todayViewController.h"
#implementation todayViewController
#synthesize testLabel;
#synthesize DATAMODEL;
- (void)viewDidLoad
{
todaySpentLabel.text = [[DATAMODEL test]stringValue];
}
#end
DBDataModel.h
#import <Foundation/Foundation.h>
#interface DBDataModel : NSObject
#property (nonatomic, retain) NSNumber* test;
#end
DBDataModel.m
#import "DBDataModel.h"
#implementation DBDataModel
#synthesize test;
-(id)init{
test = [[NSNumber alloc]initWithInt:4];
return self;
}
#end
when i build it, i get the following error: EXC_BAD_ACCESS in this line:
#synthesize DATAMODEL;
of DBFactoryClass.m
What #synthesize does is to automatically generate implementations of the accessors for a property. EXC_BAD_ACCESS there means that you're accessing garbage when one of the accessors is executed.
That's probably happening here:
[self setDATAMODEL:[[DBDataModel alloc]init ]];
Make sure that DBDataModel's implementation of init actually returns a legitimate object.
As far as I can tell, your DBFactoryClass class is never stored anywhere, and therefore released right after the allocation if you use ARC (Since you use the strong keyword I assumed you do).
- (BOOL)...didFinishLaunching... {
DBFactoryClass *FACTORY = [[DBFactoryClass alloc ]init ];
// If you use ARC this might be released right afterwards
return YES;
}
If you want the factory to be a singleton, use something like this
+ (id)sharedInstance {
static dispatch_once_t once;
static MyFoo *instance;
dispatch_once(&once, ^{
instance = [[self alloc] init];
});
return instance;
}