James,
We did consider an availability model with feature detection similar to
the one you proposed. This is essentially the model that Objective-C has.
We’ve found that there are significant drawbacks to that model and believe
that version checks + compile-time checking offer a better user experience.
* Compile-time checking of availability makes version checking less
fragile.
A key component of Swift’s availability model is that — unlike in
Objective-C or JavaScript — any use of an API element is verified for
availability by the compiler. This eliminates the main source of fragility
in the version checking model: getting a version check wrong and calling
the API anyway. The compiler makes the strong guarantee that it will detect
this and inform the developer that they have made a mistake.
* Feature detection checks are hard to reason about and test.
With version checks it is is immediately obvious on what environment the
code will execute. This makes it easy to reason about, for example, how to
test the given class/method/branch. In contrast, with feature detection
even determining when a given branch of an availability check will execute
requires a trip to the documentation. A polyfill model would make this even
more challenging because the feature could be available earlier than the
documentation states!
* SPI and direct feature detection.
Apple sometimes adds features first as SPI and only later makes them
available as API, potentially changing behavior when doing so. This means
that a naive check for the presence of, say, a class or method, does not
necessarily make it safe to call. For example, Apple added UINib as API in
iOS 4, but earlier versions used an incompatible class with the same name.
In this case, the Objective-C idiom checking for the feature effectively
“lied” about UINib’s availability. When it comes to API, the run-time
checks for #available need to allow symbols to be present but still not
considered available. Version checks (either OS versions or library
versions) are one mechanism to do this.
* Dead check detection
One nice aspect of version checks is that it gives the compiler the
ability to detect availability checks that are no longer needed because the
application is being deployed only on versions of the OS on which the check
would always succeed. This is not possible with a direct feature detection
model alone (it still needs APIs to be annotated with their versions) and
would help prevent older codebases from being littered with feature checks
falling back to effectively dead code that no one knows whether it is safe
to remove or not.
* Overloading makes it verbose to check for a feature.
Swift’s support for function and method overloads makes it quite verbose
to check for existence of a feature. Unambiguously identifying a method in
a #available check would require specifying its parameter types and even
constraints on generic type parameters.
* Features are often correlated.
In Objective-C codebases we find that developers often make implicit
assumption about OS versions even when using direct feature checks. For
example, a developer will often check for the existence of one method and
then perform an unguarded use of another method that was introduced in the
same version. With a direct feature detection model, the developer would
have to check for each of these features separately — even though they know
they were introduced in the same OS version! The fact that Objective-C
developers are already skipping checks for correlated features shows that
their mental model is already based on versions, so it makes sense to match
that model in the affordances exposed by the language.
All that said, polyfill is a widely used technique and it would be
interesting to see what it would take to *safely* integrate into Swift. In
my view, the two key challenges there are (1) compatibility (whose polyfill
wins when two libraries try to polyfill the same thing?) and (2) how to
make assume-guarantee @available() annotations with polyfill/feature
detection safe at compile time and efficient without leaking implementation
details to clients (we don’t want every function to have to list all the
functions it calls [and all the functions they call, etc.] in its
@available() annotation!).
Devin
On Feb 2, 2016, at 2:03 AM, James Campbell via swift-evolution < > swift-evolution@swift.org> wrote:
Coming from a web background (before my iOS career) to me #avaliable has
huge problem. It encourages fragility.
In my eyes we should encourage two types of detection: Features to make
code more adaptable to different environments and language version
detection: so we can understand the actual code.
See this example below:
func magic(object: Object)
{
if(#avaliable(9.0, 10))
{
object.foo()
}
}
Ideally for me I would love to check if the foo function exists like so:
func iOS9OnlyProtocolFunction(object: Object)
{
if(#avaliable(Object.foo))
{
object.foo()
}
else
{
object.baz()
}
}
I think this encourages feature detection which results in less fragile
code. What I would love to do is also to extend this to extensions so we
could encourage polyfills.
extend object where not_avaliable(Object.foo)
{
func foo()
{
//Polyfill for platforms which don't support the Object.foo method
}
}
Not sure about compiler details but being able to polyfill the function
results in much cleaner code for me. I love this approach from the web, so
I created my own Objective-C Library to do this:
GitHub - jcampbell05/Polly: Polyfills for iOS
*___________________________________*
*James⎥Lead Engineer*
*james@supmenow.com <james@supmenow.com>⎥supmenow.com
<http://supmenow.com/>\*
*Sup*
*Runway East *
*10 Finsbury Square*
*London*
* EC2A 1AF *
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution