Cannot Dynamically Set Enum in Swift with Reflection - swift

I am attempting to dynamically clone a class object in Swift using reflection and the setValue function. This class contains an enum property that is backed with an Int, which is causing this dynamic reflection to crash:
#objc enum Status : Int {
case green
case yellow
case red
}
#objc class State : NSObject {
#objc var status : Status
init(_ status: Status) {
self.status = status
}
}
func testReflectiveClone() {
let state1 = State(.green)
let state2 = State(.yellow)
let state1Mirror = Mirror(reflecting: state1)
for property in state1Mirror.children.enumerated() {
let label = property.element.label
state2.setValue(property.element.value, forKey: label!) //crashes here
}
}
This test function is throwing the following error in XCode:
-[__SwiftValue longLongValue]: unrecognized selector sent to instance 0x600001e5a340 (NSInvalidArgumentException)
Is it even possible to dynamically set enum values? What modification would I need to make to get this to work?

You can add rawValue to avoid the runtime error.
state2.setValue(property.element.value.rawValue, forKey: label!)
Please check the definition of setValue.
open func setValue(_ value: Any?, forKey key: String)
The enum value you passed will be transferred to internal class named "__SwiftValue", the compiler found that "status" was an Int enum type and tried to convert the value to Swift.Int with "longlongvalue", so the runtime error happened.

Related

Most specific generic function not called

I'm using #propertyWrapper to reduce my UserDefaults boilerplate as follows…
enum PreferenceKey: String, CaseIterable {
case enumName, stringName
}
#propertyWrapper
struct Prefs<T> {
let key: PreferenceKey
var wrappedValue: T? {
get {
UserDefaults.object(for: key)
}
set {
UserDefaults.set(newValue, for: key)
}
}
}
struct Preferences {
#Prefs(key: .enumName) static var enumName: Name?
#Prefs(key: .stringName) static var stringName: String?
}
extension UserDefaults {
static func object<T>(for key: PreferenceKey) -> T? {
standard.object(forKey: key.rawValue) as? T
}
static func object<T: RawRepresentable>(for key: PreferenceKey) -> T? where T.RawValue == String {
if let value = standard.object(forKey: key.rawValue) as? String {
return T(rawValue: value)
}
return nil
}
static func set<T: RawRepresentable>(_ value: T, for key: PreferenceKey) {
print("Set Raw Value \(value)")
standard.set(value.rawValue, forKey: key.rawValue)
}
static func set<T>(_ value: T, for key: PreferenceKey) {
print("Set Value \(value)")
standard.set(value, forKey: key.rawValue)
}
}
This works fine when setting a regular property list type…
Preferences.stringName = "Fred"
// Set Value Optional("Fred")
print(Preferences.stringName)
// Optional("Fred")
But when trying to set a value that is RawRepresentable, it fails…
Preferences.enumName = .Fred
// Set Value Optional(__lldb_expr_10.Name.Fred)
// libc++abi.dylib: terminating with uncaught exception of type NSException
Rather than calling the most specific version of UserDefaults.set(, it calls the non-specific one.
Just calling
UserDefaults.set(Name.Fred, for: .enumName)
works fine. In this case it calls the most specific function.
With further testing, and it seems that this isn't a #propertyWrapper issue. The following top level function also fails to call the more specific generic function. It seems like some type information is being lost somewhere
func set<T>(_ value: T?) {
UserDefaults.set(value, for: .enumName)
}
set(Name.Fred)
// Set Value Optional(__lldb_expr_5.Name.Fred)
// libc++abi.dylib: terminating with uncaught exception of type NSException
What am I missing? Any thoughts as to how I can resolve this?
What am I missing?
Swift is essentially a statically typed language and selecting which of your function overloads to call is determined at compile time.
In your working example:
UserDefaults.set(Name.Fred, for: .enumName)
the type of the first argument is known by the compiler. This type implements RawRepresentable and the compiler uses that to select the overload you expect.
Now consider your failing example:
func set<T>(_ value: T?) {
UserDefaults.set(value, for: .enumName)
}
set(Name.Fred)
When the compiler compiles the set function the only thing it knows about the argument value is that is has a type which it can reference as T. There are no constraints on T, at runtime a value of any type can be passed, so in determining which overload of UserDefaults.set to compile a call to the compiler can only select the overload which also has no constraints and accepts a value of any type.
Any thoughts as to how I can resolve this?
You already know one solution, you overloaded UserDefaults.set, you could overload your set function. However you might wish to consider your design here in the light of Swift's compile-time resolution of overloads – you may not want layers of overloaded functions calling each other.
HTH

Getting error while accessing internal enum from public enum in swift 4.2

I am getting error while accessing internal enum values inside public enum
Code snippet
#objc public enum Sample1 : Int {
case valid
fileprivate static var upgradeStatus:[String: Sample1] = [
RawString.validValue.rawValue : .valid
]
}
internal enum RawString: String {
case validValue = "Invalid"
}
The error says
Enum 'RawString' is internal and cannot be referenced from a property initializer in a '#_fixed_layout' type
Looks like it is because of bridging #Objc but not able to fix the issue. I cannot remove #Objc as my code is used in Objective C project as well.
Could anyone please help me in resolving this issue.
P.S : Started observing this error once after I update to Xcode 10
You could convert upgradeStatus to a static method, this will make your code compile:
fileprivate static func upgradeStatus() -> [String: Sample1] {
return [
RawString.validValue.rawValue : .valid
]
}

Cannot assign to property: <Enum:case> is not settable

for example we have simple enum
public enum CXActionSheetToolBarButtonItem {
case cancel
case done
case now
private static var titles: [CXActionSheetToolBarButtonItem: String] = [
.cancel: "Cancel",
.done: "Done",
.now: "Now",
]
public var title: String {
get { return CXActionSheetToolBarButtonItem.titles[self] ?? String(describing: self) }
// what am I want to do
set(value) { CXActionSheetToolBarButtonItem.titles[self] = value }
}
// what am I forced to do
public static func setTitle(_ title: String, for item: CXActionSheetToolBarButtonItem) {
CXActionSheetToolBarButtonItem.titles[item] = title
}
}
why I don't can set new title like this
CXActionSheetToolBarButtonItem.cancel.title = "asd"
compiler responded error - Cannot assign to property: 'cancel' is not settable, but I can set title with function
CXActionSheetToolBarButtonItem.setTitle("asd", for: .cancel)
what should I change for worked my var as settable? .cancel.title = "asd"
Using an enum for this seems inappropriate, but I'll address the porblem at face value. You need to mark your setter as nonmutating, so that it can be called on non-var instances of your enum:
public enum CXActionSheetToolBarButtonItem {
// ...
public var title: String {
get { return CXActionSheetToolBarButtonItem.titles[self] ?? String(describing: self) }
nonmutating set(value) { CXActionSheetToolBarButtonItem.titles[self] = value }
}
}
CXActionSheetToolBarButtonItem.cancel.title = "foo" // Works... but why would you want this?!
I think the main reason why you can't do that is because the Swift compiler sees enum cases as immutable by default (similar to an immutable struct declared with let), and here you are trying to mutate it.
A good way to see this is if you try to add a mutating function to this enum
mutating public func setTitle2(_ newValue: String) {
CXActionSheetToolBarButtonItem.titles[self] = newValue
}
You will then receive the error message
error: cannot use mutating member on immutable value: 'cancel' returns immutable value
How to work around this
One way to have a similar behavior is by changing this enum into a set of static variables (which is more consistent with what you are trying to achieve).
public struct CXActionSheetToolBarButtonItem {
var title: String
static var cancel = CXActionSheetToolBarButtonItem(title: "Cancel")
static var done = CXActionSheetToolBarButtonItem(title: "Done")
static var now = CXActionSheetToolBarButtonItem(title: "Now")
}
Now you can use the following
CXActionSheetToolBarButtonItem.cancel.title = "asd"
Also, you still have the ability to use the dot-syntax
let buttonItem: CXActionSheetToolBarButtonItem = .cancel // it works
Hope that helps!
TD;DR: Because enum is an immutable object.
First of all: Whatever you're trying to do here – it's certainly not a good approach and pretty dangerous. As Craig pointed out in his comment, you're messing around with instance properties and static properties. You can have multiple instances of an enum – but when you want to change the title of a particular instance, you also change the title for all other instances. That's unexpected behavior and you should really think of another solution.
That being said, your code actually does work – with a little modification: Instead of
CXActionSheetToolBarButtonItem.cancel.title = "asd"
you can write
var item = CXActionSheetToolBarButtonItem.cancel
item.title = "asd"
This will compile.
The reason behind it is that an enum is a value type. Everytime you create an instance of an enum type, e.g. var item = CXActionSheetToolBarButtonItem.cancel, the enum's value is copied into the new variable item. You can choose if you want that value to be mutable or not by using either var or let. That's how Swift enums are intended to be used.
A single enum case like CXActionSheetToolBarButtonItem.cancel is immutable by definition.
CXActionSheetToolBarButtonItem.cancel.title = "asd"
wouldn't have any meaning because there is no instance to which the title can be assigned. The enum case .cancel is not bound to a variable.

Swift 3. xCode8. Assignment of value to attribute of Boolean type of an entity raises error

Core data
let appDelegate = UIApplication.shared().delegate as! AppDelegate
let container = appDelegate.persistentContainer
let managedObjectContext = container.viewContext
for item in items {
let game = NSEntityDescription.insertNewObject(forEntityName: "Game", into: managedObjectContext) as! GameEntity
game.str = item.str
game.rating = item.rating
game.isConquered = false
when executing assignment to game.conquered I am getting:
exception 'NSInvalidArgumentException' reason unrecognized selector sent to instance
In data model
extension GameEntity {
#nonobjc class func fetchRequest() -> NSFetchRequest< GameEntity > {
return NSFetchRequest< GameEntity >(entityName: "Game");
}
#NSManaged var conquered: Bool
#NSManaged var rating: Int32
#NSManaged var str: String?
}
In model explorer in xCode conquered has type Boolean
I tried to state type for conquered as Boolean in code, but got compiler error - that it can't be represented as NSManaged.
I know I can use Int32 instead of Bool, and differentiate between 0/1 values, I just want to understand what is the reason of error I am getting.
after I renamed isConquered to conquered issue has been solved.
So, as I understand issue was in the prefix is, is there any rules in standard that stipulates that some prefixes shouldn't be used for attribute's names??

Obtaining a string representation of a protocol dynamically

I'm looking for a way to obtain a protocol name dynamically from the protocol type, without having to use the #objc attribute in the protocol declaration.
I know that this works:
func keyForProtocol(aProtocol: Protocol) -> String {
return NSStringFromProtocol(aProtocol)
}
but only if the protocol has the #obj attribute:
#objc protocol Test {}
var key = keyForProtocol(Test.self) // Key contains "TestApp.Test"
however as soon as I remove the #objc attribute, compilation fails with this error:
'Test.Protocol' is not convertible to 'Protocol'
Is there any way to achieve that?
Note: the reason why I want to avoid #objc is that it doesn't allow usage of associated types (i.e. generics in protocol) - in those cases compilation fails with this error: Method cannot be marked #objc because the type of parameter xx cannot be represented in Objective-C
I recently found a solution by implementing the keyForProtocol method as follows:
func keyForProtocol<P>(aProtocol: P.Type) -> String {
return ("\(aProtocol)")
}
It works with any type, not just for protocols, but I'm ok with that.
Some examples from a playground:
protocol Test {}
keyForProtocol(Test.self) // Prints "__lldb_expr_92.Test"
class MyClass {}
keyForProtocol(MyClass.self) // Prints "__lldb_expr_92.MyClass"
keyForProtocol(Int.self) // Prints "Swift.Int"
keyForProtocol(UIView.self) // Prints "UIView"
As Antonio said:
let protocolDescription = "\(aProtocol)"
will return a (sometime strange) protocol name.
I finally completed my task converting back this string to a protocol with:
let protocolReference = NSProtocolFromString(protocolDescription)
This technique is perfect if you need to retrieve the protocol type from a generic parameter like in this example (a lightweight/homemade dependency manager):
class DependencyManager<T> {
private static func inject(_ aProtocol: T.Type) -> T {
let application = UIApplication.shared.delegate as! LMApplication
let dependencies = application.applicationDependencies
let protocolReference = NSProtocolFromString("\(aProtocol)")
let dependency = dependencies!.object(for: protocolReference)
return dependency! as! T
}
}
Usage:
let object: Protocol = DependencyManager.inject(Protocol.self)