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

Reply via email to