I'm trying to accomplish something like the following:
- (id<SomeProtocol>)instanceFromClass:(Class<SomeProtocol>)cls
{
return [[cls alloc] initUsingSomeConstructorDefinedInProtocolWithValue:_value];
}
However, I'm getting a No Known class method for selector 'alloc' error. How may I specify in my signature that I want to receive a class that conforms to a protocol? Or, if that part is correct, how may I create an instance from that argument using a constructor defined in the specified protocol?
Not sure why the compiler complains but you can fix by casting your parameter back to Class
- (id<SomeProtocol>)instanceFromClass:(Class<SomeProtocol>)cls
{
return [[(Class)cls alloc] initUsingSomeConstructorDefinedInProtocolWithValue:_value];
}
while still getting you the type checking you want for the parameter as hinted at in this SO answer: Declare an ObjC parameter that's a Class conforming to a protocol
Your use of the protocol is 'fine'. The issue is that the cls parameter is tagged as a class which conforms to a protocol that defines instance methods (the init method). This doesn't tell the compiler that the +alloc method is available because that is a class method on NSObject.
You can add the +alloc method to the protocol. Or you can do some casting to tell the compiler to trust you.
+ alloc is a method defined by the top level class NSObject. When you have a class like Class <SomeProtocol>, the compiler only knows that this is some class and it implements SomeProtocol but it cannot know if that is a subclass of NSObject or not, since in Obj-C you can define own top-level classes that don't inherit from NSObject (not that this is generally a good idea but it is possible).
There is a special "hack" in the compiler that in case the type is just Class, the compiler will always assume that it is a subclass of NSObject and would only fail at runtime in case it isn't. But this hack only works for the exact type Class and not for Class <SomeProtocol> which is a distinct type.
So what you can do is to either cast to Class, so the hack works again:
[[(Class)cls alloc] ...]
or you can also do that
[[cls.class alloc] ...]
in case that cls will be a subclass of NSObject at runtime because then it will have a + class method.
Note that if I call instanceFromClass: with a class, that does implement SomeProtocol but is no subclass of NSObject and also does not implement a + alloc method, both methods above will fail at runtime and the app will crash.
Related
I am used to seeing things like id<NSCopying> myVar or MyObject<NSCopying> myVar, where we are stating that the variable in question can happily have NSCopying methods called on it without the compiler throwing a wobbly.
But I recently spotted some code that defined a variable like this:
Class<NSCopying> myClass;
I was wondering what this actually means as it seems subtly different from the top two examples. It seems like we're saying that the variable myClass can happily accept method calls from NSCopying - but having a class type able to accept these instance variable methods doesn't seem to make much sense.
It has occurred to me that variables of type class are technically objects themselves which is probably confusing me or the compiler (probably me!).
So I guess I'm asking:
What does something like Class<NSCopying> myClass; actually mean
How does Class<NSCopying> myClass; differ to something like id<NSCopying> myVar
Where could something like Class<NSCopying> myClass; be meaningfully used?
Notes:
I am just using NSCopying as an example and isn't integral to my use case
Wherever I refer to Class I mean the Obj-C keyword Class for declaring variables that are of type Class. I am not using this as a generic term for any class type.
Well I think it might mean that Class <NSCopying> myClass points to a class that implements NSCopying protocol. It can be useful if protocol has class method declarations and you want to call them.
For example:
#protocol NSSecureCoding <NSCoding>
#required
// The Secure Coding Guide should be consulted when writing methods that decode data.
+ (BOOL)supportsSecureCoding;
#end
id <NSCopying> myVar
Using id you can supply an instance of any type of class (so long as it implements the protocol, or you cast it to make the compiler trust you).
Class <NSCopying> myClass
Using Class means you can only supply a Class, not an instance.
Say you wanted your app to be configurable. Say you had a number of classes which offered a number of different features, but you didn't care about inheritance they had, just how to create and configure them. You could use a protocol that the class has to conform to and offer a method where the classes can be registered. Then you can instantiate those classes using the defined protocol without knowing anything else about the class.
Class<ProtocolName> is the type of a variable pointing to a class object with a meta class that conforms to ProtocolName. In other words: The class implements the methods from ProtocolName with class methods.
Example: NSCopying contains one required method:
#protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
#end
Class<NSCopying> means that there is a method
+ (id)copyWithZone:(NSZone *)zone;
in the class being pointed to. That is true for all NSObject derived classes and useful for using class objects as dictionary keys. See the documentation.
What does it mean in Objective-C if you try to mix Class and Protocol? I'm trying to get the complier to warn me at compiler time if a class doesn't conform to a protocol. For example:
#protocol FLLiveDataProtocol <NSObject>
...
#end
- (id)initWithDataPath:(NSString *)path usingDataClassFactory:(Class<FLLiveDataProtocol>)dataFactoryClass;
However, I get all kind of interesting issues when I try this. First, it doesn't trigger a compiler error if you pass in a class that doesn't conform to the protocol, second you will get errors such as "No know class method for selector alloc" if you try to do [[dataFactoryClass] alloc] init] even through the protocol conforms to NSObject.
I haven't seen many examples of people trying to mix protocol's and the Class keyword (I should say Typedef -- thanks Nikolai).
I know I can use conformsToProtocol to check at runtime, but it would be nice if I could get this to check at compile time.
There's no static type information for class objects in Objective-C (see this question), so there's no way to express the need for a class to conform to a protocol.
Side note: Class is not a keyword but a typedef in objc.h:
typedef struct objc_class *Class;
second you will get errors such as "No know class method for selector
alloc" if you try to do [[dataFactoryClass] alloc] init] even through
the protocol conforms to NSObject.
The reason for this is that neither your protocol or the NSObject protocol declare +alloc. You could fix this by adding an +alloc method (or some other class method that returns a new instance) to your protocol declaration.
Is there a way to indicate to the compiler that a class object conforms to a protocol?
As I understand, by creating +(void)foo class methods, an instance of that class object will have those methods as instance methods. So, as long as I create +(void)foo methods for all required protocol methods, I can have a class object act as a delegate.
My problem of course is that in the class's header file, I only know how to indicate that instances of the class conform to the protocol (as is typically the case). So, the best I've figured out is to cast the class object like so:
something.delegate = (id<SomethingDelegate>)[self class]
Any ideas?
Related, but different:
ObjC: is there such a thing as a "class protocol"?
What you're doing now is correct as it will silence warnings which is your goal. You will be sending the class object messages defined in the protocol for instances which is a bit confusing, but the runtime doesn't care.
Think about it this way: you want to set a delegate to an object that responds to the messages defined in the protocol. Your class does this, and your class is also an object. Therefore, you should treat your class like an object that conforms to that protocol. Therefore, what you've written is completely correct (based on what you're trying to do).
One thing to note, though, is this class will not properly respond to conformsToProtocol:. This is generally okay for a delegate setup anyway (delegates don't usually check if the class conforms — they just check if it can respond to a selector).
As a side note, one thing you can do syntactically is:
Class<SomethingDelegate> variable = (Class<SomethingDelegate>)[self class];
The difference here is that the compiler will use the class methods from the protocol instead of instance messages. This is not what you want in your case, though.
There is no Objective-C syntax to indicate that a metaclass conforms to a protocol.
I think you can do it at runtime, by using class_addProtocol on the metaclass. But I haven't tried it.
I guess you could also write a +conformsToProtocol: method on your class, and lie about your conformance. This could have unexpected side-effects, since there's already a +conformsToProtocol: on NSObject (in addition to -conformsToProtocol:).
Neither of these will eliminate the need for a cast to shut the compiler up. Just use a singleton.
I have the following method:
-(void)SomeMethod:(id)classOrProtocol;
It will be called like this:
[self someMethod:#protocol(SomeProtocol)];
Or
[self someMethod:[SomeClass class]];
Within the method body I need to decide if |classOrProtocol| is:
Any Class(Class) OR Any Protocol(Protocol) OR Anything else
[[classOrProtocol class] isKindOfClass: [Protocol class]]
Results in a (build)error:
Receiver 'Protocol' is a forward class and corresponding #interface may not exist
So how can I tell a Protocol from a Class from anything else?
In Objective-C 2 (i.e. unless you use 32 bit runtime on OS X) Protocol is defined to be just a forward class, see /usr/include/objc/runtime.h. The real interface is nowhere declared. You can try to include /usr/inlcude/objc/Protocol.h by saying
#import <objc/Protocol.h>
but as is written there, no method is publicly supported for an instance of Protocol. The only accepted way to deal with Protocol instances is to use runtime functions, given in Objective-C Runtime Reference. It's not even publicly defined whether Protocol is a subclass of anything, and it's not even stated that it implements NSObject protocol. So you can't call any method on it.
Of course you can use the source code of the runtime to see what's going on. Protocol inherits from Object (which is a remnant from pre-OpenStep NeXTSTep), not from NSObject. So you can't use the familiar methods for NSObject-derived objects, including Class of NSObject-derived objects. See the opensourced implementations of Protocol.h and Protocol.m. As you see there, the class Protocol itself doesn't do anything, because every method just casts self to protocol_t and calls a function. In fact, as can be seen from the function _read_images and others in objc-runtime-new.mm, the isa pointer of a Protocol object is set by hand when the executable and libraries are loaded, and never used.
So, don't try to inspect whether an id is a Protocol or not.
If you really need to do this, you can use
id foo=...;
if(foo->isa==class_getClass("Protocol")){
...
}
But, seriously, don't do it.
This is not an issue 'caused by inability to determine whether it's class or protocol. The error is 'caused by missing interface of Protocol class. Make sure you import Protocol.m at the top of your implementation file where you're testing argument's type.
You can also try using NSClassFromString() function which will return Class object or nil. Do note though that if nil is returned it doesn't mean that argument is protocol. It just means that it could be undefined class too!
There is also method NSProtocolFromString which returns appropriate results - Protocol for protocol and nil for undefined protocol.
There are a couple of things about Objective-C that are confusing to me:
Firstly, in the objective-c guide, it is very clear that each class needs to call the init method of its subclass. It's a little bit unclear about whether or not a class that inherits directly from NSObject needs to call its init method. Is this the case? And if so, why is that?
Secondly, in the section about NSObject, there's this warning:
A class that doesn’t need to inherit any special behavior from another class should nevertheless be made a subclass of the NSObject class. Instances of the class must at least have the ability to behave like Objective-C objects at runtime. Inheriting this ability from the NSObject class is much simpler and much more reliable than reinventing it in a new class definition.
Does this mean that I need to specify that all objects inherit from NSObject explicitly? Or is this like Java/Python/C# where all classes are subtypes of NSObject? If not, is there any reason to make a root class other than NSObject?
1) Any time an object is allocated in Objective-C its memory is zeroed out, and must be initialized by a call to init. Subclasses of NSObject may have their own specialized init routines, and at the beginning of such they should call their superclass' init routine something like so:
self = [super init];
The idea being that all init routines eventually trickle up to NSObject's init.
2) You need to be explicit about the inheritance:
#instance myClass : NSObject { /*...*/ } #end
There is no reason to have a root class other than NSObject -- a lot of Objective-C relies heavily on this class, so trying to circumvent it will result in you needlessly shooting yourself in the foot.
Since it is possible to inherit from different root base classes, yes you must explicitly declare you inherit from NSObject when you make any new class (unless of course you are subclassing something else already, which itself in turn probably subclasses NSObject).
Almost never is there a need to make your own base class, nor would it be easy to do so.
Objective-C can have multiple root classes, so you need to be explicit about inheritance. IIRC NSProxy is another root class. You'll likely never want or need to create your own root class, but they do exist.
As for calling NSObject's init, it's part custom and part safety. NSObject's init may not do anything now, that's no guarantee that future behaviour won't change. Call init to be safe.
You need to call [super init] because there is code behind initializing that you dont have to write because it is written for you in NSObjects init, such as probably actual memory allocation etc.