Note: this is no longer relevant. Recent versions of Swift have multiple features that address enum binary compatibility in various ways, such as #unknown default, frozen enums, etc.
Various enums in HealthKit tend to get new values added with each release of iOS. For example, HKWorkoutActivityType has had new values added in each iOS version since its introduction.
Say I am mapping this enum to a string value using a Swift switch statement:
extension HKWorkoutActivityType {
var displayName: String {
switch self {
case .americanFootball: return "American Football"
// ...Exhaustive switch statement, with a line for every enum case.
// Including values added in iOS 10 and 11...
case .taiChi: return "Tai Chi"
}
}
}
let event: HKWorkoutEvent = ...
print("Activity type is: \(event.type.displayName)")
This switch statement, compiled against the iOS 11 SDK, works fine and is backward compatible with older iOS versions. Note that at the time of compilation, the switch statement is exhaustive, so there is no default case.
But if new HKWorkoutActivityType values are added in iOS 12, and I don't recompile this code, how will the displayName getter behave for new enum values? Should I expect a crash? Undefined behavior? Does it depend on the type of enum (for example, here it's an Objective-C NS_ENUM, but will Swift enums behave differently)? etc.
FWIW, this is the partially what this Swift Evolution proposal is addressing.
Hopefully they will decide on a solution that satisfies issues like this nicely too!
Long story short, you may be able to avoid this issue by adding a default case, (even though the compiler will yell at you), or using version tags. However this problem likely falls under "undefined" currently.
The long story:
The current version of Swift does not have ABI stability, so a compiled Swift application is not guaranteed to (and almost definitely wont) interface with a Framework compiled with a newer version (the reason the platform Frameworks are still Objective-C).
So how this category of changes affect Swift is a work in progress. We will probably have a better definition of how to deal with this type of issue when Swift 5 is released. until then adding default and/or version checking is probably the way to go.
Very interesting question, and upvoted. I know of no way to perfectly test this in (a) Xcode 9 and (b) iOS 11. But that may be your answer.
I think the desired solution if if #available(iOS 12, *), where though is at issue. Encapsulate the entire switch statement? Just the iOS 12 addition?
The result should be that between the target iOS version in Xcode and the Swift compiler, it's covered - and should yield an error (hopefully explaining the issue that iOS 11 is targeted but something is only available in iOS 12) to indicate you either need to use (a) the if #available(iOS 12, *) someplace or change your target.
I know of no easy way to test this though, without rebuilding. Which is integral to your question! Therefore I guess the rule is:
Always rebuild your app when a new iOS (and associated Xcode) version is released.
Consider this part of you taking ownership of your code.
Related
Just started learning Swift, am really confused about the following behaviour.
This is what I get when I run String.contains without Foundation:
"".contains("") // true
"a".contains("") // true
"a".contains("a") // true
"" == "" // true
And this is what I get with Foundation:
import Foundation
"".contains("") // false
"a".contains("") // false
"a".contains("a") // true
"" == "" // true
Why are the results different depending on whether I import Foundation? Are there other such differences, and is there an exhaustive list somewhere? Didn't find anything in the Foundation documentation, but this seems important to document. I'm only aware of this other example.
Also: How does this happen and is it normal? I understand that Swift has stuff like extensions that change the behaviour of every instance of something once they're included, but surely that should only add behaviour, not change existing behaviour. And if existing behaviour is changed, shouldn't the language indicate this somehow, like make me use a different type if I want the different behaviour?
Basically this is the same as the question I answer here.
Foundation is not part of Swift, it's part of Cocoa, the older Objective-C library that preceded Swift by many, many years. Foundation's version of a string is NSString. But Swift String is "bridged" to NSString, so as soon as you import Foundation, a bunch of NSString methods spring to life as if they were part of Swift String even though they are not. In your case, you actually end up calling a completely different method which, as you've discovered, gives different results.
A good way to see this is to command-click on the term contains in your code (or even better, option-click it and then click Open in Developer Documentation):
If you have not imported Foundation (or UIKit), you jump to Swift String's contains.
If you have imported Foundation, you jump to Foundation's contains.
As for this part:
shouldn't the language indicate this somehow
I'm afraid Stack Overflow is not very good on "should" questions. I would say, yes, this is a little maddening, but it's part of the price we pay for the easy and seamless integration of Swift into the world of Cocoa programming. You could argue that the Swift people should not have named their method contains, but that train has left the station, and besides, it's a name that perfectly indicates what the method does.
Another thing to keep in mind is that you would probably never really use Swift except in the presence of Foundation (perhaps because you're in the presence of UIKit or SwiftUI or AppKit) so in practical terms the issue wouldn't arise. You've hit an unusual edge-case, which is commendable but, ex hypothesi, unusual.
To make things even more complicated, I think the Swift library method you encountered may have been just introduced as part of Xcode 14 and Swift 5.7 etc. See https://developer.apple.com/videos/play/wwdc2022/110354/?time=1415 for the WWDC '22 discussion of new String features. In earlier versions of Xcode, the phrase "a".contains("") would not even have compiled in the absence of Foundation — and so the problem would never have arisen!
One of the worst offenders is NSAccessibilityProtocol, for example.
The protocol declares lots of members (26 functions)
Its conformed to by some of the most common object types (e.g. NSView, NSWindow) conform to it.
NSObject even has these methods! (Even though it doesn't conform to NSAccessibilityProtocol explicitly, for some reason)
The function names contain common words like "frame", "style", "range", "size", "button", "menu", etc., so they come up really frequently as false positives (because of to fuzzy searching)
Most (all?) of the members are deprecated
Is it possible to filter out these members from code completion? When I do need them, I would rather just look them up in the API docs manually, if it meant that they wouldn't disturb me the other 95% of the time.
Here are some similar questions on the topic, none of which are up to date and high quality:
How can I exclude certain terms from Xcode's code completion? (auto complete, content assist, suggest was to use AppCode, obviously not exactly applicable
Is there a way to improve Xcode's code completion?, old (2014), suggestion is to use a plugin (Xcode discontinued its support for plugins)
Is there a way to disable or modify Xcode's code completion?, applies to an old Xcode version. /Applications/Xcode.app/Contents/PlugIns/TextMacros.xctxtmacro doesn't exist on my machine (Xcode Version 12.4 (12D4e))
Codable got introduce in Swift 4. My project language version is Swift 3.3 but still I am able to use Codable in my project. This is not the issue but how is it possible? I just want to know.
The Swift compiler version and "Swift Language Version" build setting (corresponding to the -swift-version command-line flag) are two different things. The former is the actual version of compiler and standard library you're using, whereas the latter is just a flag to tell the compiler to attempt to mimic the behaviour of previous Swift versions. This allows for a smoother migration experience – you can update to the latest Swift compiler without having to immediately update your codebase to accommodate for the latest changes in the language.
In your case, it sounds like you're using a Swift 4.1 compiler with the "Swift Language Version" set to Swift 3.3. This is known as "Swift 3 compatibility mode". Note that the version Swift 3.3 is a pseudo version – it exists only to represent the Swift 4.1 compiler running in Swift 3 compatibility mode.
Here's a handy table (info taken from SE-0212) that maps compiler to language version per compatibility mode:
(note that now SE-0212 is implemented, this version bumping for compatibility modes will no longer take place for Swift 5 and above)
So this means that you're using the Swift 4.1 standard library (which includes the new Codable protocol) and compiling with the Swift 4.1 compiler (which includes the necessary compiler magic for Codable synthesis). This is why you're still able to take advantage of the new Codable protocol.
However by running in Swift 3 compatibility mode, you're instructing the compiler to mimic the behaviour of a Swift 3 compiler. This will, for example, cause it to allow you to access declarations marked #available(swift, obsoleted: 4), prevent you from accessing declarations marked #available(swift, introduced: 4), ignore code within a #if swift(>=4) conditional compilation block, and otherwise do its best to preserve source compatibility with Swift 3.
The latter it doesn't always achieve perfectly, for example this compiles with a Swift 3.1 compiler:
protocol P {}
typealias X = protocol<P, AnyObject>
class C : X {}
but it doesn't compile with a Swift 4.1 compiler running in Swift 3 compatibility mode (SR-8153).
If the Decodable and Encodable protocols were marked as #available(swift, introduced: 4), then you indeed wouldn't be able to access them in Swift 3 compatibility mode. But there's no real reason to mark them as such, as there's no real reason to prevent people from taking advantage of them in Swift 3 mode, as they're fully supported by the Swift 4 compiler.
However because Swift 3 compatibility mode is just that – a temporary mode for source compatibility, it's not something you'll be able to use forever with future compiler versions. It will no longer be an option in the Swift 5 compiler (you will have a compatibility mode for Swift 4, however). So you need to make sure you update your codebase at some point to Swift 4 in order to be able to have a smooth migration to the Swift 5 compiler.
Finally, it's worth noting that a new compiler directive (that can be used in a conditional compilation block) is being introduced in Swift 4.2. Unlike #if swift(...) which checks against the language version as supplied by -swift-version, #if compiler(...) will check against the actual version of the compiler, ignoring any compatibility mode it might be running in.
I think may be you are using latest Swift compiler.
What has replaced the method toUIntMax() and the method toIntMax() in Swift 4 ? The error occurred within the FacebookCore framework.
Any help would be appreciated
The concept of IntMax has been completely removed as part of SE-104.
Converting from one integer type to another is performed using the concept of the 'maximum width integer' (see MaxInt), which is an artificial limitation. The very existence of MaxInt makes it unclear what to do should someone implement Int256, for example.
The proposed model eliminates the 'largest integer type' concept previously used to interoperate between integer types (see toIntMax in the current model) and instead provides access to machine words. It also introduces the multipliedFullWidth(by:), dividingFullWidth(_:), and quotientAndRemainder methods. Together these changes can be used to provide an efficient implementation of bignums that would be hard to achieve otherwise.
In this specific case FB SDK should simply use the UInt64($0) initializer which is now available for any BinaryInteger type thanks to the new protocols.
You can also for now, can select Swift 3.2 under Pods -> Targets -> ObjectMapper -> Swift language version option
Is it possible to concoct a compile time assert in Swift like static_assert in C++? Maybe some way to exploit type constraints on generics to force a compiler break?
This has been accepted into Swift as of version 4.2, here is the Swift evolution for the proposal.
If you're talking about a general assert, where the app will crash if a given condition fails, just use: assert(condition,message)
For example: assert(2 == 3,"failing because 2 does not equal 3")
This is possible in Swift. However, I should note that Apple's design mantra is that an app should never crash, but instead should handle all its errors in a "sophisticated" fashion.