How to access a method by inheritance of #protocols in Objective-C? - objective-c

I have a program where inheritance of protocols are there say:
#protocol A
-(void)methodA
#end
The protocol which inherits:
#protocol B<A>
-(void)methodB
#end
The class which implements #protocolA method is
#interface classB<B>
#end
#implementation classB
-(void)methodA
{
//somecode
}
#end
Now i wanted the methodA to be called from Some other class:
#implementation SomeotherClass
{
//call of methodA
//????
id<A>obj=[[classB alloc]init];//i have tried it
[obj methodA];// even this is not working
}
How to do that?

Just send the message as usual:
SomeotherClass *obj = [[[SomeotherClass alloc] init] autorelease];
[obj methodA];
Since instances of classB implement protocol B, they also claim to respond to -methodA.
The following compiles and runs without any problems:
MyClass.h:
#import <Foundation/Foundation.h>
#protocol A
-(void) methodA;
#end
#protocol B <A>
-(void) methodB;
#end
#interface MyClass : NSObject <B>
{
}
#end
MyClass.m:
#import "MyClass.h"
#implementation MyClass
-(void) methodA
{
NSLog(#"%s", __PRETTY_FUNCTION__);
}
-(void) methodB
{
NSLog(#"%s", __PRETTY_FUNCTION__);
}
#end
the code:
MyClass *obj = [[MyClass alloc] init];
[obj methodA];
[obj methodB];
[obj release];

You don't say how it fails to work. I would guess that there's a compilation error as you don't implement methodB in your classB.
Another possibility: what happens after you've initialised classB in SomeOtherClass? Are you sure that you get a valid object back? If it returned a nil the run time would be perfectly within its rights to do nothing when you sent the methodA message to it.

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.

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.

How to do method overriding in Object-C

I am trying to do a class extension and calling the extended method from a generic method. I'm just wondering if it's possible to do it? Below is my code.
ClassA.h:
#interface ClassA : NSObject
-(void) method;
ClassA.m:
#import "ClassA.h"
-(void) method{
NSLog(#"do A");
}
ClassB.h:
#interface ClassB : ClassA
-(void) method;
ClassB.m:
#import "ClassB.h"
-(void) method{
NSLog(#"do B");
}
and in MainClass.m
-(void) doMethod {
ClassA *class = [[ClassB alloc] init];
[class method];
}
I tried to do something like this, but nothing was print out. Is it possible to do this? and will it print out "do B"??
Your code compiles and runs fine (minus a small typo in #interface ClassA.h that should be #interface ClassA):
cristi:tmp diciu$ cat test.m
#import <Cocoa/Cocoa.h>
#interface ClassA : NSObject
- (void) method;
#end
#interface ClassB : ClassA
-(void) method;
#end
#implementation ClassA
-(void) method{
NSLog(#"do A");
}
#end
#implementation ClassB
-(void) method{
NSLog(#"do B");
}
#end
int main()
{
ClassA *class = [[ClassB alloc] init];
[class method];
}
cristi:tmp diciu$ gcc test.m -framework Cocoa
cristi:tmp diciu$ ./a.out
2012-06-21 15:04:22.652 a.out[14408:707] do B
Apple's Objective-C docs contain a chapter on how inheritance works that is available online here.

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.