Method parameters, protocol methods, delegates and respondsToSelector calls - objective-c

I have this kind of code :
MyClass.h
#import "MyOtherClass.h"
#interface MyClass : NSObject<SomeProtocol, MyOtherClassDelegate> {
...
}
MyClass.m
+ (void) doThis {
[someObject doThisWithDelegate:self]; // someObject is MyOtherClass type
}
MyOtherClass.h :
#protocol MyOtherClassDelegate <NSObject>
#required
// Some methods
#end
- (void) doThisWithDelegate:(id<SomeOtherProtocol>)delegate;
MyOtherClass.m :
- (void) doThisWithDelegate:(id<SomeOtherProtocol>)delegate {
if ([delegate respondsToSelector:#selector(myProtocolMethod:error:)]) do things;
}
Doing like this, I have the following warnings at compile time :
on the line [someObject doThisWithDelegate:self];
"Incompatible pointer types sending Class to parameter of type id<SomeOtherProtocol>"
on the method declaratéin in MyOtherClass.h :
"Passing argument to parameter 'delegate' here"
Before, I hadn't typed the id param (with id<SomeOtherProtocol>), it was just "alone" (id). I noticed that the test :
if ([delegate respondsToSelector:#selector(myProtocolMethod:error:)])
returned FALSE (but of course methods are implemented and declared in the delegate).
So I decided to try to force the id type to conform protocol that causes me that warning at compile time.
What is happening here ?
Why do I have this error, and why do the respondsToSelector do not return TRUE ?

If you want to call respondsToSelector: on a property with a type of id<SomeProtocol> then make sure your protocol conforms to the NSObject protocol.
Example:
#protocol SomeOtherProtocol <NSObject>
// methods
#end
This will then allow you to do:
if ([self.delegate respondsToSelector:#selector(myProtocolMethod:error:)]) {
}
This assumes delegate is defined as:
#property (nonatomic, weak) id<SomeOtherProtocol> delegate;

First make sure you conform to , respondsToSelector is part of NSObject.
Second I would check that i had effectively set the delegate property.
This line :
if ([self.delegate respondsToSelector:#selector(myProtocolMethod:error:)])
should work, since the property delegate is pointing to an object that should have myProtocolMethod:error: ....
I would probably debug with a breakpoint or a test on a setter for the delegate :
-(void)setDelegate:(id)delegate
{
NSLog("We are setting a delegate!: %#", [delegate description]);
_delegate = delegate;
}
If when you run your app, you see the "We setting...." after you do your if line or if you don't see it at all, then you know where your issue is.

Problem was that I've declare the doThis method as a class method and not instance method. So self is not valid when passed as a parameter.

Related

How to initialize a subclass when the init method of the superclass is NS_UNAVAILABLE

I'm attempting to write a test for an objective-c class. The class I'm trying to test is MyClass and it looks like this:
#interface MyClass : NSObject
- (void)dispatchEvent:(IMAAdEvent *)event;
#end
In order to test this dispatchEvent method, I need to pass in an instance of IMAAdEvent. The IMAAdEvent class comes from Google's library GoogleAds-IMA-iOS-SDK.
Unfortunately, I can't call init on this class because the init method is marked as NS_UNAVAILABLE. In XCode I get an error that reflects this:
'init' in unavailable
Ideally, I would like to make my own mock subclass of IMAAdEvent like this. Is there some way I can initialize my subclass without calling the unavailable init method on the superclass?:
#interface MockImaAdEvent : IMAAdEvent
#end
#implementation MockImaAdEvent
- (id)init {
// is there something I can do here so that I return an instances
// of the subclass without calling [super init]?
}
#end
As of Xcode 12.5, Swift is no longer able to use the previous solution. The compiler has started returning errors for init() is unavailable on lines of code where new init functions have been added. The Xcode 12.5 Release Notes indicate the following:
Clang now infers the availability of +new from availability annotations on -init methods. Since +new calls [[Foo alloc] init], +new isn’t available unless +init is available.
Despite this release note, there is still a valid workaround. By writing the mock class in Objective-C and using a bridging-header to bring it into Swift, the mock class can still call super.new to get a new instance of the parent class (and then customize the subclass from there).
Here is an example:
#interface MockInAppMessagingCampaignInfo ()
#property (strong, nonatomic) NSString *campaignNameValue;
#end
#implementation MockInAppMessagingCampaignInfo
+ (id)newWithCampaignName:(NSString *)campaignName {
MockInAppMessagingCampaignInfo *newObject = super.new;
newObject.campaignNameValue = campaignName;
return newObject;
}
- (NSString *)campaignName {
self.campaignNameWasCalled = YES;
return self.campaignNameValue ?: #"";
}
#end
If I use a method that's not called init then this seems to work. It still seems really weird to not call [super init] in this function, but it's working and returning a new instance of the MockImaAdEvent class
#interface MockImaAdEvent : IMAAdEvent {
enum IMAAdEventType type;
}
#property (nonatomic) enum IMAAdEventType type;
#end
#implementation MockImaAdEvent
#synthesize type;
- (id)initWithType:(NSInteger)_type {
type = _type;
return self;
}
#end
// in my test I can initialize like this:
MockImaAdEvent *adEvent = [[MockImaAdEvent alloc] initWithType:kIMAAdEvent_LOADED];
An alternative solution for Xcode 12.5 that doesn't require Objective-C bridging-header is to create a custom static initialiser that uses the objc runtime to invoke new.
I have used that for fakes that subclass objects with unavailable initialisers and works great.
Example:
static func customInit() -> SomeObjectFake {
let instance = SomeObjectFake.perform(NSSelectorFromString("new")).takeRetainedValue() as! SomeObjectFake
...
return instance
}

Objective-C: Compiler warning for accidental method override

I just found a bug in our iOS App which was triggered by an accidental method override.
In this case a property in a Subclass override a "private" method in the Superclass.
#interface MyClass : NSObject
- (void)doSomething;
#end
#implementation MyClass
- (void)doSomething {
[self hideView];
}
- (void)hideView {
}
#end
#interface MySubclass : MyClass
#property (NS_NONATOMIC_IOSONLY) IBInspectable BOOL hideView;
#end
#implementation MySubclass
#end
If [self hideView] is called within the doSomething method, the hideView method is not called. Instead just the property is asked for its value. I understand why this is happening but this is a error prone situation since the subclass is not aware of the hideView method.
My question is how to prevent those issues? Is there a compiler warning?
You can not completely prevent those issues, this is the intended behaviour in Objective C. However, if you want to protect your private methods from accidental override, you can prefix their names with an identifier of your library (or any other string that you like), for example:
- (void)__mylib_hideView {}

NSObject call to self gives XCode error

I have a class WebServices that inherits from NSObject. I am using xcode4.2 and ARC turned on.
When I created the class, there was no other method in the NSObject lie viewDidLoad or init.
The issues is that when I try to call self.something or [self someMethod] Xcode flags my code red and complains with:
implicit conversion of Objective-C pointer type 'Class' to C pointer type 'struct obj_class*' requires a bridge cast
Please help. Why isn't cocoa like java where you call "this" and get the object you are in?
// WebService.h file
#interface WebService : NSObject
#property (weak, nonatomic) NSString * myString;
+(void) setAndPrintMyString:(NSString*) someString;
#end
//WebService.m file
#import "WebService.h"
#implementation WebService
#synthesize myString=_myString;
+(void) printMyString:(NSString*) someString{
[self setMyString:someString]; //XCode does not allow
NSLog(#"myString is set to %#",self.myString); //XCode dose not allow
}
#end
Declaring a method with + means that it is a class method. Within a class method self refers to the class itself, which in your case would be [WebService class]. If you declared and instance method (using -) then inside the method self would refer to the instance, which is what you want.
To set an instance variable - you need an instance
WebService *webService = [[WebService alloc] init];
webService.myString = #"some string";
Now to make your method work you need to declare it with a - instead of + which makes it an instance method
- (void)printMyString:(NSString *)someString
{
[self setMyString:someString];
NSLog(#"myString is set to %#",self.myString);
}
Now
[webService printMyString:#"boom"];
results in the instance variable myString being set to boom and the console logging out `myString is set to boom".
viewDidLoad method doesn't fit with NSObject subclass. It's a method for UI which will be there in UIViewController subclasses.
Now, Coming to point about self.something OR [self someMethod], That works perfectly well with NSObject subclasses. You need to show us the code, in which you are facing problem.
Just for your reference (I think you should start developing for iOS after going through this):
NSObject Class Reference
UIViewController Class Reference

Odd or erroneous warning with multiple #protocols

I've got an odd situation where I've got two protocols and a couple classes that are initialized with delegates which implement the protocols. Standard stuff, really. However, XCode 4 is tossing up erroneous warnings that I can't seem to wrap my head around. Here's the collapsed source listing showing the definitions and implementations.
// CLASS A
#protocol ClassADelegate <NSObject>
-(void) DoSomething;
#end
#interface ClassA : NSObject { }
+(ClassA *) classWithDelegate:(id<ClassADelegate>)d;
-(ClassA *) initWithDelegate:(id<ClassADelegate>)d;
#end
#implementation ClassA
+(ClassA *) classWithDelegate:(id<ClassADelegate>)d {
return [[[ClassA alloc]initWithDelegate:d]autorelease];
}
-(ClassA *) initWithDelegate:(id<ClassADelegate>)d{
self = [super init];
return self;
}
#end
// CLASS B
#protocol ClassBDelegate <NSObject>
-(void) DoSomethingElse;
#end
#interface ClassB : NSObject<ClassADelegate> { }
+(ClassB *) classWithDelegate:(id<ClassBDelegate>)d;
-(ClassB *) initWithDelegate:(id<ClassBDelegate>)d;
-(void) DoSomething;
#end
#implementation ClassB
+(ClassB *) classWithDelegate:(id<ClassBDelegate>)d {
return [[[ClassB alloc]initWithDelegate:d]autorelease];
}
-(ClassB *) initWithDelegate:(id<ClassBDelegate>)d{
self = [super init];
return self;
}
-(void) DoSomething {
}
#end
Now here's the crazy part -- in the static constructor of ClassB on this line:
return [[[ClassB alloc]initWithDelegate:d]autorelease];
...the compiler is throwing a warning that doesn't make sense:
"Type 'id <ClassBDelegate>' does not conform to the 'ClassADelegate' protocol"
Am I missing something? The selector clearly sets the type to id<ClassBDelegate>, so d should be correct. Is this just the compiler getting confused?
The compiler is getting confused because you have methods that have the same name but different return/parameter types, and Objective-C is not exactly friends with method overloading.
When the compiler analyses:
[[[ClassB alloc]initWithDelegate:d]autorelease];
it starts with:
[ClassB alloc]
and the return type of +[ClassB alloc] is id, so the expression above is of type id. The next step is to analyse:
[##expression of type id## initWithDelegate:d]
and at this point there are two possible methods:
-(ClassA *) initWithDelegate:(id<ClassADelegate>)d;
-(ClassB *) initWithDelegate:(id<ClassBDelegate>)d;
Both are possible because the receiver is of type id, so it could be either a ClassA instance or a ClassB instance. But, aha!, the protocol of the first parameter differs, so there shouldn’t be any confusion. However, method lookup is based on two variables: whether it’s a class or instance method, and the corresponding selector. In the methods above, both are instance methods and both have the same selector. The compiler has decided to pick the first one.
One fix is to tell the compiler that the object returned by [ClassB alloc] is of type ClassB * instead of the generic type id:
return [[(ClassB *)[ClassB alloc]initWithDelegate:d]autorelease];
and maybe use this pattern in all similar classes.
Another fix is to give those methods different names, e.g. -initWithClassADelegate: and -initWithClassBDelegate:.

Objective C: warning on overriding init

I have a class 'DOInstance' which I inherit later on. Here's its declaration:
#interface DOInstance : NSObject {
}
- (DOInstance *) initWithSynckey:(NSString *)_synckey;
#end
Then I have a subclass of DOInstance:
#interface Workflow_Workitem_Header_1px: DOInstance {
}
//- (Workflow_Workitem_Header_1px *) initWithSynckey:(NSString *)_synckey;
#end
I go ahead and implement it in the implementation file:
- (Workflow_Workitem_Header_1px *) initWithSynckey:(NSString *)_synckey {
[super initWithSynckey:_synckey];
//..
//..
return self;
}
Now, If I do not declare initWithSynckey: (the commented declaration above) in my subclass declaration, I get a warning at the implementation: "warning: initialization from distinct Objective-C type". If I declare it, this warning goes away. Okay.
Moving on:
I later do an instantiation of my subclass:
Workflow_Workitem_Header_1px *instance;
instance = [[Workflow_Workitem_Header_1px alloc] initWithSynckey:#"xxxx"];
Now, this gives me the same warning (irrespective of whether or not I declare the corresponding initWithSynckey: selector in my subclass. Namely, "warning: initialization from distinct Objective-C type".
What am I doing wrong?
Methods named init... should have return type (id), not the type of the class. Check out NSString.h and NSArray.h (among other classes) for examples. That may be what is causing your problem.
In this case, the overriding method must return the same type as the superclass' declaration.
DOInstance defines this:
- (DOInstance *) initWithSynckey:(NSString *)_synckey;
so Workflow_Workitem_Header_1px must look like this:
#interface Workflow_Workitem_Header_1px: DOInstance {
}
- (DOInstance *) initWithSynckey:(NSString *)_synckey;
#end
Any time you get the warning "warning: initialization from distinct Objective-C type" you're doing something in contravention of your typing: changing a method signature, and the like.