objective c delegate understanding - objective-c

I have a class ApiClient which gets some data from a server using NSURLConnection and sends it back to its delegate
I have another class called Fetcher.h that calls some function on ApiClient and implements the delegate method in it.
I have a third class (View Controller) that calls the Fetcher.h executeCall() and triggers the whole process.
Fetcher *fetcher = [[Fetcher alloc] init];
[fetcher getData];
If I call the ApiClient code directly in the View controller, it works fine. Why wont it work from the Fetcher class? What am I doing wrong?
Within the Fetcher getData I have this code
APIClient* client = [APIClient sharedInstance];
[client setDelegate:self];
[client getData];
Thanks in advance.

It is possible that since you are not assigning the Fetcher object you are creating in the viewcontroller to any property or anything, it is getting released at the end of whatever method you are allocating it in. Try adding this to your viewcontroller:
.h
#interface ViewController
#property (strong, nonatomic) Fetcher *myFetcher; //Add this line in your view controller's interface
...
.m
#implementation ViewController
#synthesize myFetcher;
...
Fetcher *fetcher = [[Fetcher alloc] init];
[fetcher getData];
self.myFetcher = fetcher;//Add this line in your code as well

Try this...
#protocol APIClientProtocol <NSObject>
#optional
- (void) handleMessage;
#end
#interface APIClient : NSObject
#property (readwrite, nonatomic, strong) id<APIClientProtocol> delegate;
#end
#implementation APIClient
#synthesize delegate;
- (void) someAPIClientWork
{
// Do some client work here
if ( [self.delegate respondsToSelector:#selector(handleMessage)] )
[self.delegate performSelector:#selector(handleMessage)];
}
#end
.h of Fetcher
#interface Fetcher : NSObject <APIClientProtocol>
#end
.m of Fetcher
#implementation Fetcher
- (void) someInit
{
APIClient *client = [APIClient sharedInstance];
client.delegate = self;
}
- (void) handleMessage
{
// Something in APIClient was called
}
#end

Related

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

NSSoundDelegate not being called

I am updating some legacy objective C code to be able to be compiled under OSX 10.13. The legacy code worked and most of the update code does as well except for an NSSoundDelegate that needs to handle a didFinishPlaying function. The delegate method is not being called. The delegate method is contained in a class called MyClass. Here is relevant code.
In MyClass.h:
#class MyClass;
#protocol MyClass <NSObject>
#optional
- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)flag;
#end
#interface MyClass : NSObject <NSSoundDelegate>
{
}
#property (nonatomic, assign) id <NSSoundDelegate> delegate;
- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)flag;
- (id) init;
#end
Then in MyClass.m:
#implementation MyClass
#synthesize delegate;
- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)flag
{
if (flag) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"handleNSSoundDidFinishPlaying" object:sound];
}
}
- (id)init
{
MyClass *thePointer;
self = [super init];
if (self) {
thePointer = self;
self.delegate = (id)thePointer;
isInitialized = NO;
isClosing = NO;
[self set_currentSounds:[NSMutableArray arrayWithCapacity:0]];
}
return self;
}
#end
Can anyone see what I'm missing?
I think you should notify the delegate object like:
if([_delegate respondsToSelector:#selector(sound: didFinishPlaying:)])
[_delegate sound:self didFinishPlaying:_flag];
Hope this will help you.
Found the problem! When allocating the sound to be played, you have to set the sounds delegate using [theSnd setDelegate:self]; so that when the sound stops playing, the delegate gets called, in this case the delegate is in the MyClass .m file.

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

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;
}

Passing data between objects/threads in Cocoa

I create a new thread from a controller like this:
[NSThread detachNewThreadSelector:#selector(makeMovie) toTarget:movieMaker withObject:nil];
What is the best way to call methods on the controller from the new thread/movieMaker object?
In this case, you probably want to use the delegate pattern. In your movie-maker class’s .h file:
#protocol MovieMakerDelegate;
#interface MovieMaker : NSObject
{
id<MovieMakerDelegate> delegate;
...
}
#property (nonatomic, assign) id<MovieMakerDelegate> delegate;
...
#end
#protocol MovieMakerDelegate <NSObject>
- (void)movieMaker:(MovieMaker *)maker didSomething:(id)result;
#end
...and in its .m:
#implementation MovieMaker
#synthesize delegate;
...
- (void)makeMovie
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
...
[self performSelectorOnMainThread:#selector(notifyDelegateOfDoingSomethingWithObject:) withObject:someObject waitUntilDone:YES];
[pool release];
}
- (void)notifyDelegateOfDoingSomethingWithObject:(id)someObject
{
[self.delegate movieMaker:self didSomething:someObject];
}
...
#end
Then in your controller’s header, declare that it supports the MovieMakerDelegate protocol, like this:
#interface MyController : Whatever <MovieMakerDelegate>
and in your .m, before calling your -detachNewThreadSelector:etc:, call movieMaker.delegate = self;. Implement the -movieMaker:didSomething: method on your controller class and you’re good to go.
You may pass the controller instance to your makeMovie method, with the withObject parameter.
[NSThread detachNewThreadSelector:#selector(makeMovie) toTarget:movieMaker withObject:self];
You can also use a singleton-like pattern, if applicable, to retrieve your controller instance from the thread.
Use performSelectorOnMainThread:withObject:waitUntilDone: (or similar performSelector:... methods), it is the easiest way.