#propertyWrapper for Optional type: wrappedValue initialization error for automatically assigned nil value - swift

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.

Related

Constraining method or class to accept only optionals

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.

Extending Optional for all Types except one

In the interest of efficient coding, I was wondering if there is a way to write an extension on Optional for all wrapper types With a declared exception.
In this particular instance, that would be String.
My desired use-case would be as follows:
extension Optional where Wrapped != String {
var asString: String? { return self as? String }
}
I'm getting a whole bunch of compilation errors here, where of course this is not possible.
I want to be able to do this so that I don't see the asString property on String? objects, as there is some redundancy there, of course.
Note: this should be able to be applied to any valid type that Wrapped can take.
extension Optional {
var asString: String? {
guard let unwrapped = self else {
return nil
}
return "\(unwrapped)"
}
}
This solution is only allowed by the language
You can't exclude some classes or protocols in where block.
Example,
let some: Range? = 0..<1
some.asString
"0..<1"

Swift, Core Data, Optional Integer16 and keyPath

I have an entity in CoreData which has an optional property of type Integer 16. It can genuinely be nil and in my application I want to refer to it as an Int? type. As Int? (or for that matter Int16?) isn't a recognised Objective-C type, the compiler throws a bit of a wobbly. I want to avoid using code like NSNumber?.intValue throughout so I've actually set up my ManagedObject type with custom accessors for this property. My question relates to identifying the property through #keyPath rather than a static string. In Core Data the field is named 'pin' on entity 'User'. Here's the code I have:
class User: NSManagedObject {
// MARK: - Properties
static let pinKey = "pin"
#NSManaged internal(set) var name: String
#NSManaged fileprivate var primitivePin: NSNumber?
internal(set) var pin: Int? {
get {
willAccessValue(forKey: #keyPath(pin)) // THIS LINE ERRORS
let value: Int? = primitivePin.map { $0.intValue }
didAccessValue(forKey: User.pinKey)
return value
}
set {
willChangeValue(forKey: User.pinKey)
primitivePin = newValue.map { NSNumber(value: Int16($0)) }
didChangeValue(forKey: User.pinKey)
}
}
}
The line in error is what I 'want' to achieve but of course the var pin isn't an obj-c type and the compiler complains, so I have defined the static constant pinKey as you can see. #keyPath feels like the right way to go about it, and the entity does have a field called pin, but in this scenario is the only option open to me to use a static value?
In #keyPath you have to specify property name. If you don't have defined property called pin, you will receive an error. In your case you have to use #keyPath(User.primitivePin). I believe this should work.
Also, i guess, calling map is redundant here. You can write directly let value = primitivePin?.intValue and so on.
The answer is....with custom properties/accessors #keyPath can't used as there is no defined #NSManaged property for it - as Maksym points out. However, you can't use the defined primitive for it either, instead using the property name as a String as also shown in the code above (User.pinKey)

Swift protocol extensions for Value(Structures) types

public struct KZErrorInfo: Unboxable {
var statusCode = -1
var status: String?
var errorMessage: String?
public init() {
}
public init(unboxer: Unboxer) {
self.statusCode = unboxer.unbox("StatusCode")
self.status = unboxer.unbox("Status")
self.errorMessage = unboxer.unbox("Message")
}
}
protocol KZClientResponse: ETClientResponse {
var errorInfo: KZErrorInfo? { get set }
}
var errorInfo: KZErrorInfo? {
get {
if let value = objc_getAssociatedObject(self, &xoAssociationKeyErrorInfo) as? KZErrorInfo {
return value
}
return nil
}
set(newValue) {
if let error = newValue {
objc_setAssociatedObject(self, &xoAssociationKeyErrorInfo, error, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
}
My objective is to have a default implantation for the protocol KZClientResponse and Xcode is giving me a compile error as below. In the case of value types, how to overcome this issue? Appreciate you suggestions.
As the error message is indicating, objc_getAssociatedObject(_:_:) and objc_setAssociatedObject(_:_:_:_:) require AnyClass as the first argument. You cannot use Swift structs as AnyClass.
Think another way to store errorInfo which works with structs.
Why don't you have it as the struct's property?
... giving me a compile error as below. In the case of value types, how to overcome this issue?
You can't overcome the compiler error. You're trying to mix apples with oranges. objc_getAssociatedObject is, by definition, Objective-C. But Objective-C knows nothing of Swift structs; it cannot possibly see them. The only thing it knows about are what it calls objects — that is, classes and their instances. To work with a Swift struct, you cannot use the Objective-C runtime at all: you must operate entirely within Swift itself.

Optional chaining and Array in swift

Let's take these two simple classes to show my problem:
class Object{
var name:String?
// keep it simple... and useless
}
class TestClass {
var objects:AnyObject[]?
func initializeObjects (){
objects?.insert(Object(), atIndex:0) // Error
objects?.insert(Object(), atIndex:1) // Error
objects?.insert(Object(), atIndex:2) // Error
}
}
With this implementation I get 3 errors Could not find member 'insert' where I try to add object into the objects array.
Now, if I remove the optional from objects definition and the optional chain in initializeObjects it works with no problem (here the working code)
class Object{
var name:String?
}
class TestClass {
var objects:AnyObject[] = AnyObject[]() // REMOVE optional and initialize an empty array
func initializeObjects (){
objects.insert(Object(), atIndex:0) // Remove Opt chaining
objects.insert(Object(), atIndex:1) // Remove Opt chaining
objects.insert(Object(), atIndex:2) // Remove Opt chaining
}
}
I can't understand what is wrong in the first implementation.
I thought it checks with objects? if objects is not nil and at this point it adds an element using insert:atIndex:. But I'm probably wrong -.-
Arrays in Swift are structs and structs are value types.
Optionals in Swift are actually enums (Optional<T> or ImplicitlyUnwrappedOptional<T>).
When you are unwrapping an optional (implicitly or explicitly) of a value type, what you get is actually a constant copy of the struct. And you can't call mutating methods on a constant struct.
Executing objects?.insert(Object(), atIndex:0) basically means this:
if let tmp = objects {
tmp.insert(Object(), atIndex:0)
}
As a workaround, you need to assign the unwrapped value to a variable and then assign the variable back to your optional property. That's how value types work.
This is reproducible for any struct, not only Arrays:
struct S {
var value: Int = 0
}
var varS: S = S()
varS.value = 10 //can be called
let constS: S = S()
constS.value = 10 //cannot be called - constant!
var optionalS: S? = S()
optionalS?.value = 10 //cannot be called, unwrapping makes a constant copy!
//workaround
if optionalS {
var tmpS = optionalS!
tmpS.value = 10
optionalS = tmpS
}
Some relevant discussion here: https://devforums.apple.com/thread/233111?tstart=60