Conditional Conformance to Hashable - swift

With Xcode 10, and Swift 4.2 there are additional types that conform to Hashable as long as their elements also conform to Hashable (Array, Dictionary, etc).
I currently have some code in my project to add Hashable conformance for Swift 4.1 and below like this:
extension Array: Hashable where Element: Hashable {
public var hashValue: Int {
let prime = 31
var result = 1
for element in self {
result = prime * result + element.hashValue
}
return result
}
}
However, even if I add #if !swift(>=4.2) around the code I still see the same warning in Xcode.
My question is, how can I keep the conditional conformance to Hashable for Swift 4.1 and below, but silence the warning for Swift 4.2?

The conditional compilation statement #if swift(...) checks against the language version you're running with – which can differ from the compiler (and therefore standard library) version.
In your case, it sounds like you're using a Swift 4.2 compiler in Swift 4 compatibility mode, which gives you a language version of 4.1.50. This therefore passes the conditional compilation statement and your extension is compiled, giving you a duplicate conformance.
In order to check for a compiler version less than 4.2, you want the following:
// less than 4.2 in Swift 4 compat mode and (less than 4.2 in 3 compat mode or
// greater than 4).
#if !swift(>=4.1.50) && (!swift(>=3.4) || swift(>=4.0))
extension Array : Hashable where Element : Hashable {
public var hashValue: Int {
let prime = 31
var result = 1
for element in self {
result = prime * result + element.hashValue
}
return result
}
}
#endif
Things will be much better with the new #if compiler directive, which is available in Swift 4.2 from Xcode 10 beta 4 onwards (confirmed by #MartinR). With that, you can directly test for the compiler version, ignoring any compatibility modes it might be running in.
This doesn't help your specific case as you need the code to be understood by both Swift 4.2 and 4.1 compilers (as also pointed out by Martin), but for future compatibility problems, you could for example use #if !compiler(>=5) in order to only compile a block of code if using a 4.2 compiler.

Related

Using previewCGImageRepresentation in both Xcode 13 and Xcode 12?

I’ve run into an issue where Xcode 13b2 (iOS 15 SDK) changed the Swift return type of AVCapturePhoto.previewCGImageRepresentation(). In Xcode 12.5.1 (iOS 14 SDK), this method returns Unmanged<CGImage>. In 13b2 - 13b4, it returns CGImage?.
I need my code to compile under both Xcode versions, since Xcode 13 has other issues, and can’t be used to submit builds to the App Store. I thought I was clever writing this, but it won’t compile, because it’s not conditional code compilation check but rather a runtime check:
extension AVCapturePhoto {
func stupidOSChangePreviewCGImageRepresentation() -> CGImage? {
if #available(iOS 15, *) {
return self.previewCGImageRepresentation()
} else {
return self.previewCGImageRepresentation()?.takeUnretainedValue()
}
}
}
Another possibility might be to create a user-defined Xcode setting, but I don’t think that can be done conditionally based on Xcode or SDK version.
There might be some unsafe pointer histrionics one can do…
Any other ideas?
You can make a check on the swift compiler version. An exhaustive list is available on wikipedia at the time of writing.
https://en.wikipedia.org/wiki/Xcode#Toolchain_versions
extension AVCapturePhoto {
func stupidOSChangePreviewCGImageRepresentation() -> CGImage? {
#if compiler(>=5.5)
return self.previewCGImageRepresentation()
#else
return self.previewCGImageRepresentation()?.takeUnretainedValue()
#endif
}
}

Enum initialized with a non-existent rawValue does not fail and return nil

I have the following code in a playground (Xcode 9.0.1):
import MapKit
enum Test: UInt {
case first
case second
case third
}
let test = Test(rawValue: 4) as Any
print(test) // nil
let type = MKMapType(rawValue: 999)
print(type == nil) // false
print(type!.rawValue) // 999
MKMapType is defined as
enum MKMapType : UInt
As the maximum value of a MKMapType is 5, I expect the initializer of the enum to fail and return nil. Instead it returns 999. Am I missing some ObjC/Swift bridging here or could this be a bug?
I filed a bug with Apple and this is the reply I received:
"Engineering has determined that this issue behaves as intended based on the following information:
Because C enums may have values added in future releases, or even have "private case" values used by the framework that are not included in the headers, there's no way to check whether a value provided in Swift is actually valid or invalid. Therefore, init(rawValue:) is obliged to produce a value just as a C cast would. There are discussions in the Swift Open Source project on how to improve this situation in later versions of Swift, but the initializer for MKMapType still won't return nil."
Thanks to Apple Engineering for this explanation.

Swift 3.2 - compiler doesn't let me use the Darwin.kevent global function

This was working in Swift 3.1, however once I switched to Xcode 9 it stopped compiling. Here's a sample code:
let kq: Int32 = 0
let changelist: UnsafePointer<kevent>! = nil
let nchanges: Int32 = 0
let eventlist: UnsafeMutablePointer<kevent>! = nil
let nevents: Int32 = 0
let timeout: UnsafePointer<timespec>! = nil
Darwin.kevent(kq, changelist, nchanges, eventlist, nevents, timeout)
Error is
error: missing argument labels 'ident:filter:flags:fflags:data:udata:' in call
The problems seems to be caused by the fact that the Darwin module exports both a struct an a function with the same name - kevent, and the compiler doesn't choose the global function and instead wants me to add the struct initializer labels, which doesn't work for me as the parameters lists don't match.
The kevent struct initalizer looks like this
public init(ident: UInt, filter: Int16, flags: UInt16, fflags: UInt32, data: Int, udata: UnsafeMutableRawPointer!)
I even tried declaring all arguments as local variables (as in the sample code), in order to make sure there is no type inference that would make the function call incorrect which could cause the compiler to think that I wanted to use the struct. No luck.
Does anybody know a solution to this "overloading" issue?
I cannot tell you why the fully qualified function name does not compile with Swift 3.2, but
kevent(kq, changelist, nchanges, eventlist, nevents, timeout)
without the Darwin prefix compiles without problems (tested with Xcode 9 GM, Swift 3.2 and Swift 4).
I can confirm that
Darwin.kevent(kq, changelist, nchanges, eventlist, nevents, timeout)
compiles with Swift 3.1 (Xcode 8.3.3), so you might want to file a bug
report.

Does _ArrayType or _ArrayProtocol not available in Swift 3.1?

I was using _ArrayType in my project when I was running on swift 2.1. I upgraded to swift 3.0.2 (Xcode 8.2.1) last week and I found here that _ArrayType is changed to _ArrayProtocol and it was working well.
Today I upgraded my Xcode to 8.3.1, and it gives me error:
Use of undeclared type '_ArrayProtocol'. Here is my code:
extension _ArrayProtocol where Iterator.Element == UInt8 {
static func stringValue(_ array: [UInt8]) -> String {
return String(cString: array)
}
}
What's wrong now? Why _ArrayProtocol is undeclared in swift 3.1 while it was working in swift 3.0.2.
Also when I look here in git I see _ArrayProtocol available.
Than I looked into Swift 2.1 docs I am able to see '_ArrayType' in protocol listing but in Swift 3.0/3.1 docs I am not able to see _ArrayProtocol.
Type names starting with an underscore should always treated as internal.
In Swift 3.1, it is marked as internal in the source code and therefore
not publicly visible.
Using _ArrayProtocol was a workaround in earlier Swift versions where
you could not define an Array extension with a "same type" requirement.
This is now possible as of Swift 3.1, as described in the
Xcode 8.3 release notes:
Constrained extensions allow same-type constraints between generic parameters and concrete types. (SR-1009)
Using the internal protocol is therefore not necessary anymore,
and you can simply define
extension Array where Element == UInt8 {
}
But note that your static func stringValue() does not need any
restriction of the element type. What you perhaps intended is to
define an instance method like this:
extension Array where Element == UInt8 {
func stringValue() -> String {
return String(cString: self)
}
}
print([65, 66, 67, 0].stringValue()) // ABC
Also note that String(cString:) expects a null-terminated sequence
of UTF-8 bytes.

How can one use XCTAssertNil with optional structs?

Update 3/23/2016 I just tested my original sample code below and it all compiles fine in XCode 7.3. Looks like XCTAssertNil was updated along the way to take an expression of type () throws -> Any? Therefore this question and answer may be no longer needed (except for a while with older versions of the compiler.)
I'm writing my first unit tests in XCode with XCTest. I'm unsure how one can take advantage of XCTAssertNil as it seems to only compile when using certain types. It appears it will work with optionals made from classes and built-in primitives, but not structs. How would one go about using this method?
For structs the compiler gives the following error (assuming 'SimpleStruct' is the name of your type):
'SimpleStruct' is not identical to 'AnyObject'
Here's a simple test class to illustrate some of the types that compile okay and other's that don't.
import Cocoa
import XCTest
struct SimpleStruct {
}
class SimpleClass {
}
class Tests: XCTestCase {
func testl() {
var simpleStruct:SimpleStruct? = nil;
var simpleClass:SimpleClass? = nil;
var i:Int? = nil;
var s:String? = nil;
var tuple:(Int,String)? = nil;
XCTAssertNil(simpleStruct); // compile error
XCTAssertNil(simpleClass); // OK
XCTAssertNil(i); // OK
XCTAssertNil(s); // OK
XCTAssertNil(tuple); // compile error
}
}
Update 3/23/2016 Updated for XCode 7.3 (however if you see my edit to the question, it would appear this workaround is no longer needed)
Here is a workaround. I created my own generic function:
func AssertNil<T>(#autoclosure expression: () -> T?, message: String = "",
file: StaticString = #file, line: UInt = #line) {
if (expression() != nil) {
XCTFail(message, file:file, line:line)
}
}
Doesn't seem like this should be necessary. Is this just a result of XCTest originally targeting Objective-C and not being updated/bridged enough for Swift yet?
Edit: I've done enough research to see that AnyObject can be used to represent any class but not structs. However, that then doesn't explain why the code in my original post compiles for Int types and String types. (I read somewhere else that Xcode may auto convert these to NSNumber and NSString for you which might explain why. See http://www.scottlogic.com/blog/2014/09/24/swift-anyobject.html and http://www.drewag.me/posts/swift-s-weird-handling-of-basic-value-types-and-anyobject. I'll try removing my import of Cocoa which imports Foundation to see what happens)
Edit2: XCTest also imports Foundation so I can't test what I wanted to. I could create my own methods and test this. For now, I assume that the auto-conversions are what are allowing the int and string optionals to compile. Seems like XCTest isn't quite ready for prime time with Swift.
Update 8/13/2015: Edited the function to be compatible with XCode 7 beta