I need to constrain API so that users can call it only with types that are explicitly optional. How can I make this happen?
class Foo<T> {
let value: T?
init(_ value: T?) {
self.value = value
}
}
let optionalValue: Bool? = true
Foo(optionalValue) // Should work
let nonOptionalValue: Bool = true
Foo(nonOptionalValue) // Should fail, ideally at compile time
Foo(true) // Should work or acceptable to replace it with
Foo(.some(true))
The problem is that if you pass a non-Optional where an Optional is expected, the non-Optional is not rejected; instead, it is implicitly wrapped in an Optional. That is why this line of code is legal:
let optionalValue: Bool? = true
And for the same reason, this line of code is legal:
let nonOptionalValue: Bool = true
Foo(nonOptionalValue) // even though Foo's parameter is typed as Optional
You cannot turn off this feature. It is baked into the language. And indeed, as the first example shows, you yourself have come to rely upon it! Thus, for that very reason, you can never prevent a non-Optional from being passed here. That's the price we pay for the convenience of implicit Optional wrapping.
One option can be to declare init with inout property.
init(_ value: inout T?) {
self.value = value
}
Now below line gives compiler error "Inout argument could be set to a value with a type other than 'Bool'; use a value declared as type 'Bool?' instead"
Foo(&nonOptionalValue) // Should fail, ideally at compile time
Constraints:
User of class Foo may not want to pass by reference.
User will not be able to write Foo(true)
But, these can be worked around by creating a local var before calling the init.
Related
I am trying to call the method .enumerate() on an instance of a type which conforms to the protocol Sequence. According to Apple's documentation, this should be correct, since .enumerate is part of the Sequence protocol.
However, I receive this complaint from the compiler:
Member 'enumerated' cannot be used on value of type 'any Sequence<URL>'; consider using a generic constraint instead.
Yet, if I remove the type annotation, then it works.
Here is an example which reproduces the problem:
func example() -> URL? {
let fm : FileManager = FileManager.default
let appDir : FileManager.SearchPathDirectory = FileManager.SearchPathDirectory.applicationDirectory
let domMask : FileManager.SearchPathDomainMask = FileManager.SearchPathDomainMask.allDomainsMask
let appResourceValues : [URLResourceKey] = [URLResourceKey.localizedNameKey]
var appURLs : any Sequence<URL> = fm.urls(for: appDir, in: domMask)
//var appURLs : Sequence<URL> = fm.urls(for: appDir, in: domMask)
//var appURLs = fm.urls(for: appDir, in: domMask)
var appURL : URL? = appURLs.enumerated().first { (offset: Int, element: URL) in
try! element.resourceValues(forKeys: Set(appResourceValues)).localizedName!.contains("App Store")
}?.element
return appURL
}
There are two commented lines in the code above, which are alternate ways to instantiate appURLs. If I use the first commented line, which is the old Swift syntax apparently, then I receive an error telling me that in order to add a type annotation which enforces a protocol, I need to use any protocolName, and not protocolName. (According to a comment on another post, this was a recent change in Swift: Use of protocol 'YourProtocol' as a type must be written 'any YourProtocol' Error, https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md)
If I use the second commented line, which removes the protocol annotation altogether, then the code works.
Is this a bug in Swift? How can I apply an annotation to indicate that it must conform to Sequence<URL> without breaking the code?
I tried to declare a generic type parameter, but Swift won't let me. None of these work:
associatedtype does exactly what I want: it creates a generic type parameter. But it doesn't work outside a protocol.
If you annotate appURLs with the existential type any Sequence<URL>, then that means that you don't know what concrete type it actually stores. This is problematic for calling enumerated, because enumerated returns EnumeratedSequence<Self>:
func enumerated() -> EnumeratedSequence<Self>
Self means "type on which this is called" - the exact thing that you don't know. Sometimes, having an unknown Self is fine. e.g. if methods with these signatures existed in Sequence:
func f() -> Self
func g() -> (Self, Int)
func h(p: (Self) -> Void)
func i() -> [Self]
func j() -> [Int: Self]
func k() -> Self?
All of these are covariant positions. It remains type safe to substitute Self in these positions with any Sequence<URL>, which is why you can still call these methods. However, it is not safe to do the same with EnumeratedSequence<Self>, because even though SomeConcreteImplementationOfSequence is a any Sequence<URL>, EnumeratedSequence<SomeConcreteImplementationOfSequence> is not a EnumeratedSequence<any Sequence<URL>>. Generics are invariant in Swift - the Self in EnumeratedSequence<Self> is in an invariant position.
You can see they talk about when functions involving Self can be called in this SE proposal:
[...] but references to Self-rooted associated types will for the
same reasons some Self references do today. As alluded to back in
Inconsistent Language Semantics, references to covariant Self
are already getting automatically replaced with the base object type,
permitting usage of Self-returning methods on existential values [...]
This way, a protocol or protocol extension member
(method/property/subscript/initializer) may be used on an existential
value unless:
The type of the invoked member (accessor — for storage declarations),
as viewed in context of the base type, contains references to Self
or Self-rooted associated types in non-covariant position. [...]
They even use enumerated() as an example further down!
extension Sequence {
public func enumerated() -> EnumeratedSequence<Self> {
return EnumeratedSequence(_base: self)
}
}
func printEnumerated(s: Sequence) {
// error: member 'enumerated' cannot be used on value of type protocol type 'Sequence'
// because it references 'Self' in invariant position; use a conformance constraint
// instead. [fix-it: printEnumerated(s: Sequence) -> printEnumerated<S: Sequence>(s: S)]
for (index, element) in s.enumerated() {
print("\(index) : \(element)")
}
}
Besides, EnumeratedSequence<any Sequence<URL>> isn't even a valid type! EnumeratedSequence requires its type parameter to be a Sequence, but any Sequence<URL> isn't one! Because Sequence has static requirements.
Responding to your comments,
It is bad practice to type hint something as [URL] when you only intend to use the qualities encapsulated by Sequence protocol
That is not bad practice. Rather, putting type annotations where they are not needed is considered not Swifty.
Example: I may want to use a method which compiles a enumerable list of URLs, but the way that these URLs are fetched will depend on runtime parameters (e.g., do I have internet access? Is an external drive currently mounted?). Depending on these parameters, it may be more efficient (or only be possible) to acquire the list of URLs as [URL] or as any other type which conforms to Sequence<URL>. In that case, the return type of such a function will be anything which conforms to Sequence<URL>
In that case, your function can return an AnySequence<URL>. Unlike any Sequence<URL>, this is a concrete type. You just need to do the extra step of wrapping other sequence types to it:
func fetchSomeURLs() -> AnySequence<URL> {
if someCondition {
return AnySequence([url1, url2]) // [URL]
} else {
return AnySequence(someOtherImplementationOfSequence)
}
}
I have concluded that the Swift compiler isn't sophisticated enough to check conformity of a variable to a protocol. (There are some limited cases where it will work.)
A work-around in this case is the following:
extension Sequence {
func enumerated_custom() -> any Sequence<(offset:Int, element:Iterator.Element)> {
var index : Int = -1
return self.lazy.map({ (el:Iterator.Element) in
index = index+1 ; return (index+1, el)
})
}
}
Then, we can do appURLs.enumerated_custom() to get a Sequence<(Int, URL)> which mimics the behaviour of appURLs.enumerated(). I can then use the annotation appURLs : Sequence<URL> in order to check for conformity, and the code does not break.
Related informational links:
https://belkadan.com/blog/2021/10/Swift-Regret-Generic-Parameters-are-not-Members/
https://github.com/apple/swift/pull/39492
https://github.com/apple/swift-evolution/blob/main/proposals/0309-unlock-existential-types-for-all-protocols.md
https://github.com/apple/swift/pull/41131
I faced strange error when usign propertyWrapper feature. Sample code below:
#propertyWrapper
struct Wrapper<Value> {
private var parameter: Int?
var wrappedValue: Value
init(wrappedValue: Value, parameter: Int? = 0) {
self.wrappedValue = wrappedValue
self.parameter = parameter
}
}
class WrapperTest {
#Wrapper var valueImplicitNil: Double? // OK
#Wrapper var valueExplicitNil: Double? = nil // OK
//#Wrapper(parameter: 1) var valueWithParamImplicitNil: Double? // NOK (Missing argument for parameter 'wrappedValue' in call)
#Wrapper(parameter: 1) var valueWithParamExplicitNil: Double? = nil // OK
}
For valueImplicitNil, Swift automatically assigns nil to it, and then propertyWrapper initializer automatically assigns nil to first init parameter wrappedValue - both behaviours clearly described in Swift documentation.
For some reason it doesn't do the same for valueWithParamImplicitNil variable. Any ideas, why is it so?
EDIT: As advised, I reported an issue SR-14411 in bugs.swift.org, and am using workaround proposed below.
Here property wrapper is a struct, therefore all its properties must be initialized when an instance is created. In your example, no value is given, so the compiler emits an error.
Your property wrapper struct takes a non-optional wrappedValue - it can’t be initialised without a value. If you don’t provide one you get a compiler error as you can see.
If you want a property wrapper that doesn’t require an initial wrapper value then you need to make wrappedValue optional.
As others have said in the comments, this looks like a compiler bug. Basically, the compiler should be able to fill in the first parameter for the initializer (wrappedValue) in the second case also, since clearly it can handle the default value for nil properties if no arguments are passed to the property declaration.
So, if #Wrapper var valueImplicitNil: Double? gets expanded by the compiler to
private var _valueImplicitNil = Wrapper<Double?>(wrappedValue: nil)
var valueImplicitNil: Double? {
get { return _valueImplicitNil.wrappedValue }
set { _valueImplicitNil.wrappedValue = newValue }
}
, so it should be the case for #Wrapper(parameter: 1) var valueWithParamImplicitNil: Double?, which should expand to:
private var _valueImplicitNil = Wrapper<Double?>(wrappedValue: nil, parameter: 1)
var valueImplicitNil: Double? {
get { return _valueImplicitNil.wrappedValue }
set { _valueImplicitNil.wrappedValue = newValue }
}
A workaround until this gets fixed in the compiler is to add another initializer that has only the second argument:
init<V>(parameter: Int? = 0) where Value == V? {
self.init(wrappedValue: nil, parameter: parameter)
}
At least this is the approach AppStorage took, so it seems that the Apple engineers ran into the same problem, but found a way to work around this limitation.
You can copy this playground verbatim:
var closures=[() -> Void]()
class Thing {
let name: String
init(_ name: String) { self.name=name }
}
class RefThingWrapper {
let thing: Thing
init(_ t: Thing) {
self.thing=t
closures.append { [thing, weak self] in // Even though `thing` captured strongly, `self` could still get deallocated.
print((self == nil) ? "`self` deallocated" : "`self` not deallocated")
print(thing.name) // Decided to use `thing` within closure to eliminate any chance of optimizations affecting the test results — which I found when I captured `self` strongly without using it.
}
}
}
struct ValThingWrapper {
let thing: Thing
init(_ t: Thing) {
self.thing=t
closures.append { [weak thing] in
print((thing == nil) ? "`thing` deallocated" : "`thing` not deallocated")
}
}
}
var wrapper=Optional(RefThingWrapper(Thing("Thing 1"))) // Test with reference type.
//var wrapper=Optional(ValThingWrapper(Thing("Thing 1"))) // Test with value type.
closures[0]()
wrapper=nil
closures[0]()
It demonstrates how a property of self — whether self is a reference or value type — can be captured within a closure independently of self. Running the program as is demonstrates a captured property existing after self has been deallocated. Testing with the value type wrapper demonstrates that, if weakly captured, the instance will be deallocated once the referencing value instance is deallocated.
I wasn't sure this was possible because when creating the closure at first, I forgot to initialize the property I was capturing. So the compiler complained — in the capture list — 'self' used before all stored properties are initialized. So I figured self was being captured implicitly, and only after digging deeper discovered otherwise.
Is this documented somewhere? I found this post by Joe Groff where he proposes:
For 'let' properties of classes, it'd be reasonable to propose having
closures capture the property directly by default in this way
instead of capturing 'self' (and possibly allowing referencing them
without 'self.', since 'self' wouldn't be involved in any cycle formed
this way).
This was back in 2015, and I didn't find any implemented proposals that arose from the discussion. Is there any authoritative source that communicates this behavior?
If you’re just asking for documentation on capture lists and reference types, see The Swift Programming Language Resolving Strong Reference Cycles for Closures. Also see the Language Reference: Capture Lists
If your capture list includes value type, you’re getting copy of the value.
var foo = 1
let closure = { [foo] in
print(foo)
}
foo = 42
closure() // 1; capturing value of `foo` as it was when the closure was declared
If your capture list includes a reference type, you’re getting a copy of the reference to that current instance.
class Bar {
var value: Int
init(value: Int) { self.value = value }
}
var bar = Bar(value: 1)
let closure = { [bar] in
print(bar.value)
}
bar.value = 2
bar = Bar(value: 3)
closure() // 2; capturing reference to first instance that was subsequently updated
These captured references are strong by default, but can optionally be marked as weak or unowned, as needed, too.
That Capture Lists document outlines the way that you can capture a property without capturing self:
You can also bind an arbitrary expression to a named value in a capture list. The expression is evaluated when the closure is created, and the value is captured with the specified strength. For example:
// Weak capture of "self.parent" as "parent"
myFunction { [weak parent = self.parent] in print(parent!.title) }
I’m not crazy about their code sample, but it illustrates the capture of a property without capturing self, nonetheless.
In messing around with Swift today I came across a strange thing. Here's the unit test I developed which shows some unexpected behaviours when using Swift's AnyObject.
class SwiftLanguageTests: XCTestCase {
class TestClass {
var name:String?
var xyz:String?
}
func testAccessingPropertiesOfAnyObjectInstancesReturnsNils() {
let instance = TestClass()
instance.xyz = "xyz"
instance.name = "name"
let typeAnyObject = instance as AnyObject
// Correct: Won't compile because 'xyz' is an unknown property in any class.
XCTAssertEqual("xyz", typeAnyObject.xyz)
// Unexpected: Will compile because 'name' is a property of NSException
// Strange: But returns a nil when accessed.
XCTAssertEqual("name", typeAnyObject.name)
}
}
This code is a simplification of some other code where there is a Swift function that can only return a AnyObject.
As expected, after creating an instance of TestClass, casting it to AnyObject and setting another variable, accessing the property xyz won't compile because AnyObject does not have such a property.
But surprisingly a property called name is accepted by the compiler because there is a property by that name on NSException. It appears that Swift is quite happy to accept any property name as long as it exists somewhere in the runtime.
The next unexpected behaviour and the thing that got all this started is that attempting to access the name property returns a nil. Watching the various variables in the debugger, I can see that typeAnyObject is pointing at the original TestClass instance and it's name property has a value of "name".
Swift doesn't throw an error when accessing typeAnyObject.name so I would expect it to find and return "name". But instead I get nil.
I would be interested if anyone can shed some light on what is going on here?
My main concern is that I would expect Swift to either throw an error when accessing a property that does not exist on AnyObject, or find and return the correct value. Currently neither is happening.
Similar as in Objective-C, where you can send arbitrary messages to id,
arbitrary properties and methods can be called on an instance of AnyObject
in Swift. The details are different however, and it is documented in
Interacting with Objective-C APIs
in the "Using Swift with Cocoa and Objective-C" book.
Swift includes an AnyObject type that represents some kind of object. This is similar to Objective-C’s id type. Swift imports id as AnyObject, which allows you to write type-safe Swift code while maintaining the flexibility of an untyped object.
...
You can call any Objective-C method and access any property on an AnyObject value without casting to a more specific class type. This includes Objective-C compatible methods and properties marked with the #objc attribute.
...
When you call a method on a value of AnyObject type, that method call behaves like an implicitly unwrapped optional. You can use the same optional chaining syntax you would use for optional methods in protocols to optionally invoke a method on AnyObject.
Here is an example:
func tryToGetTimeInterval(obj : AnyObject) {
let ti = obj.timeIntervalSinceReferenceDate // NSTimeInterval!
if let theTi = ti {
print(theTi)
} else {
print("does not respond to `timeIntervalSinceReferenceDate`")
}
}
tryToGetTimeInterval(NSDate(timeIntervalSinceReferenceDate: 1234))
// 1234.0
tryToGetTimeInterval(NSString(string: "abc"))
// does not respond to `timeIntervalSinceReferenceDate`
obj.timeIntervalSinceReferenceDate is an implicitly unwrapped optional
and nil if the object does not have that property.
Here an example for checking and calling a method:
func tryToGetFirstCharacter(obj : AnyObject) {
let fc = obj.characterAtIndex // ((Int) -> unichar)!
if let theFc = fc {
print(theFc(0))
} else {
print("does not respond to `characterAtIndex`")
}
}
tryToGetFirstCharacter(NSDate(timeIntervalSinceReferenceDate: 1234))
// does not respond to `characterAtIndex`
tryToGetFirstCharacter(NSString(string: "abc"))
// 97
obj.characterAtIndex is an implicitly unwrapped optional closure. That code
can be simplified using optional chaining:
func tryToGetFirstCharacter(obj : AnyObject) {
if let c = obj.characterAtIndex?(0) {
print(c)
} else {
print("does not respond to `characterAtIndex`")
}
}
In your case, TestClass does not have any #objc properties.
let xyz = typeAnyObject.xyz // error: value of type 'AnyObject' has no member 'xyz'
does not compile because the xyz property is unknown to the compiler.
let name = typeAnyObject.name // String!
does compile because – as you noticed – NSException has a name property.
The value however is nil because TestClass does not have an
Objective-C compatible name method. As above, you should use optional
binding to safely unwrap the value (or test against nil).
If your class is derived from NSObject
class TestClass : NSObject {
var name : String?
var xyz : String?
}
then
let xyz = typeAnyObject.xyz // String?!
does compile. (Alternatively, mark the class or the properties with #objc.)
But now
let name = typeAnyObject.name // error: Ambigous use of `name`
does not compile anymore. The reason is that both TestClass and NSException
have a name property, but with different types (String? vs String),
so the type of that expression is ambiguous. This ambiguity can only be
resolved by (optionally) casting the AnyObject back to TestClass:
if let name = (typeAnyObject as? TestClass)?.name {
print(name)
}
Conclusion:
You can call any method/property on an instance of AnyObject if that
method/property is Objective-C compatible.
You have to test the implicitly unwrapped optional against nil or
use optional binding to check that the instance actually has that
method/property.
Ambiguities arise if more than one class has (Objective-C) compatible
methods with the same name but different types.
In particular because of the last point, I would try to avoid this
mechanism if possible, and optionally cast to a known class instead
(as in the last example).
it has nothing with NSException!
from Apple documentation:
protocol AnyObject { ... }
The protocol to which all classes implicitly conform.
When used as a concrete type, all known #objc methods and properties are available, as implicitly-unwrapped-optional methods and properties respectively, on each instance of AnyObject
name is #objc property, xyz is not.
try this :-)
let typeAnyObject = instance as Any
or
#objc class TestClass: NSObject {
var name:String?
var xyz:String? }
let instance = TestClass() instance.xyz = "xyz" instance.name = "name"
let typeAnyObject = instance as AnyObject
typeAnyObject.name // will not compile now
Is it possible to have an Optional inout parameter to a function in Swift? I am trying to do this:
func testFunc( inout optionalParam: MyClass? ) {
if optionalParam {
...
}
}
...but when I try to call it and pass nil, it is giving me a strange compile error:
Type 'inout MyClass?' does not conform to protocol 'NilLiteralConvertible'
I don't see why my class should have to conform to some special protocol when it's already declared as an optional.
It won't compile because the function expecting a reference but you passed nil. The problem have nothing to do with optional.
By declaring parameter with inout means that you will assign some value to it inside the function body. How can it assign value to nil?
You need to call it like
var a : MyClass? = nil
testFunc(&a) // value of a can be changed inside the function
If you know C++, this is C++ version of your code without optional
struct MyClass {};
void testFunc(MyClass &p) {}
int main () { testFunc(nullptr); }
and you have this error message
main.cpp:6:6: note: candidate function not viable: no known conversion from 'nullptr_t' to 'MyClass &' for 1st argument
which is kind of equivalent to the on you got (but easier to understand)
Actually what #devios1 needs is "optional pointer".
But inout MyClass? means "pointer to an optional".
The following should work in Swift 4
class MyClass {
// func foo() {}
}
func testFunc(_ optionalParam: UnsafeMutablePointer<MyClass>? ) {
if let optionalParam = optionalParam {
// optionalParam.pointee.foo()
// optionalParam.pointee = MyClass()
}
}
testFunc(nil)
var myClass = MyClass()
testFunc(&myClass)
This is possible, and it may have everything to do with Closure.()
The optional can have a nil value, but it is not expecting nil. So the optional would work if you simply did not pass it to the function or passed a variable of type MyClass? with value nil. Like Bryan states.
When you write the function it has to have a default value to be an optional param like so:
func testFunc(inout optionalParam:MyClass?={var nilRef:MyClass?;return &nilRef}()) {
if optionalParam != nil {
...
}
}
Notice if optionalParam {...} is changed to if optionalParam != nil {...}
you can NOT uwwrap optionalParam without checking it,i.e.if optionalParam? != nil, as uwrapping, optionalParam? fails when optionalParam==nil. ~note, var a :MyClass? has a value of nil until it is assigned.
Call is either, testFunc(optionalParam:&a) or testFunc(), but never testFunc(nil)
Think you may be getting two separate concepts intertwined as they have similar names, optional parameters and optionals. Optionals are variables with an extended nil condition. Optional parameters are function parameters which are optional.
Further reading here. Apple is trying to change the verbiage from optional parameter to 'parameters with default values'. It is unfortunate they didn't incorporate optional parameter behavior within these new optional thingies. What is the point of passing nil optionals as optionals if they will fail when unwrapped. Maybe it gets deeper at the heart of what this optional thing is if it can be passed before unwrapping... Schrodinger's cat
The exact passage from the docs is:
You can only pass a variable as the argument for an in-out parameter.
You cannot pass a constant or a literal value as the argument, because
constants and literals cannot be modified. You place an ampersand (&)
directly before a variable’s name when you pass it as an argument to
an inout parameter, to indicate that it can be modified by the
function.
Note however, that (edited) per #gnasher's comment, you only need to pass a class as inout (aka by reference), if you intend or want to allow for the instance to be / being replaced by another instance, and not just have the original modified. The passage in the documentation that covers this is:
In-Out Parameters
Variable parameters, as described above, can only be changed within
the function itself. If you want a function to modify a parameter’s
value, and you want those changes to persist after the function call
has ended, define that parameter as an in-out parameter instead.
Here are three tests that cover the usage of var and inout.
class Bar : Printable {
var value = 1
init(_ value:Int) { self.value = value }
var description:String { return "Bar is: \(value)" }
}
let bar = Bar(1)
func changeBarWithoutInoutSinceBarIsAClassYaySwift(b:Bar) { b.value = 2 }
changeBarWithoutInoutSinceBarIsAClassYaySwift(bar)
println("after: \(bar)") // 2
var bar2 = Bar(0)
func tryToReplaceLocalBarWithoutInout(var b:Bar) { b = Bar(99) }
tryToReplaceLocalBarWithoutInout(bar2)
println("after: \(bar2)") // 0
var bar3 = Bar(0)
func replaceClassInstanceViaInout(inout b:Bar) { b = Bar(99) }
replaceClassInstanceViaInout(&bar3)
println("after: \(bar3)") // 99