I want to implement subscripting in my custom class and thus implemented the following methods:
- (id)objectForKeyedSubscript:(id<NSCopying>)key;
- (void)setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key;
But I have a strange issue, because I've never met it before: [(id)obj isKindOfClass:] throws an ARC Semantic Issue:
No known instance method for selector 'isKindOfClass:'
As far as I remember, I didn't have any problems with id before... Is it a bug with Xcode (I'm using the Xcode 5 Developer Preview 2), or have I forgotten something important?
- (id)objectForKeyedSubscript:(id<NSCopying>)key {
if(![key isKindOfClass:[NSString class]]) { // error
...
} else {
...
}
}
isKindOfClass: is a method of the NSObject protocol, so you can either
declare key as conforming to the protocol
- (id)objectForKeyedSubscript:(id <NSCopying, NSObject> )key { ...
or require key to be derived from NSObject (which conforms to that protocol):
- (id)objectForKeyedSubscript:(NSObject <NSCopying> *)key { ...
Related
Consider the following Objective-C protocol declaration, which requires only class methods:
#protocol TDWMethoding<NSObject>
+ (void)foo;
+ (void)bar;
#end
Assuming I need to return an instance of a Class which conforms to this protocol from a method, how am I supposed to specify the return type?
- (nullable /*return-type*/)instantiateMethoding {
Class instance = ... // some implementation
if ([instance conformsToProtocol:#protocol(TDWMethoding)]) {
return instance;
}
return nil;
}
There are a number of working options I considered so far in regards to how to express the /*return-type*/, but each has its own downsides:
Class - this way it doesn't expose conformance. What kind of Class is it? What does it do? Does it conform to the protocol at all?
Class<TDWMethoding> - this looks like a viable solution and even was suggested a few times by other developers (here and here) but I personally find it inconsistent and misleading: when we have a variable of form Type<Protocol> *instance, it commonly means that protocol class methods should be sent to the instance's class ([[instance class] foo]) not the instance itself ([instance foo]);
id<TDWMethoding> and returning an instance of the class instead - this is consistent, but it requires me to instantiate the class, which is both redundant and prevents me from hiding the constructors of the utility classes which conforms to the protocol with NS_UNAVAILABLE macro.
Is there a better semantic to express such a return-type?
Class<TDWMethoding> is correct. It's not inconsistent. When something is of type Class, you send class methods to it. When something is an instance, and you want to send to the class, you access its -class.
That said, this does seem very strange, and likely means you're overusing Class methods. You should think hard about whether a sharedInstance is a better model for this.
But if you want to identify the type, Class<TDWMethoding> is correct, though id would likely be more common, as discussed in How to cast Class object to conformance witih protocol.
After digging a little deeper into The Objective-C Programming Language documentation, I actually found the exact answer to such a scenario:
Protocols can’t be used to type class objects. Only instances can be statically typed to a protocol, just as only instances can be statically typed to a class. (However, at runtime, both classes and instances respond to a conformsToProtocol: message.)
Which means that it's just not supported and I should implement this differently. (e.g. with use of a singleton pattern, as suggested in Rob's answer)
The solution is doesn't use such protocols at all. Why? Because it's inflexible.
It should be just:
#protocol TDWMethoding
- (void)foo;
- (void)bar;
#end
Then you will be able to do any what you want, for example you will be able to create wrapper for yours class, that will be implementing yours protocol.
#interface TDWMethodingModel<TDWMethoding>
#property (nonatomic, readonly) void (^fooCaller)(void);
#property (nonatomic, readonly) void (^barCaller)(void);
- (instancetype)initWithFooCaller:(void (^)(void))fooCaller barCaller:(void (^)(void))barCaller NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
#end
#implementation TDWMethodingModel
- (instancetype)initWithFooCaller:(void (^)(void))fooCaller barCaller:(void (^)(void))barCaller {
self = [super init];
if (nil == self) {
return nil;
}
_fooCaller = fooCaller;
_barCaller = barCaller;
return self;
}
- (void)foo {
self.fooCaller();
}
- (void)bar {
self.barCaller();
}
#end
then:
- (id<TDWMethoding>)instantiateMethoding
{
static id<TDWMethoding> methoding;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
methoding = [[TDWMethodingModel alloc] initWithFooCaller:^{
[SomeClass foo];
} barCaller:^{
[SomeClass bar];
}];
});
return methoding;
}
Using this Objective-C property:
#interface TSOnboardingPersonalizeViewController
#property (nonatomic, weak) NSObject<PersonalizeContentCoordinatorDelegate> * _Nullable delegate;
#end
crashes in a Swift extension like this:
extension TSOnboardingPersonalizeViewController {
func next() {
self.delegate?.performAction(.Forward)
}
}
POing self.delegate shows:
(lldb) po self.delegate
▿ Optional<protocol<PersonalizeContentCoordinatorDelegate>>
▿ Some : <MyApp.PersonalizeContentCoordinator: 0x8e964cf58140>
But it doesn't crash if I cast the property to the protocol type:
extension TSOnboardingPersonalizeViewController {
func next() {
if let delegate = self.delegate as? PersonalizeContentCoordinatorDelegate {
delegate.performAction(.Forward)
}
}
}
POing delegate shows:
(lldb) po delegate
<MyApp.PersonalizeContentCoordinator: 0x7f86bcf59930>
Why do I need to explicity cast the property to the PersonalizeContentCoordinatorDelegate protocol type?
I'm using Swift 2.2 and Xcode 7.3
I think it could be due to the fact that the delegate is an NSObject, which probably doesn't have a performAction method. So, even though the delegate conforms to the protocol in Objective-C, Swift probably sees an NSObject (which doesn't include that method).
This seems like a "Lost in Translation" sort of thing, where Objective-C is telling Swift "This object conforms to this protocol." Then when executed, Swift tries calling that method on an NSObject which obviously doesn't include that method and crashes. This would explain why casting would work, because until you tell Swift you have an object of a certain type, it can't execute any of that type's specific methods (even if the underlying object actually is that type).
This is just a guess, though and should definitely be taken with a grain of salt (and maybe a better answer if one comes along).
In Objective-C I am often using the following "design pattern" in my code to "broadcast" the scenes input callbacks:
#protocol RPSceneDelegate <NSObject>
-(void)someMethod;
-(void)didTap;
#end
#interface RPScene : SKScene
#property (nonatomic, strong) NSMutableArray<id<RPSceneDelegate>>* delegates;
-(void)addDelegate:(id<RPSceneDelegate>)delegate_;
-(void)removeDelegate:(id<RPSceneDelegate>)delegate_;
#end
#implementation RPScene
#pragma mark - Delegate Handling
-(void)addDelegate:(id<RPSceneDelegate>)delegate_ {
if ([self.delegates containsObject:delegate_]) return;
[self.delegates addObject:delegate_];
}
-(void)removeDelegate:(id<RPSceneDelegate>)delegate_ {
if (![self.delegates containsObject:delegate_]) return;
[self.delegates removeObject:delegate_];
}
-(void)didTap {
for (id<RPSceneDelegate> delegate_ in self.delegates) {
// Just a small example to show what I am doing
[delegate_ performSelector:#selector(didTap)];
}
}
#end
Last night I tried to convert this to swift using Swift-Arrays (not NSMutableArray) but I ended up totally frustrating when checking:
array.contains(theObject)
To get a better understanding of Swift it would help me if someone could convert that little piece of code to Swift. So how do I implement that in Swift!?
EDIT: My code in Swift
protocol RPSceneDelegate {
func someMethod()
}
class RPScene: SKScene {
var delegates = [RPSceneDelegate]()
func addDelegate(delegate: RPSceneDelegate) {
if !delegates.contains(delegate) {
delegates.append(delegate)
}
}
}
What works fine is:
delegates.append(delegate)
What gives me an error is:
delegates.contains(delegate)
The Compiler says:
Cannot convert value of type 'RPSceneDelegate' to expected argument
type '#noescape (RPSceneDelegate) throws -> Bool'
Problem is. I just don't know what that means ... My first idea was that .contains() requires a function, not an instance / object but I have no clue how to implement that...
It's not a helpful error message...
What is means is that in order to check if an array contains something the compiler needs to be able to compare them. At the moment the objects only respond to a simple protocol so the compiler has no idea how to compare them.
Your protocol needs to extend Equatable, and depending on exactly what your objects conforming to this protocol are you may need to implement the equality function.
We came across this issue after we upgraded to AFNetworking 2.6.0, which added nullability annotations, and UIWebView's extension -loadRequest:progress:success:failure: started crashing. Below is minimal example causing the crash.
Obj-C class:
NS_ASSUME_NONNULL_BEGIN
#interface Foo : NSObject
- (void) fooWithSuccess:(NSString* (^)(NSString* string))success;
#end
NS_ASSUME_NONNULL_END
#implementation Foo
- (void)fooWithSuccess:(NSString* (^)(NSString*))success
{
// EXC_BAD_ACCESS on the following line
NSLog(#"%#", success(#"Foo"));
}
#end
Calling the code above from swift (e.g. in viewDidAppear()):
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
foo.fooWithSuccess { string in
return string
}
}
However, it works without problems if Obj-c code is either not annotated or return parameter of success block is nullable:
- (void) fooWithSuccess:(NSString* __nullable (^)(NSString* string))success;
It seems to us that this behaviour is bug in Obj-c and Swift2 interoperability, and we are wondering if anyone has any insight / we are missing something.
Update
Issue is only with Swift2. When running in Swift 1.2 program behaves as expected.
Radar
Radar has been filed: rdar://22911182
I'm new to the Objective C business (Java developer most of the time) and am woking on my first killer app now. :-)
At the moment I am somehow confused about the usage of selectors as method arguments. They seem to be a little bit different than delegates in C# for example.
Given the following method signature
-(void)execute:(SEL)callback;
is there a way to enforce the signature for the selector passed to such a method?
The method is expecting a selector of a method with the following signature
-(void)foo:(NSData*)data;
But the SEL (type) is generic, so there is a good chance to pass a wrong selector to the
execute method. OK at least at runtime one would see a funny behavior... but I would like to see a compiler warning/error when this happens.
The quick answer is: no, there is no way to have the compiler enforce the method signature of a method selector that is provided via a SEL argument.
One of the strengths of Objective-C is that it is weakly-typed language, which allows for a lot more dynamic behaviour. Of course, this comes at the cost of compile-time type safety.
In order to do what (I think) you want, the best approach is to use delegates. Cocoa uses delegates to allow another class to implement "callback"-type methods. Here is how it might look:
FooController.h
#protocol FooControllerDelegate
#required:
- (void)handleData:(NSData *)data forFoo:(FooController *)foo;
#end
#interface FooController : NSObject
{
id <FooControllerDelegate> * delegate;
}
#property (assign) id <FooControllerDelegate> * delegate;
- (void)doStuff;
#end
FooController.m
#interface FooController (delegateCalls)
- (void)handleData:(NSData *)data;
#end
#implementation FooController
#synthesize delegate;
- (id)init
{
if ((self = [super init]) == nil) { return nil; }
delegate = nil;
...
return self;
}
- (void)doStuff
{
...
[self handleData:data];
}
- (void)handleData:(NSData *)data
{
if (delegate != nil)
{
[delegate handleData:data forFoo:self];
}
else
{
return;
// or throw an error
// or handle it yourself
}
}
#end
Using the #required keyword in your delegate protocol will prevent you from assigning a delegate to a FooController that does not implement the method exactly as described in the protocol. Attempting to provide a delegate that does not match the #required protocol method will result in a compiler error.
Here is how you would create a delegate class to work with the above code:
#interface MyFooHandler <FooControllerDelegate> : NSObject
{
}
- (void)handleData:(NSData *)data forFoo:(FooController *)foo;
#end
#implementation MyFooHandler
- (void)handleData:(NSData *)data forFoo:(FooController *)foo
{
// do something here
}
#end
And here is how you would use everything:
FooController * foo = [[FooController alloc] init];
MyFooHandler * fooHandler = [[MyFooHandler alloc] init];
...
[foo setDelegate:fooHandler]; // this would cause a compiler error if fooHandler
// did not implement the protocol properly
...
[foo doStuff]; // this will call the delegate method on fooHandler
...
[fooHandler release];
[foo release];
To directly answer your question, no, the SEL type allows any type of selector, not just ones with a specific signature.
You may want to consider passing an object instead of a SEL, and document that the passed object should respond to a particular message. For example:
- (void)execute:(id)object
{
// Do the execute stuff, then...
if ([object respondsToSelector:#selector(notifyOnExecute:)]) {
[object notifyOnExecute:self];
}
// You could handle the "else" case here, if desired
}
If you want to enforce the data handling, use isKindOfClass inside your selector. This works a lot like instanceof which you are familiar with in Java.