Proposal: transform Obj-C methods that return values by reference into tuple returns

Agh, that subject header was a mouthful. The code examples should make this proposal pretty clear, though. Basically: Swift's system of getting rid of NSError ** parameters and turning them into something easier to deal with is great. I'd like to propose doing something similar with reference parameters; if a reference parameter is marked "out" in the header, thus guaranteeing that it is only for returning a value and that its initial value doesn't matter, we can eliminate the parameter and move it to the actual return value. If the Obj-C method already has a return value, we can make it a tuple. So, something like this (assuming nonnull):


- (NSString *)foo:(out NSString **)bar;


becomes something like this:


func foo() -> (String, String)


The Obj-C return value, if there is one, would always be the first element in the tuple, accessible via .0. For methods using the common Obj-C naming conventions for methods that return values by reference, the other elements in the tuple could be named. In this case, "get<name>" would remain the name of the method, but additional by-reference parameters and their names could be removed from the method name completely and moved to the return tuple. In both cases the argument label could be used to determine the name, like so:


- (void)getFoo:(out NSString **)foo bar:(out NSString **)bar;
- (NSString *)fooWithBar:(NSString *)bar baz:(out NSString **)baz;
- (NSString *)fooAndReturnBar:(out NSString **)bar;


become:


func getFoo() -> (foo: String, bar: String)
func fooWithBar(bar: String) -> (String, baz: String)
func foo() -> (String, bar: String)


Methods that have void returns (or which have Boolean returns and an error parameter, which Swift will turn into a void return) don't even need a tuple:


- (void)foo:(out NSString **)bar;


becomes


func foo() -> String


Furthermore, something like -[NSURL getResourceValue:forKey:error:], once the "out" keyword is added to its declaration, becomes this:


func getResourceValueForKey() throws -> AnyObject?


so that instead of this rather Byzantine-looking construction:


var sizeObj: AnyObject? = nil

try url.getResourceValue(&sizeObj, forKey: NSURLFileSizeKey)

if size = sizeObj as? NSNumber {
   // do something with size
}


you could just do this:


if let size = try url.getResourceValueForKey(NSURLFileSizeKey) as? NSNumber {
    // do something with size
}


So much cleaner, and generally more "swifty"!


The beauty of it all is that we don't even have to invent a new keyword for this, since Obj-C already has an "out" keyword (which was originally there for use with Distributed Objects, but I see no reason we couldn't repurpose it here). We could even wrap the call in an autoreleasepool to get rid of the autorelease on the returned-by-reference values, if the performance trade-off is deemed to be worth it.


You may say, what about methods that can take NULL as the reference parameter, and skip doing the work to generate that value if they get NULL as a parameter? Won't this proposed change make such methods inefficient in cases where you don't want one of the values? Well, assuming the pointer is declared as nullable, you could just do this:


let (foo, bar: _) = someMethod()


or:


let (foo, _) = someMethod()


and, seeing that a particular return value is not needed, Swift would pass NULL to the Objective-C method. (If the pointer were non-nullable, Swift would send it an actual pointer and simply ignore the result).


What think you?

No love for this idea, then?

I think it looks interesting/nice, but I've just skimmed through it. It would be interesting to see any argument against it.

I like it, but haven't particularly thought through the consequences. I suppose there's a lot of cases where the call won't read very well because arguments and their labels are yanked to the front, though.

Objective-C has naming conventions that are used in a lot of cases with by-reference parameters (getFoo:, fooAndReturnBar:, etc.), so I'd expect a lot of them to translate fairly naturally (C APIs, of course, don't name anything at all, so they're going to be equally unclear before and after). If there are cases that end up reading badly, I suppose we could do something like extend the "out" keyword with a parameter that allows it to be given a different name in Swift, like so:


- (NSString *)foo:(out(bar) NSString **)bar;

to:

func foo() -> (String, bar: String)


We should see what kinds of badly-reading constructions we can find in the Cocoa API first, though, before deciding whether additions like this are necessary. Do you have any examples?

No, I haven't put any effort into checking out parameters in the Cocoa API.

I think the construct you're trying to eliminate is both fairly rare and likely inconsistent in the Cocoa APIs. The former makes it a lot of trouble for a small gain, and the latter makes manual auditing necessary, case by case.


Under these circumstances, I think it would be simpler to just augment the Cocoa APIs with new "convenience" APIs that translate into Swift to produce the method forms you prefer. That means that Obj-C needs a new tuple-ish return type (perhaps a struct packaged as NS_TUPLE, kinda like Swift-compatible enums are packaged as NS_ENUM or NS_OPTIONS), or that the original SDK header files go cross-language and permit the embedding of Swift method definitions using Swift syntax (that are ignored by the Obj-C compiler).


I prefer the second of these ideas, because it's an open-ended architecture for improving Swift access to the SDK in the future. Who knows what API bee will get into your bonnet next?? 😉

I think the construct you're trying to eliminate is both fairly rare

I made a (quick-and-dirty, ugly) Perl script to try to detect this construct in the SDK (http://pastebin.com/gB2e2F3d if you're interested), and it outputted something 1,055 lines long. There are going to be some false positives in there (and probably some false negatives as well, since my script doesn't check for every possible pointer type), but still—1055 lines.

and likely inconsistent in the Cocoa APIs.

I'd expect that most of them would be fairly consistent, conforming either to "-getFoo:" or "-doSomethingAndReturnFoo:" if it's the first parameter, or just "foo:" if it's one of the others. This is the way most of the APIs that I've personally used have constructed it, IIRC. You are free to run my script above and see if you can find inconsistencies; I'd be interested to know the result.

the latter makes manual auditing necessary, case by case.

That's true, but so did the nullability and lightweight generic annotations, and both of those (especially nullability) were far more difficult to audit than this. A parameter that is returning something by reference is often pretty obvious just from looking at the header. The auditing for methods that follow conventions such as "-getFoo:" could even be partially automated. In addition, due to the pre-existing nature of the "out" keyword, some APIs are audited already (-[NSURL getResourceValue:forKey:error:] turns out to be one of these, in fact; it looks like the "out" keyword was added to this API in 10.9).


edit: Why is this still "currently being moderated" almost 24 hours after I posted it? I don't think I used any profanity. What's going on?

I don't think I used any profanity. What's going on?

The issue is not profanity but links. DevForums has a whitelist of sites that you’re allowed to link to; anything else requires moderator approval.

We’re working to make this run smoother but for the moment that’s the way things are.

Share and Enjoy

Quinn "The Eskimo!"
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

1055 seems like a very small number to me, compared to the number of declarations in the complete SDK.


Your response suggests you think that I think that manual auditing would somehow be too much work. Actually, I think the opposite, that it doesn't sound like much work at all. My argument is that the effort would be better spent constructing desirable APIs directly.


I'm OK with your suggested outcome. I just don't think your suggested mechanism is bold enough. Or, for that matter, that you need to suggest a mechanism at all. "Oh, look, it would be so easy to do" isn't the sales pitch. "Returning values via reference parameters is an ugly thing to do in Swift" is the sales pitch.

1055 seems like a very small number to me, compared to the number of declarations in the complete SDK.

If you actually run the script and look at the list of APIs with an eye for quality rather than quantity, though, you'll see that many of them are very frequently used APIs. -[NSURL getResourceValue:forKey:error:], for example, is a method I use pretty much all the time (and hating it every time). The -[NSAttributedString initWithSomething:documentAttributes:] methods are another pretty common set of APIs. Just about every API in NSScanner uses this construction. NSInputStream uses it. One of the methods you have to override to make an NSFormatter subclass uses it. The NSURL methods dealing with bookmarks use it. Lots of methods in NSFileManager and NSWorkspace dealing with files use it, and absolutely anything related to text layout is just chock full of it.


Plus, my Perl script isn't even an exhaustive scan. You'd find even more if you searched for things like custom structs and CFTypes, for example; mine mainly looks for objects and primitive integer types. Plus, mine scans Obj-C methods only, and not C functions—searching for C functions would increase the number by quite a lot, since an astonishing number of CF functions that return something do it by reference.


Your response suggests you think that I think that manual auditing would somehow be too much work. Actually, I think the opposite, that it doesn't sound like much work at all.

Last time, you said "The former makes it a lot of trouble for a small gain". Glad to see that you agree with me now. :-P


"Oh, look, it would be so easy to do" isn't the sales pitch. "Returning values via reference parameters is an ugly thing to do in Swift" is the sales pitch.

That's the sales pitch I already gave in the OP, though. Look at those examples, especially the NSURL one, and tell me they're not far less ugly with my proposed change.


Actually, I'd argue that returning values via reference parameters is an ugly thing to do in Objective-C. The main difference is that we didn't really have a choice back then, because Obj-C had no other way for a method to return multiple values (well, and that it's even uglier in Swift).

I like the syntax, but I worry of the debugging headache when I want to know the selector name of the original method. Swift's do-catch defines the bridging pattern well so we can somehow infer that

func getValueForKey(:) throws -> AnyObject


has selector

"getValueForKey:error:"


but for the proposed tuple-ization of "out" arguments, the method loses it's argument names. One way to work around would be to require the labels in the tuple

- (NSString *)fooWithBar:(NSString *)bar baz:(out NSString **)baz;


func fooWithBar(bar: String) -> (baz: String)


but still I feel there's an edge case we'll get bitten by somewhere.


EDIT:

Apologies, you did propose that as well! Sorry, I didn't see your examples.

If there are any edge cases, please feel free to point them out! It would be interesting to see what people come up with, and if possible, I could adjust the proposal as necessary.

Proposal: transform Obj-C methods that return values by reference into tuple returns
 
 
Q