"if let" syntax for legacy objective-c properties.

17 views
Skip to first unread message

Samuel Williams

unread,
Sep 18, 2015, 11:23:05 AM9/18/15
to Swift Language
Hey everyone.

I'm working on a "legacy" objective-c codebase. It has code like this:

- (void)resetSheet:(id)sender {

if ([sender respondsToSelector:@selector(property)]) {

PMProperty * selectedProperty = [sender performSelector:@selector(property)];

if (selectedProperty) {

self.tenure.property = (PMProperty *)[self.managedObjectContext objectWithID:selectedProperty.objectID];

}

}

}


I'm trying to write something equivalent in Swift with the same incoming arguments. I'm writing this:

override func resetSheet(sender: AnyObject!) {

if let selectedProperty = sender.property {

self.propertyTransaction?.property = self.managedObjectContext.objectWithID(selectedProperty.objectID) as? PMProperty

}

}


I was under the impression that the `if let` syntax would check whether the condition was nil or not. But, when I run this code, if `sender.property` is nil, `selectedProperty` is nil and the if condition is executed branch is executed anyway. I assumed that the `if` statement wouldn't be executed if `selectedProperty` was nil. What am I doing wrong or mis-understanding? In this example, sender is always a valid object, and the type of sender.property is PMProperty!

Thanks
Samuel

Jens Alfke

unread,
Sep 18, 2015, 5:33:19 PM9/18/15
to Samuel Williams, Swift Language

On Sep 18, 2015, at 3:28 AM, Samuel Williams <space.ship...@gmail.com> wrote:

I was under the impression that the `if let` syntax would check whether the condition was nil or not. But, when I run this code, if `sender.property` is nil, `selectedProperty` is nil and the if condition is executed branch is executed anyway.

I’m not 100% sure, but I think the problem is that you’re using “!” not “?”. While both of those result in storing the value as an optional, the “!” syntax makes it behave syntactically like a non-optional — that is, it will be dereferenced automatically. So I am guessing that “if let” with a “!” variable always succeeds, since with “!” you’re telling Swift “this can never be null”.

—Jens

PS: Also, I notice the snippet you posted is not legal Swift code, since it's accessing a property of  AnyObject, which has no properties defined. I assume you simplified it before posting?

Brent Royal-Gordon

unread,
Sep 18, 2015, 8:36:00 PM9/18/15
to Samuel Williams, Swift Language
This:

> - (void)resetSheet:(id)sender {
> if ([sender respondsToSelector:@selector(property)]) {
> PMProperty * selectedProperty = [sender performSelector:@selector(property)];
>
> if (selectedProperty) {
> self.tenure.property = (PMProperty *)[self.managedObjectContext objectWithID:selectedProperty.objectID];
> }
> }
> }

Has very different semantics from this:

> override func resetSheet(sender: AnyObject!) {
> if let selectedProperty = sender.property {
> self.propertyTransaction?.property = self.managedObjectContext.objectWithID(selectedProperty.objectID) as? PMProperty
> }
> }

The Objective-C code tests if sender has a property (really, a method) named “property”, and if it does, calls it and does something with the value if it’s not nil. The Swift code just calls “property”, *assuming* that sender is not nil and has a “property” property, and tests if the value is nil.

A direct translation of the Objective-C might look more like:

override func resetSheet(sender: AnyObject?) {
if let selectedProperty = sender?.property? {
self.propertyTransaction?.property = self.managedObjectContext.objectWithID(selectedProperty.objectID) as? PMProperty
}
}

The question mark after “sender" indicates that “sender" might be nil, and the one after “property” indicates that “sender” might not have a “property” property. However, this doesn't actually compile in my playground—Swift doesn’t allow typing this loose.

You’re much better off giving “sender” a specific type:

override func resetSheet(sender: PMPropertyController?) {
if let selectedProperty = sender?.property {
self.propertyTransaction?.property = self.managedObjectContext.objectWithID(selectedProperty.objectID) as? PMProperty
}
}

Or if you can’t guarantee the type of “sender” (or you can’t change the AnyObject in your superclass for some reason), use a type check:

override func resetSheet(sender: AnyObject?) {
if let propertyController = sender as? PMPropertyController, selectedProperty = propertyController.property {
self.propertyTransaction?.property = self.managedObjectContext.objectWithID(selectedProperty.objectID) as? PMProperty
}
}

If “sender” can be one of several unrelated types, all of which have a “property” property, declare a PropertyHoldingType protocol that requires the “property” property and make all of the valid types conform to it. Then you can either make resetSheet(_:) take an instance of PropertyHoldingType, or do an “if let”/“as?” conditional cast to PropertyHoldingType.

HTH,
--
Brent Royal-Gordon
Architechies

Reply all
Reply to author
Forward
0 new messages