This is a re-post of a proposal I previously submitted to Radar. This seems
like a more appropriate place for it, though, and since Apple seems to be
planning a heuristic mechanism to rewrite Objective-C APIs to make them more
idiomatic to Swift
(https://github.com/apple/swift-evolution/blob/master/proposals/0005-objective-c-name-translation.md
<https://github.com/apple/swift-evolution/blob/master/proposals/0005-objective-c-name-translation.md>),
I thought I’d bounce this by the list.
Motivation:
Swift's system of getting rid of NSError ** parameters and turning them into
something easier to deal with is great. However, NSError is not the only
possible type of reference parameter that can appear in C and Objective-C APIs.
Some extremely common APIs in the frameworks require passing C-style pointers,
including the popular -[NSURL getResourceValue:forKey:error:]. These reference
pointers are currently exposed as ugly UnsafeMutablePointer or
AutoreleasingUnsafeMutablePointer constructs that seem quite out of place in a
Swift public API.
Proposed Solution:
If an Objective-C method takes a reference parameter, and that parameter is
marked "out" in the declaration, this guarantees that the parameter is purely
for returning a value, and that its initial value will be ignored. Thus, we can
eliminate the parameter entirely and move it to the method's return value. If
the Objective-C method already has a return value, we can accommodate both
return values by having the method return 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:] 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). Many APIs, such as -[NSURL getResourceValue:forKey:error:] mentioned
above, already use it. 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.
One possible objection could be raised regarding methods that can take NULL as
the reference parameter, and skip doing the work to generate that value in this
case; one could argue that the proposed change could make such methods
inefficient in cases where you don't want one of the values. However, assuming
the parameter is nullable, we could account for this as well by assigning one
of the return values to _, like this:
let (foo, bar: _) = someMethod()
or:
let (foo, _) = someMethod()
and, seeing that a particular return value is not needed, Swift could pass NULL
for the undesired reference parameter. (If the pointer were non-nullable, Swift
would send it an actual pointer and simply ignore the result).
Impact on Existing Code:
The impact is similar to the existing, accepted Objective-C translation
proposal. It will break lots of existing use of Objective-C code, but since
that is happening soon anyway, this seems like an appropriate time to consider
other things such as this.
Further Discussion:
The proposal above discussed mainly Objective-C code; however, the logical next
step may be to extend it to C as well. This would dramatically simplify the
CoreFoundation interface as well, since that API relies extremely heavily on
reference parameters to return things, with the actual return type often simply
being a Boolean or an OSStatus. This would probably be more complicated,
however, since you wouldn’t necessarily have the advantage of the naming
conventions that exist in Objective-C for this type of API.
Charles
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution