Crash when calling Obj-C property from Swift extension - objective-c

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

Related

How to use NSSet<Class> in Swift (exported as Set<NSObject>)?

I have to fulfill a protocol requirement that is defined in Objective-C like this:
#protocol AProtocol <NSObject>
+ (NSSet<Class> * _Nullable)someClasses;
#end
I want to implement this protocol in a subclass written in Swift. I want to return a Set of classes of another Object. The class I want to return is defined like this:
class B: NSObject {}
The class that conforms to the protocol is defined like this:
class A: NSObject, AProtocol {
static func someClasses() -> Set<NSObject>? {
return [B.self]
}
}
Why is NSSet<Class> bridged to Set<NSObject> instead of Set?
This solution is crashing, how can I solve the problem?
NSSet<Class> is bridged to Set<NSObject> because AnyClass does not conform to Hashable which is a precondition for the ValueType of Set.
It can be solved with the following extension for NSObjectProtocol:
extension NSObjectProtocol where Self: NSObject {
static var objcClass: NSObject {
return (self as AnyObject) as! NSObject
}
}
This returns the class of the object casted as NSObject. It is necessary to cast it first to AnyObject because the type system of Swift is so strong that it would not compile or give a warning when directly casting a type to an instance type. In Objective-C this is fine because Class is also just an object. Since NSObject is implemented in Objective-C and the extension is just for NSObjectProtocol, this is save to use (even with the force cast).
Implementing the extension on NSObjectProtocol and not on NSObject itself brings the positive effect that it is not exported to Objective-C.

Working with Swift Arrays - How to implement an Array of Objects conforming to a protocol

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.

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.

when to use respondsToSelector in objective-c

- (void)someMethod
{
if ( [delegate respondsToSelector:#selector(operationShouldProceed)] )
{
if ( [delegate operationShouldProceed] )
{
// do something appropriate
}
}
}
The documentation says:
The precaution is necessary only for optional methods in a formal protocol or methods of an informal protocol
What does it mean? If I use a formal protocol I can just use [delegate myMethod]?
You use it pretty much just when you think you need to: to check to see if an object implements the method you are about to call. Usually this is done when you have an optional methods or an informal protocol.
I've only ever used respondsToSelector when I'm writing code that must communicate with a delegate object.
if ([self.delegate respondsToSelector:#selector(engineDidStartRunning:)]) {
[self.delegate engineDidStartRunning:self];
}
You sometimes would want to use respondsToSelector on any method that returns and id or generic NSObject where you aren't sure what the class of the returned object is.
Just to add to what #kubi said, another time I use it is when a method was added to a pre-existing class in a newer version of the frameworks, but I still need to be backwards-compatible. For example:
if ([myObject respondsToSelector:#selector(doAwesomeNewThing)]) {
[myObject doAwesomeNewThing];
} else {
[self doOldWorkaroundHackWithObject:myObject];
}
As kubi mentioned respondsToSelector is normally used when you have a an instance of a method that conforms to a protocol.
// Extend from the NSObject protocol so it is safe to call `respondsToSelector`
#protocol MyProtocol <NSObject>
// #required by default
- (void) requiredMethod;
#optional
- (void)optionalMethod;
#end
Given and instance of this protocol we can safely call any required method.
id <MyProtocol> myObject = ...
[myObject requiredMethod];
However, optional methods may or may not be implemented, so you need to check at runtime.
if ([myObject respondsToSelector:#selector(optionalMethod)])
{
[myObject optionalMethod];
}
Doing this will prevent a crash with an unrecognised selector.
Also, the reason why you should declare protocols as an extension of NSObjects, i.e.
#protocol MyProtocol <NSObject>
Is because the NSObject protocol declares the respondsToSelector: selector. Otherwise XCode would think that it is unsafe to call it.
Old question, but I have learned to be very cautios with using stuff like addTarget:#selector(fu:) because the method name is not checked nor included in refactoring by XCODE. This has caused me quite some trouble already. So now I made it a habbit to always embed stuff like addTarget or addObserver in a respondsToSelector-Check like so:
if([self respondsToSelector:#selector(buttonClicked:)]){
[self.button addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
}else{
DebugLog(#"Warning - a class or delegate did not respond to selector in class %#", self);
}
I know its not super elegant, but i'd rather add some boilerplate code than have an unexpected crash of my apps in the App Store.

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.