Cast object ( UIViewController ) to their unknown subclass - objective-c

I have developed a generic controller for sliding UIView and I manage UIViewController (and obviously subclass ) and must call a specific method (freeze) only if a subclass of UIViewController respond to this method:
-(void)freezeRootViewController
{
if([_rootViewController respondsToSelector:#selector(freeze)])
[ ((id) _rootViewController) freeze];
}
I don't know the class of _rootViewController but I know it is a subclass of UIViewController, for this reason I have tried to cast my _rootViewController to ID, but I can't compile:
Environment: XCode 4.5.1, iOs 4.3+ with ARC
Error: "No know instance method for selector 'freeze'"
Note: I CAN'T force the developer to use a specific UIViewController subclass for the _rootViewController.

After checking if a "generic" type responds to a selector you invoke it by using performSelector:/performSelector:withObject: if it is a simple 0-1 parameter method, otherwise use an NSInvocation. Since freeze has no arguments you would just use performSelector:.
-(void)freezeRootViewController
{
if([_rootViewController respondsToSelector:#selector(freeze)])
[_rootViewController performSelector:#selector(freeze)];
}

This seems to be a warning rather than an error - you should be able to compile your code and just ignore this diagnostic message. However, if you want to get rid of it completely, you can declare a protocol and do the cast as follows:
#protocol MyProtocol <NSObject>
- (void)freeze;
#end
[(id <MyProtocol>)_rootViewController freeze];

Related

Crash when calling Obj-C property from Swift extension

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).

What is the correct way of accessing a Swift Delegate from Objective-C?

Environment: Xcode 6.1.1 & Xcode 6.2 Beta
Greetings:
I need to publish a NSString within a Swift doc from a neighboring Objective-C doc within the same project. For example, display "Hello World" generated in Objective-C upon a Swift page. I've made a proof-of-concept demo; based on feedback.
I'm thinking of using an ObjC --> Swift delegate via a protocol pattern as shown below:
Note: the Swift file is the delegate.
Here I'm calling the delegate method in Swift, from Objective-C:
#pragma mark - Action methods
- (IBAction)sendDelegateAction:(UIButton *)sender {
[_delegate radiusString:#"Hello World"];
}
I've instantiated the Objective-C file to link the delegate to the instance (I hope I got it right):
let geo32Controller = MyObjCTableViewController()
geo32Controller.delegate = self
So far, the compiler complained that the Swift protocol couldn't be found.
Here's the protocol (declared in Swift):
#objc protocol DiscoveryContributeProtocol {
// optional
func radiusString(radiusString:String)
}
And here's the delegate reference to that protocol in the Objective-C header file:
#interface MyObjCTableViewController : UIViewController<UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, weak) id<DiscoveryContributeProtocol> delegate;
#end
However, the compiler can't find the protocol:
BTW: when I put the bridge reference in the ObjC's header file, I get a compiler error:
Two Questions:
Do I have the correct pattern (did I instantiate the ObjC correctly) ?
How do I make the Objective-C portion see the Swift protocol for the delegate link?
You have the right idea, but have a few bugs that are preventing this from working.
You've declared Geo32Boundaries as conforming to the DiscoveryContributeProtocol, but it doesn't need to and doesn't actually implement it, it only has a property that conforms to that protocol. That's the source of the "Method 'radiusString:' not implemented" error:
#interface Geo32Boundaries: UIViewController // <-- that's all you need
You're setting the delegate incorrectly -- the code you have there looks like it's trying to set a class instance of Geo32Boundaries to self, but you're also trying to call it like a function. You'll need to set the delegate on a the instance of the Geo32Boundaries view controller that is being presented to the user. I don't know where that code lives, so I can't give a great example, but it'll be something like:
geo32Controller.delegate = self
Lastly, though not a bug, your protocol should really be called DiscoveryContributeDelegate -- we usually don't use "protocol" in the protocol name.

Implementing method without conforming protocol

In my TheTabBarController, without conforming to UINavigationControllerDelegate protocol, i could assign my class to moreNavigationController.delegate.
// without conforming to protocol, <UINavigationControllerDelegate>
#interface TheTabBarController : UITabBarController
self.moreNavigationController.delegate = self;
It just raises the following warning but compiles successfully.
Assigning to 'id' from incompatible
type 'TheTabBarController *const __strong'
The protocol's method is called at run-time without any error. I use it to hide more navigation bar for some view controllers.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
So, i want to know, is this legal and safe or; will it crash later or leak memory? How could this be allowed semantically? How could run-time resolves the method although its not defined in the interface and the protocol is not conformed? Or, UITabBarController uses a hidden category that conforms to the protocol?
Protocols have no runtime meaning. They are only used during compilation to display type errors when you try to do things like you are doing now. Why would you not want TheTabBarController to be a UINavigationControllerDelegate if it implements the protocol?
In Objective-C, you can call any method on any object and it might handle it by implementing forwardInvocation:(NSInvocation *)anInvocation or one of the related methods. You can also add new methods to an object or class at runtime using objc_install_instance_method and related functions.

How to call a delegate's function without getting the "instance method not found" warning in ios?

In the apps I worked on, I often found such lines of code
[delegate aFunction];
that generated the "instance method "aFunction" not found (return type defaults to id)" warning
Now, I did a bit of research on SO and found out that the warning can be removed by declaring the function for cases when you call it on self ([self aFunction];), but none of the answers said anything about my case, when I use a delegate.
So, long story short, what can I do to correctly call a delegate's method inside another class?
Things appear to work fine, so this is not a major issue, but a warning means I'm not doing something completely correct so I would like to learn what's the best practice for such cases
Thank you for your help in advance!
So, if I'm understanding you correctly, your issues can be taken away by declaring your protocol as follows:
#class SomeClass;
#protocol SomeClassDelegate <NSObject>
#required
- (void)thisObjectDidSomething:(SomeClass*)instance;
#optional
- (void)thisObjectDidSomethingUnimportant:(SomeClass*)instance;
#end
Then your delegate ivar and property look like this (use assign instead of weak if you're not using ARC):
#interface SomeClass () {
__weak id<SomeClassDelegate> delegate_;
}
#property (weak) id<SomeClassDelegate> delegate;
And in the .h file of any class that's going to implement that protocol, do this:
#interface TCReader : NSObject <SomeClassDelegate> {
}
Since it's safe to call selectors on nil, for required methods, you can just:
[self.delegate thisObjectDidSomething:self]
But for optional methods, you'd better:
if ([self.delegate respondsToSelector:#selector(thisObjectDidSomethingUnimportant:)]) {
[self.delegate thisObjectDidSomethingUnimportant:self]
}
The main point here is that by declaring and making use of a protocol, you let XCode know that those methods are defined for objects implementing the protocol. If you require that your delegate implement that protocol, then Xcode knows that your delegate has those methods defined.

Cast an instance of a class to a #protocol in Objective-C

I have an object (a UIViewController) which may or may not conform to a protocol I've defined.
I know I can determine if the object conforms to the protocol, then safely call the method:
if([self.myViewController conformsToProtocol:#protocol(MyProtocol)]) {
[self.myViewController protocolMethod]; // <-- warning here
}
However, XCode shows a warning:
warning 'UIViewController' may not respond to '-protocolMethod'
What's the right way to prevent this warning? I can't seem to cast self.myViewController as a MyProtocol class.
The correct way to do this is to do:
if ([self.myViewController conformsToProtocol:#protocol(MyProtocol)])
{
UIViewController <MyProtocol> *vc = (UIViewController <MyProtocol> *) self.myViewController;
[vc protocolMethod];
}
The UIViewController <MyProtocol> * type-cast translates to "vc is a UIViewController object that conforms to MyProtocol", whereas using id <MyProtocol> translates to "vc is an object of an unknown class that conforms to MyProtocol".
This way the compiler will give you proper type checking on vc - the compiler will only give you a warning if any method that's not declared on either UIViewController or <MyProtocol> is called. id should only be used in the situation if you don't know the class/type of the object being cast.
You can cast it like this:
if([self.myViewController conformsToProtocol:#protocol(MyProtocol)])
{
id<MyProtocol> p = (id<MyProtocol>)self.myViewController;
[p protocolMethod];
}
This threw me for a bit, too. In Objective-C, the protocol isn't the type itself, so you need to specify id (or some other type, such as NSObject) along with the protocol that you want.