Mixing Partial KeyPaths and Arrays in Swift - arrays

Apologies if this is trivial, but I have not yet gotten my head around KeyPaths fully. I have a class (ContainerClass) which has an array of classes in it (mySettings). the mySettings Class has two variables (color and name). The array of mySettings is defined at runtime. I am trying to modify the properties in the mySettings class via KeyPaths. The following code is a simplified version of what I am trying to achieve:
import Cocoa
import Foundation
class Settings {
var color: Int?
var name: String?
init(color: Int, name: String) {
self.color = color
self.name = name
}
}
class ContainerClass {
var mySettings = [Settings]()
}
var myClass = ContainerClass()
let kp = \ContainerClass.mySettings
let kp2 = [\Settings.color, \Settings.name]
myClass.mySettings.append(Settings(color: 1, name: "One"))
myClass.mySettings.append(Settings(color: 2, name: "Two"))
myClass.mySettings.append(Settings(color: 3, name: "Three"))
myClass.mySettings.append(Settings(color: 4, name: "Four"))
myClass.mySettings.append(Settings(color: 5, name: "Five"))
for index in 0..<myClass.mySettings.count {
print(myClass.mySettings[index].color!)
}
let target = myClass[keyPath: kp][0][keyPath: kp2[0]]
print("\ntarget:", target as! Int)
myClass[keyPath: kp][0][keyPath: kp2[0]] = 128 // ERROR: Cannot assign through subscript: 'myClass' is immutable
for index in 0..<myClass.mySettings.count {
print(myClass.mySettings[index].color!)
}
I need two separate KeyPaths: one to target one specific mySettings element of the array contained in ContainerClass, and another KeyPath to target one of the two properties within the Settings class. At runtime, the program will determine which element of the array and which property must be modified.
I can read the property (in my example via: let target = myClass[keyPath: kp][0][keyPath: kp2[0]]), but I cannot modify it by trying, for example, a line like: myClass[keyPath: kp][0][keyPath: kp2[0]] = 128. I get an error: Cannot assign through subscript: 'myClass' is immutable.
I am not sure about the interaction of these partial keypaths with arrays. Any help on how to resolve this (i.e. being able to modify properties within Settings this way) will be much appreciated.

Related

Problem on RealmSwift: "Invalid array input: more values (1) than properties (0)." while trying to persist an Array of String

I'm trying to persist in a table view cell, the result of a quiz test with questions and I needed the array of answers given (String Array) so I decided to use RealmSwift.
I created this class and of course I created also a RealmString object in the same file to handle the possibility to persist arrays of String in Realm in this way:
class RealmString: Object {
dynamic var stringValue = ""
}
class Test: Object {
#objc dynamic var ID = UUID().uuidString
#objc dynamic var testScore : String = String()
#objc dynamic var testTitle : String = String()
#objc dynamic var testSubTitle : String = String()
#objc dynamic var dateOfExecution: String = String()
#objc dynamic var answersGiven: [String] {
get {
return _backingAnswersGiven.map { $0.stringValue }
}
set {
_backingAnswersGiven.removeAll()
_backingAnswersGiven.append(objectsIn: (newValue.map({ RealmString(value: [$0]) })))
}
}
let _backingAnswersGiven = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["answersGiven"]
}
override static func primaryKey() -> String? {
return "ID"
}
Now in the view controller:
I have a variable that stores the result (is an Int array that will take ten answers with values from 0 to 5, and these will later be converted to String)
i.e.: [0,2,2,3,4,5,2,1,0,2] -> ["0","2","2","3","4","5","2","1","0","2"]
and when an option is selected in a question the value is set with this function, everything works fine.
public var questionResults: [Int] = []
func setValueToQuestion(questionNumber: Int) {
questionResults[questionNumber] = optionChosen
}
When the test is completed successfully everything is saved in this way:
let test = Test()
test.ID = currentTest?.ID ?? UUID().uuidString
test.testTitle = testTitleLabel.text!
test.testScore = resultNumberLabel.text!
test.testSubTitle = resultLabel.text!
test.dateOfExecution = dateTimeString
test.answersGiven = questionResults.map({String($0)})
DBManager.sharedInstance.addData(object: test)
I tried the code separately also adding breakpoints and everything works in the flow, expect this line:
test.answersGiven = questionResults.map({String($0)})
that raises the error shown in the title: "Invalid array input: more values (1) than properties (0)."
I guess it can be an error of mapping maybe?
This value is then treated in the rest of flow as a simple swift array of String = [String]
There are a few issues which may be leading to that error.
First the RealmString property is not persisted because it needs #objc
dynamic var stringValue = ""
should be
#objc dynamic var stringValue = ""
Secondly, and this is important, Realm does not support primitives in Lists. Well, it kinda does but not very well.
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
See my answer to this question but in a nutshell, you need another class to store the string in - kind of like your RealmString class.
class StringClass: Object {
#objc dynamic var myString = ""
}
and then change the Test object property to use the StringClass property
#objc dynamic var answersGiven = [StringClass]()
and then I see you're trying to use a backed var and computed property but I am not sure why. It may be simpler to use use the var itself
let _backingAnswersGiven = List<RealmString>()
since the List collection already handles what's being computed.
For example, if you set the list you can set it to another list (which wipes out the current list). Or when you get the list let myStringList = test._backingAnswersGiven, gets all of the StringClasses in the list without having to .map over them.

Swift 4 Array get reference

I ran into an issue with arrays in Swift. The problem is that it's a value type in Swift. I'm trying to find a workaround.
Here is the code that I have:
class Object: Codable{
var name : String?
}
var objects: Array<Object>?
objects = Array<Object>()
if var obj = objects { // <----- Creates a copy of array here
let o = Object()
o.name = "1"
objects?.append(o)
print(obj) //<----- this one is missing "o" object
print(objects)
}
I cannot use NSMutableArray because I have an array inside another codable class.
What's everybody's experience on this one? If somebody can share a solutions for that.
Getting used to arrays as value types isn't too tough really. If i were you my version of the code would just look like this
var objects: Array<Object>?
objects = Array<Object>()
if var unwrappedObjs = objects {
let o = Object()
o.name = "1"
unwrappedObjs.append(o)
objects = unwrappedObjs
}
or alternatively maybe this:
var objects: Array<Object>?
objects = Array<Object>()
if objects != nil {
let o = Object()
o.name = "1"
objects?.append(o)
}
Lastly you could always try making your own "ReferenceArray" class that wraps the array APIs and gives you reference semantics but that seems like overkill. Sooner rather than later, arrays as value types will seem natural to reason about.
bitwit already mentioned this to a point, but I think that your biggest mistake is simply not accepting the new object as the source. Unless it's important to retain the Array<Object>? you should replace it with the Array<Object> one.
var objects: Array<Object>?
objects = Array<Object>()
if var objects = objects { // <----- Creates a copy of array here
let o = Object()
o.name = "1"
objects.append(o) // objects is now the non-optional one
print(objects)
}
If it needs to be in the same scope, use guard:
var objects: Array<Object>?
objects = Array<Object>()
guard var objects = objects else { // <----- Creates a copy of array here
fatalError()
}
let o = Object()
o.name = "1"
objects.append(o) // objects is now the non-optional one
print(objects)
If you absolutely need an array to be referenced, you can make a container class:
public class ReferenceContainer<Element> {
public var element: Element
init(_ element: Element) {
self.element = element
}
}

How to save generic object to an array in swift

I have on class
class SomeClass<T>: AsyncOperation, NetworkOperationProtocol {
/// The resource model object that conforms to Parsable Response
public typealias Resource = T
}
I want to save instance of this class to an array, and want retrieve it later.
How can I achieve this?
If your class implements protocol with associatedtype, it's impossible to put it into array because such protocols have Self-requirement - they need to know concrete type for associatedtype.
However, you can use technique called type-erasure to store type without associated type information. So, for example, you can create a protocol without associatedtype, like so
protocol Operation {
func perform()
}
class SomeClass<T> : AsyncOperation, NetworkOperationProtocol, Operation
And then define an array of such operations:
let operations : [Operation] = []
operations.append(SomeClass.init())
If your generic type would be:
struct GenericType {}
You would specify the generic class using:
let array = [SomeClass<GenericType>]()
Or you can let it infer the type on its own with something like:
class SomeClass<T>: AsyncOperation, NetworkOperationProtocol {
/// The resource model object that conforms to Parsable Response
public typealias Resource = T
let resource: Resource
init(with resource: Resource) {
self.resource = resource
}
}
let item = SomeClass(with: GenericType())
let array = [item]
It should be simple
You can declare an array like this
var anArray = [SomeClass<Parsable>]()
Please note that while declaring the array I have defined the Parsable instead of T.
If you don't know the type of T while creating array. You can go following way
var anArray = [AnyObject]()
let anObject = SomeClass<Parsable>()
anArray.append(anObject)
if anArray[0] is SomeClass<Parsable> {
print(true)
}

Can I use a string from an array as a selector in Swift?

I am trying to learn Swift -- current task is making a simple menu for a Mac app from an array of objects that contain strings. The problem now is how to pass the selector, which in the array is a string, but in the code is a function.
The class is
class menuArrayObject
{
var title: String = ""
var subMenuTitles: [String] = []
var subMenuSelectors: [String] = []
}
Here is my code
for index2 in 0...counter2 - 1
{
let subMenuTitle = arrayObject.subMenuTitles[index2]
let subMenuSelector = NSSelectorFromString(arrayObject.subMenuSelectors[index2])
let subMenu = NSMenuItem(title: subMenuTitle, action: #selector(subMenuSelector(_:)),keyEquivalent: "")
indexMenu.addItem(subMenu)
}
The error message (on let subMenu =) is: "argument of #selector cannot refer to a property"
Is this do-able? Is this desirable to actually do? Is there a better way?
Thanks in advanceemphasized text
What you are trying to do is totally legit – you can indeed convert a string to a selector, then pass that selector to a menu item.
You're however trying to use the selector literal syntax to initialise a Selector, giving that language construct a Selector as an argument (which is just syntactically wrong), where in fact you can just pass the Selector returned by NSSelectorFromString to the NSMenuItem initialiser call.
The selector literal syntax #selector is used when you have a "fixed" selector in mind that you want to create a Selector for (for instance an instance method of the class you are in). The NSSelectorFromString is intended for this kind of cases like yours where the selector is a variable (now that in Swift 2.2 there is indeed some syntax given for #selector literals!)
import Cocoa
class MenuArrayObject
{
var title: String = "Foo"
var subMenuTitles: [String] = ["foo"]
var subMenuSelectors: [String] = ["foobar"]
}
let menuArrayObject = MenuArrayObject()
let indexMenu = NSMenu()
for (i, submenuTitle) in menuArrayObject.subMenuTitles.enumerate() {
let selectorStr = menuArrayObject.subMenuSelectors[i]
let selector = NSSelectorFromString(selectorStr)
let item = NSMenuItem(title: submenuTitle, action: selector, keyEquivalent: "")
indexMenu.addItem(item)
}

How to make shallow copy of array in swift

I have searched for a while but couldn't find reasonable answer for this. I want to add/remove objects in one array to make effect in 2nd array which points to first array.
class Person
{
var name:String = ""
}
var arr1:[Person] = [Person]()
let p1 = Person()
p1.name = "Umair"
let p2 = Person()
p2.name = "Ali"
arr1.append(p1)
arr1.append(p2)
var arr2 = arr1
print("\(arr1.count)") //"2\n"
print("\(arr2.count)") //"2\n"
arr1.removeFirst()
print("\(arr1.count)") //"1\n"
print("\(arr2.count)") //"2\n"
Why changing arr1 does not affect arr2. Please help me out to accomplish this.
Arrays are value types. When we copy them, each copy is independent of the other. This is a virtue in Swift. What you are trying to do requires references so that effects on one can be seen by others. Try this code. Create a class (reference type) containing your data. Now changes to the container can be seen in the other.
class Person
{
var name: String
init(_ name: String) {
self.name = name
}
}
let p1 = Person("Umair")
let p2 = Person("Ali")
class Container {
var people = [Person]()
init(people: [Person]) {
self.people = people
}
}
let arr1 = Container(people: [p1, p2])
let arr2 = arr1
print(arr1.people)
print(arr2.people)
arr1.people.removeFirst()
print(arr1.people)
print(arr2.people)
Even if you're using Swift, you can still use NSArray.
Per Apple's documentation,
NSArray is an object representing a static ordered collection, for use instead of an Array constant in cases that require reference semantics.
The only downside is you'll have to import Foundation. This isn't a problem if you're creating an iOS or Mac app, as you're depending on it already.

Resources