PropertyWrapper - Global is external, but doesn't have external or weak linkage - swift

I'm working with Property wrappers in a project that is divided into few Swift package modules dependent on each others. I have been using my own property wrappers in the code, but I want to switch to Combine, as it contains a lot of similar functionalities to my own property wrappers. In the process of converting I often occur the compiler issue:
Abort trap: 6 with error message: Global is external, but doesn't have external or weak linkage!.
As the first step I have decided to get rid of this error while still using my own property wrappers, because it can occur even there, but I managed to get rid of it. But in not a clean way, and I would like to get more knowledge on what is going on, so I could proceed with Combine later - with the same errors.
Ok, so to get rid of the Abort trap: 6 with my old property wrappers I needed to switch the way I used this property in some of modules. Instead of writing and reading it directly, I access it with $property.wrappedValue. Then it works, but this is very ugly in code, and kinda denies the puropse of using a property wrapper. Could someone explain me why this error occurs? In some of the modules Im able to use it directly with no problem. I have no idea what is going on, and what I can do to resolve this. I tried to convert a lot of similar properties with Combine, and I just get more of this errors, and actually I was not even able to resolve them like this.
Please tell me what is this error about, why it happens and what I can do to resolve it.
If his helps, this is how this property wrapper is defined:
#propertyWrapper
public class ObservableChangedValue<Value: Equatable>: Observable<Value> {
public var projectedValue: ObservableChangedValue { self }
public override var wrappedValue: Value {
get { super.wrappedValue }
set { super.wrappedValue = newValue }
}
override func shouldExecuteNotifications(oldValue: Value, newValue: Value) -> Bool {
oldValue != newValue
}
}
public class Observable<Value> {
public init(wrappedValue: Value) {
self.wrappedValue = wrappedValue
}
internal var observations = [Observation<Value>]() { didSet { isBeingObserved = !observations.isEmpty } }
public var wrappedValue: Value {
didSet {
guard shouldExecuteNotifications(oldValue: oldValue, newValue: wrappedValue) else { return }
observations.forEach {
$0.executeNotification(oldValue: oldValue, newValue: wrappedValue)
}
}
}
public weak var delegate: ObservableDelegate?
public var isBeingObserved = false {
didSet {
guard isBeingObserved != oldValue else { return }
delegate?.isBeingObservedChanged(isBeingObserved)
}
}
internal func shouldExecuteNotifications(oldValue: Value, newValue: Value) -> Bool { true }
}
One big thing - I have noticed that the error only occurs when I access this variable from other file than where it was defined. I added getters and setters for now, but still not a nice solution. This seems to be a compiler error, but Im not sure.

Using property wrappers in extensions from different file seems to be
causing this
see what they claims in documentation
Property wrapper types
A property wrapper type is a type that can be used as a property wrapper. There are two basic requirements for a property wrapper type:
The property wrapper type must be defined with the attribute
#propertyWrapper. The attribute indicates that the type is meant to
be used as a property wrapper type, and provides a point at which
the compiler can verify any other consistency rules.
The property wrapper type must have a property named wrappedValue,
whose access level is the same as that of the type itself. This is
the property used by the compiler to access the underlying value on
the wrapper instance.

Related

How to pass a class object to a function but prevent mutation?

I can't see where in the Swift language is the facility to pass a class object to a function yet prevent that function from mutating the object by either calling functions that will implicitly mutate it or setting public variables. I'm gathering that this facility just does not exist, can anyone confirm?
That is to say, all objects are always mutable everywhere they can be seen.
This is extremely common throughout Cocoa. You create an immutable class and a mutable subclass. For examples, see AVComposition/AVMutableComposition, CBService/CBMutableService, CNContact/CNMutableContact.
In ObjC, this is common practice with collections as well (arrays, dictionaries, etc), but since those are value types in Swift, there's no need to use the classes (NSArray/NSMutableArray).
In Swift, rather than creating two classes, you create an immutable protocol and a class:
protocol Person: AnyObject {
var name: String { get }
var address: String { get }
}
class MutablePerson: Person {
var name: String = ""
var address: String = ""
}
Now, any function that accept Person will have an immutable object, and any function that accepts MutablePerson will be able to mutate it. This is a general pattern you can use to give different parts of your program access to different slices of the object's API. It's much more general and flexible than just const.
That said, this is not as common a pattern in Swift as it is in ObjC, since in most cases where this is useful, the type should be a struct anyway. But it is absolutely available if needed.
To your question about doing this with two classes, as in ObjC, it's possible, as long as you define both in the same file. It's just a bit tedious:
public class Person {
public fileprivate(set) var name: String = ""
public fileprivate(set) var address: String = ""
}
public class MutablePerson: Person {
public override var name: String {
get { super.name }
set { super.name = newValue }
}
public override var address: String {
get { super.address }
set { super.address = newValue }
}
}
It's possible a property wrapper could improve this, but I haven't been able to figure out how.
There's no way I can think of to allow usage of methods, but properties are no problem**. Just use an Immutable as a function parameter.
final class Class {
var property = true
}
var object = Immutable(Class())
object.property = false // Cannot assign to property: 'object' is immutable
/// An affordance for accessing the properties of an object
/// without the ability to mutate them.
#dynamicMemberLookup
public struct Immutable<Object: AnyObject> {
private let object: Object
}
// MARK: - public
public extension Immutable {
init(_ object: Object) {
self.object = object
}
subscript<Value>(dynamicMember keyPath: KeyPath<Object, Value>) -> Value {
object[keyPath: keyPath]
}
}
** The getters could be mutating, and they could return mutating closures. 😜 But that's an issue with the protocol approach as well. The best that we can do right now is a generally accurate hack.
What you are looking for are value types (such as structs). If you mutate any properties of a value type, you mutate the instance itself.
This means that when you pass a value type to a function, the function won't be able to mutate any of the properties of said value type.
On the other hand, classes are reference types, so mutating any of their properties doesn't mutate the class instance itself. Because of this, you cannot ban functions from modifying mutable properties of the class (unless you make them setter of said properties private).

Why does EnvironmentKey have a defaultValue property?

Here's the definition of EnvironmentKey:
public protocol EnvironmentKey {
associatedtype Value
static var defaultValue: Self.Value { get }
}
I'm confused as to the purpose of the defaultValue property. Or perhaps I'm confused about the intended use of #Environment. Let me explain...
Based on my testing, I know that the defaultValue is being accessed. If you, for example, define the defaultValue as a getter:
protocol MyInjector: EnvironmentKey {
static var defaultValue: Foo {
print("get default value!")
return Foo()
}
}
you'll find that SwiftUI frequently calls this property (because of this, in practice I define my defaultValue as static let).
When you add a breakpoint on the print statement and move down the stack it stops on the arrow below:
extension EnvironmentValues {
var myInjector: Foo {
get { self[MyInjector.self] } <-----------------
set { self[MyInjector.self] = newValue }
}
}
Further down in the stack is the line in my component's init where I access the following variable:
#Environment(\.myInjector) var foo: Foo
That is, it seems to access defaultValue any time a SwiftUI view with an #Environment variable is re-rendered.
I think that the internal implementation of #Environment needs to set this default value before it determines the actual value as a way to avoid making the variable declaration optional.
Finally, I also experimented with adding #Environment to an ObservableObject in the hopes that it would have access to the same "environment" as the SwiftUI view tree.
class Bar: ObservableObject {
#Environmenet(\.myInjector var foo: Foo
}
Unfortunately, that was not the case and the instance that Bar received was different than the instance I had injected via View.environment(\.myInjector, Foo()).
I also found, btw that I could use #Environment to inject a global singleton by using the shared variable pattern:
protocol MyInjector: EnvironmentKey {
static let defaultValue = Foo()
}
and the same instance was available to SwiftUI views and any other class, but I'm not sure how that could be useful in any way.
So what is the purpose of defaultValue? Is it simply, as I suspect, so that the internal implementation can assign an arbitrary value to the variable before the true value is determined, or is there something else going on?
IMO there are two reasons to have defaultValue in this pattern (and I assume this was the intention):
1) to specify corresponding value type
2) to make environment value always valid (in usage of #Environment)
In SwiftUI: Set Status Bar Color For a Specific View post you can find example of usage of this defaultValue feature to easily connect via environment different parts of application.

Converting Older KVO to Swift 4

I'm trying to convert some old WWDC swift code to Swift 4. I think that I have everything done, except for this last bit that does some KVO. This has been pretty difficult to narrow it down to this last bit because everything appears to function like the example code - but these KVO methods do not get called in Swift 4. I found that out here: Open Radar Bug
What would be the Swift 4 way to represent the following?
// use the KVO mechanism to indicate that changes to "state" affect other properties as well
class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> {
return ["state" as NSObject]
}
class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
return ["state" as NSObject]
}
class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
return ["state" as NSObject]
}
And here are the variable definitions from the example:
override var isReady: Bool {
switch state {
case .initialized:
// If the operation has been cancelled, "isReady" should return true
return isCancelled
case .pending:
// If the operation has been cancelled, "isReady" should return true
guard !isCancelled else {
return true
}
// If super isReady, conditions can be evaluated
if super.isReady {
evaluateConditions()
}
// Until conditions have been evaluated, "isReady" returns false
return false
case .ready:
return super.isReady || isCancelled
default:
return false
}
}
override var isExecuting: Bool {
return state == .executing
}
override var isFinished: Bool {
return state == .finished
}
If more code is needed, please let me know.
If this is a duplicate question, please link to the duplicate here. I've been unable to find a solution.
The keyPathsForValuesAffecting… members can be properties instead of methods.
They must be declared #objc because the KVO system accesses the properties using the Objective-C runtime.
The properties should have type Set<String>.
If you use the #keyPath directive, the compiler can tell you when you've used an invalid key path (for example because of a spelling error or a change to the property name).
Thus:
#objc class var keyPathsForValuesAffectingIsReady: Set<String> {
return [#keyPath(state)]
}
#objc class var keyPathsForValuesAffectingIsExecuting: Set<String> {
return [#keyPath(state)]
}
#objc class var keyPathsForValuesAffectingIsFinished: Set<String> {
return [#keyPath(state)]
}
You also need to make sure your state property is declared #objc dynamic.
The main problem is that KVO is built using Objective-C, and it uses the Objective-C runtime to detect the existence of the keyPathsForValuesAffecting methods. In Swift 4, methods are no longer exposed to Objective-C by default if you don't include an #objc annotation on them. So, in a nutshell, adding the #objc annotation will probably fix your problem.
Another thing that I do—not strictly necessary, but it makes the code look a bit nicer—is to declare these as static constants. The #objc will cause these to get exposed to Objective-C as class methods, so it all works, and it's slightly cleaner. I like to put private on them, too, since these will never get called by Swift code, and there's no point cluttering your class's internal and/or public interface.
You also need to make sure that your state property is KVO-compliant, and sending the notifications when it is changed. You can either do this by making the property dynamic, which will cause the KVO system to automatically generate the notification calls for you, or you can manually call willChangeValue(for:) and didChangeValue(for:) (or the string-based versions, willChangeValue(forKey:) and didChangeValue(forKey:)) in your willSet and didSet handlers for the property.
Finally, don't use raw string key paths in Swift if you can avoid it. The #keyPath() mechanism is the preferred way to get string-based key paths (and for uses other than these legacy Objective-C methods that need to take strings, you should use the new KeyPath type which is better still). If your state property is not an Objective-C-compatible type, though, you're stuck with the old string key paths (in which case you'll fire the notifications in your willSet and didSet as described in the previous paragraph). Alternatively, you can create a dummy Any-typed object that mirrors your state property, purely for KVO purposes.
So, something like this:
#objc private static let keyPathsForValuesAffectingIsReady: Set<String> = [
#keyPath(state)
]
Now, the state property. If it's an Objective-C-compatible type, it's easy:
#objc dynamic var state: ...
Or, if it's not:
#objc var state: SomeNonObjCThing {
willSet { self.willChangeValue(forKey: "state") }
didSet { self.didChangeValue(forKey: "state") }
}
OR:
#objc private var _stateKVO: Any { return self.state }
var state: SomeNonObjCThing {
willSet { self.willChangeValue(for: \.stateKVO) }
didSet { self.didChangeValue(for: \.stateKVO) }
}
// you can now use #keyPath(_stateKVO) in keyPathsForValuesAffecting...
(NS)Operation relies heavily on NSObject-KVO
The closest Swift syntax is
#objc private class func keyPathsForValuesAffectingIsReady() -> Set<String> {
return [#keyPath(state)]
}
#objc private class func keyPathsForValuesAffectingIsExecuting() -> Set<String> {
return [#keyPath(state)]
}
#objc private class func keyPathsForValuesAffectingIsFinished() -> Set<String> {
return [#keyPath(state)]
}
Side note: You might need to make state thread-safe.

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)

Extension Error Swift 3 JSON

I'm trying to migrate my app to Swift 3 but I have troubles.
The code section that struggles me is:
extension JSON: Swift.BooleanType {
//Optional bool
public var bool: Bool? {
get {
switch self.type {
case .bool:
return self.object.boolValue
default:
return nil
}
}
set {
if newValue != nil {
self.object = NSNumber(value: newValue! as Bool)
} else {
self.object = NSNull()
}
}
}
On first line is where xCode throws an error:
extension JSON: Swift.BooleanType {
The error says: Inheritance from non-protocol type 'BooleanType' (aka 'Bool')
Does anybody know what's happening there?
Simplest solution is here,
Bad : extension JSON: Swift.BooleanType {
Good : extension JSON: {
Reason : Admittedly, I am using this to modify SwiftyJSON which is a framework for processing JSON data. In doing some research it seems that they didn't allow BooleanType to allow for inheritance. The suggested means of dealing with this is simply to remove the type.
link is given : https://forums.developer.apple.com/thread/53405
Protocols are Swift's equivalent of Java interfaces. If you've never worked with interfaces before, they're classes absent of any concrete implementation. They exist to describe the skeleton of a class (the attribute and method names it should have) without actually implementing them so that other classes that inherit from the interface can flesh them out later. In Swift they're particularly useful for implementing the Delegate pattern.
Boolean is not a protocol. It is very much a living, breathing concrete Type with an existing implementation. To do what you want to do you either need to override the existing get/set method for Boolean type or create your own Boolean type as described in the official Apple Swift Blog.