Is there a way in Obj-C to pass a class as a parameter which conforms to a protocol?
For example, say I have a slew of classes which conform to this protocol:
#protocol IMyProtocol <NSObject>
+ (id)someMethodINeed;
#end
I want to set up a method which takes in any class which conforms to this protocol.
#interface SomeClass : NSObject <IMyProtocol>
#interface AnotherClass : NSObject<IMyProtocol>
I want to make a method like this:
- (void)doSomething:(Class<IMyProtocol>)customClass
{
[customClass someMethodINeed];
//A whole bunch of other code I don't want to copy/paste each time
}
And call it with any of those classes
[self doSomething:SomeClass];
[self doSomething:AnotherClass];
I can't figure out the syntax though. Is this possible?
To make your code work you just need to pass the class, change your two calls to:
[self doSomething:SomeClass.class];
[self doSomething:AnotherClass.class];
However while this works the compiler is not doing the checks you probably expect. You can pass any class to doSomething: regardless of whether it implements your protocol; and [customClass someMethodINeed] is a dynamic call resolved (or not) at runtime and may produce an "unrecognized selector sent to class" error.
(Objective-C always does dynamic method lookup but with typed values the compiler is usually able to determine at compile time whether the runtime resolution will succeed or not. What you are are seeing here is the behavour you get when you program using id.)
Related
In Java, C++11 and some other languages you can specify that a method is intended to override another method from a base class or interface, if you then at a later point remove the method from the base class you get a compiler error. I use protocols with optional methods a lot and find that if I remove a method from there I have a tendency to forget to remove the code that implemented the method. This does not generate an error or a warning, thus creating a "dead" method.
Consider:
#protocol Prot <NSObject>
#optional
- (void)bar;
- (void)tempBar;
#end
#interface MyType : NSObject <Prot>
#end
#implementation MyType
- (void)bar { /**/ }
- (void)tempBar { /**/ }
#end
If I at one point remove tempBar from the protocol, I would like to get at least a warning from tempBar being implemented in MyType.
Is there any way in Objective-C to specify that a method is expected to be an implementation of a protocol method?
Objective-C is a dynamic language and this is rather impossible to enforce at compile time. Note that in Obj-C you can actually call methods there are not even there and the app won't crash (well, the default implementation will raise an exception but you can change that behavior).
The method can be also added in an extension, or added at runtime. Or it is just not present in the header.
Note there is also the opposite problem. When subclassing, you can override a method which you don't even know is there, because it is not present in the headers.
This is one of the reasons why Apple is moving to a more predictable language, that is, Swift.
Question is in title. Code example:
UIViewController <MyProtocol> *viewcontroller = ...;
[viewcontroller methodFromProtocol]; // I expect to be able to call all methods that the protocol defines
//UIViewControllerSubclass implements MyProtocol
UIViewControllerSubclass *viewControllerSubclassWithoutMyProtocol = [[UIViewControllerSubclass alloc] init];
[viewControllerSubclassWithoutMyProtocol methodThatIsNotInTheInterfaceIsDisplayedHere]; // I only expect to be able to call the methods that are defined in this class' interface even though this class implements MyProtocol
Your question isn't completely clear, but I think you are asking if you can "privately" conform to a protocol?
This can be done by declaring that you conform to the protocol inside the implementation file, rather than the interface file. With view controllers, you can do this in the class continuation that is generated for you automatically in the .m file, otherwise you'll need to add the class continuation in yourself:
#interface MyVCSubclass () <MyProtocol>
Now, any class that imports only the header file will not know your controller conforms to the protocol.
[viewControllerSubclassWithoutMyProtocol methodThatIsNotInTheInterfaceIsDisplayedHere];
I only expect to be able to call the methods that are defined in this class' interface even though this class implements MyProtocol
That's a bad expectation. Objective-C lets you call any method that an object implements. If you try to call a method that an object doesn't implement, two things should happen:
You get a compiler warning (not an error though)
When the code runs, it crashes, unless you've taken steps to handle such an event.
If a class implements a protocol but doesn't declare that it does so in a public header, then you can still call the method (since Objective C doesn't have private methods). I'd have thought you'd get a compiler warning, but if you're calling this from a file within the same Xcode project as your object (that is, you're not building a static library) then it's possible that Xcode is getting smart and deciding that since the method exists, it must be OK to call.
It wasn't clear from your question what you expected to happen and what actually happened. If you supply that information, we'll be able to give better answers.
Let's say I have this class and its subclass
#interface MySuperClass
- (void)open:(id)type value:(id)value;
- (void)openWebpage:(NSURL*)url;
#end
#interface MySubClass
- (void)openWebpage:(MyBookmarkClass*)bookmark;
#end
and that calling [someMySubclass openWebpage:someBookmark] calls [super open:BookmarkClass value:self.url]. And open:value calls [self openWebpage:url].
I realize this is very contrived, but I ran into a similar situation. My confusion is that even though [self openWebpage:url] is being called in MySuperClass, when it gets executed openWebpage: is being run in the context of the caller, MySubClass, which doesn't know what to do with an NSURL.
So my question is: is there any way to force something to be called in its original context? Or make it as though it calls super as many time as it can up the chain and find the method closest to the top?
There is only one context. There's a single object. Its class is MySubClass.
It is a mistake to have overridden the method with a different incompatible type. Don't do that. This is not C++ with function overloading. There's no dispatch based on the type of arguments.
The convention is to name methods by what they're acting on. So, you may have a method named -openWebpageURL: in MySuperClass and another method introduced in MySubClass named -openWebpageBookmark:. Note that MySubClass would still have a method named -openWebpageURL: inherited from MySuperClass.
If I generate methods dynamically on runtime and then call them - how can I convince compiler that the class will respond to undeclared (generated) methods and make it not throw warnings?
Update in regard to answers
When I generate the methods - their name is not known at compile time. To give an example - if I have a view controller MyFooController and it's initiated with method initWithFoo:(Foo*)foo, I'd be able to generate method like pushMyFooControllerWithFoo:(Foo *)foo for UINavigationController. Hence you notice that declaring such methods would be counter-productive.
This doesn't directly answer your question, but if I was generating method names (presumably from strings), I would call them using the string names, hence bypassing the compiler warnings.
[fooController performSelector:NSSelectorFromString(#"pushMyFooControllerWithFoo:") withObject:foo];
That way you are responsible for the validity of the generated method names.
Since you are adding methods on runtime, so you should also invoke them with runtime function, objc_msgSend or performSelector:withObject:for example, so the compiler will not going to warn you anything.
Well, if you call them, you know their signature, and if you know their signature, you can declare them, can't you?
Declare this method in a category for NSObject and make an empty implementation:
#interface NSObject (DynamicMethodsCategory)
- (void)doSomething;
#end
#implementation NSObject (DynamicMethodsCategory)
- (void)doSomething
{
}
#end
In your object you can call it without any warnings:
#implementation MyObject
- (void)someMethod
{
[self doSomething];
}
#end
Then generate implementation of [MyObject doSomething] dynamically, it will be called instead of NSObject's one.
Update:
Alternatively, the method can be declared in a category for the object. This suppresses the compiler's Incomplete implementation warning. However, I think this is not a good workaround, because the application will crash if the method is not created dynamically in runtime before it is called.
I've created my own Delegate for a ObjC class. The class itself deals with Core Data operations. Delegate methods are used to inform other classes about changes that happened to the datastore. The class that deals with the datastore is called Datastore and it's delegate is called DatastoreDelegate. A UIViewController of mine (ContactsViewController) implements the delegate.
My DatastoreDelegate is declared as followed:
#class Datastore;
#protocol DatastoreDelegate <NSObject>;
#optional
- (void)didAddMessage:(Message *)message;
- (void)didUpdateContact:(Contact *)contact;
- (void)didAddContact:(Contact *)contact;
- (void)didUpdateContact:(Contact *)contact;
- (void)didDeleteContacts;
#end
The weird thing is, my code all worked fine with these methods except for the [didAddMessage:] method. Whenever I try to call this delegate from within the Datastore class I get an error from the ContactsViewController. The error tells me the [didAddMessage:] selector is missing in the ContactsViewController instance (Unrecognized selector sent to instance). How can the selector be missing if it's optional?
I should note that my Datastore class is a Singleton. I'm not sure if this is somehow related to this issue I'm having.
"Optional" means the caller is responsible for checking that a target responds to a given selector. E.g.:
if ([_myDelegate respondsToSelector:#selector(didAddMessage:)])
{
[_myDelegate didAddMessage:theMessage];
}
Did you implement didAddMessage: in your ContactsViewController? It's optional so you aren't forced to implement it, but if you send the didAddMessage: message to ContactsViewController but haven't actually implemented it in ContactsViewController, you'll still get a compiler warning. In other words, #optional just means you don't have to implement it, but the compiler may still give a warning if you haven't implemented it but try to use it.
What you might want to do in Datastore is this:
if ([delegate respondsToSelector:#selector(didAddMessage:)]) {
[delegate didAddMessage:theMessage];
}
rather than just:
[delegate didAddMessage:theMessage];
(You'll still get a compiler warning in the first example, but it's safe to ignore since you're checking at runtime to see if the appropriate method is implemented in the delegate.)