I want to use variable names in dot notation in Swift.
I'm wrestling with trying to make sense of JSON in Swift, and having a really hard time doing things that are so easy in JS (like cycling through objects, etc).
I have variables of custom types:
var questionConfiguration: QuestionConfiguration
var priorityConfiguration: PriorityConfiguration
Each of these types has an attribute, displayOrder, I want to access this dynamically. I have tried various things, including the following (i is a variable of type Int):
var configs = [self.questionConfiguration, self.priorityConfiguration]
print([configs[i]].displayOrder)
var configs = ["questionConfiguration", "priorityConfiguration"]
print([configs[i]].displayOrder)
How can I achieve this?
EDIT:
struct QuestionConfiguration: Codable {
var displayOrder: Int
}
struct PriorityConfiguration: Codable {
var displayOrder: Int
}
You can create a protocol for same property name then you can access it easily
protocol SameProp {
var displayOrder: Int {get set}
}
class QuestionConfiguration : SameProp {
var displayOrder : Int = 4
}
class PriorityConfiguration : SameProp {
var displayOrder : Int = 6
}
var questionConfiguration: QuestionConfiguration = QuestionConfiguration()
var priorityConfiguration: PriorityConfiguration = PriorityConfiguration()
var configs = [questionConfiguration, priorityConfiguration] as [SameProp]
for elements in configs{
print(elements.displayOrder) // will print 4 and 6
}
Swift is very particular about data types and I suspect you are experiencing challenges because of it.
When you do this:
var configs = [self.questionConfiguration, self.priorityConfiguration]
In Swift, all the elements in an array have to have the same type. You've not given Swift any hints about what types this array should contain so the compiler has to infer a type. QuestionConfiguration and PriorityConfiguration are two different types. So the compiler is likely to infer that configs is of type [Any] also called Array<Any>.
Then you try something like this:
configs[i].displayOrder (not sure why your code has the extra brackets)
But configs[i] is inferred to be of type Any and that type doesn't have a displayOrder property so the compiler complains.
You need to tell the compiler that these types share some properties. One way to do that would be to add a protocol that describes what they share:
protocol DisplayOrderable {
var displayOrder: Int { get }
}
struct QuestionConfiguration: DisplayOrderable, Codable {
var displayOrder: Int
}
struct PriorityConfiguration: DisplayOrderable, Codable {
var displayOrder: Int
}
then you could use
var configs: [any DisplayOrderable] = [self.questionConfiguration, self.priorityConfiguration]
And configs[i].displayOrder should work.
Related
I never used Swift4 before, and dont know how to use KVC in it.
I try to create model with Dictionary, here the code:
class Person : NSObject {
var name: String = ""
var age: Int = 0
init(dict: [String : Any]) {
super.init()
self.setValuesForKeys(dict)
}
}
let dict: [String : Any] = ["name" : "Leon", "age" : 18]
let p = Person(dict: dict)
print(p.name, p.age)
There I get two question:
1. Why dont I using AnyObject? "Leon"and18 was infer to String and Int, does it using in KVC?
2. #objc var name: String = "" , this form is worked, but I can not understand it.
Thanks for all helps.
To implement KVC support for a property in Swift 4, you need two things:
Since the current implementation of KVC is written in Objective-C, you need the #objc annotation on your property so that Objective-C can see it. This also means that the property's type needs to be compatible with Objective-C.
In addition to exposing the property to Objective-C, you will need to set up your notifications in order for observers to be notified when the property changes. There are three ways to do this:
For stored properties, the easiest thing to do is to add the dynamic keyword like so:
#objc dynamic var foo: String
This will allow Cocoa to use Objective-C magic to automagically generate the needed notifications for you, and is usually what you want. However, if you need finer control, you can also write the notification code manually:
#objc private static let automaticallyNotifiesObserversOfFoo = false
#objc var foo: String {
willSet { self.willChangeValue(for: \.foo) }
didSet { self.didChangeValue(for: \.foo) }
}
The automaticallyNotifiesObserversOf<property name> property is there to signify to the KVC/KVO system that we are handling the notifications ourselves and that Cocoa shouldn't try to generate them for us.
Finally, if your property is not stored, but rather depends on some other property or properties, you need to implement a keyPathsForValuesAffecting<your property name here> method like so:
#objc dynamic var foo: Int
#objc dynamic var bar: Int
#objc private static let keyPathsForValuesAffectingBaz: Set<String> = [
#keyPath(foo), #keyPath(bar)
]
#objc var baz: Int { return self.foo + self.bar }
In the example above, an observer of the baz property will be notified when the value for foo or the value for bar changes.
Everything was right for me but still I was getting this error, then I solved it by
Enabling the "Inherit module from Target" in the Identity Inspector.
or
Giving the module(usually folder name/group name) in which it is present from the Identity Inspector under Custom Class
your code
var name: String = ""
var age: Int = 0
you can write the same code like this, when you are sure about its type.
var name = ""
var age = 0
Any is generally used for all types(function types and optional types), while AnyObject is used for Class types.
#objc have different meaning, when you use #objc in your swift class, that code is available in objective-c. You should use #objc attribute to specify same as objective-c class, in result older archives can be replaced by new swift class.
I have made a simple struct and implemented the Equatable protocol :
extension MyModelStruct: Equatable {}
func ==(lhs: NModelMatch, rhs: NModelMatch) -> Bool {
let areEqual = lhs.id == rhs.id
return areEqual
}
public struct MyModelStruct {
var id : String?
var staticId : String?
init(fromDictionary dictionary: NSDictionary){
id = dictionary["id"] as? String
...
}
Then in my project i get an array of [MyModelStruct], what i what to do is to remove all the MyModelStruct that have the same id
let val1 = MyModelStruct(id:9, subId:1)
let val2 = MyModelStruct(id:10, subId:1)
let val3 = MyModelStruct(id:9, subId:10)
var arrayOfModel = [val1,val2,val3]; // or set but i do not know how to use a set
var arrayCleaned = cleanFunction[M2,M3]
How can i make the cleanFunction ?
Can someone help please.
Thanks for all.
Xcode : Version 7.3.1
I really don't want people to just take an answer because it's the only one, that's why I'm showing you how you can use the power of sets. Sets are used wherever it doesn't make sense to have more than one, either it's there or not. Sets provide fast methods for checking whether an element is in the set (contains), removing an element (remove), combining two sets (union) and many more. Often people just want an array because they're familiar with it, but often a set is really what they need. With that said, here is how you can use a set:
struct Model : Hashable {
var id : String?
var hashValue: Int {
return id?.hashValue ?? 0
}
}
func ==(l: Model, r: Model) -> Bool {
return l.id == r.id
}
let modelSet : Set = [
Model(id: "a"),
Model(id: "hello"),
Model(id: "a"),
Model(id: "test")
]
// modelSet contains only the three unique Models
The only requirement for a type in order to be in a set is the Hashable protocol (which extends Equatable). In your case, you can just return the underlying hashValue of the String. If your id is always a number (which it probably is), you should change the type of id to be an Int, because Strings are much less efficient than Ints and it doesn't make sense to use a String.
Also consider storing this property somewhere, so that every time you receive new models, you can just do
modelSet.unionInPlace(newModels)
Use a Set instead of an Array.
I agree you are better off using a Set. You should be able to initialize the Set using an Array, e.g., var arrayOfModel: Set = [val1, val2, val3]. But because you are using a custom type, you will need to make sure that MyModelStruct conforms to hashable. This link has a good explanation.
But if you want to use an array then you need to change
let areEqual = rhs.id == lhs.id
to
let areEqual = rhs.id == lhs.id && rhs.subId == lhs.subId)
You need to modify your struct to have a subId property (and make the variables Int instead of String.
In answer to your question, yes you do need to iterative over the array.
If you extend the Array type with this function :
extension Array
{
func uniqueValues<V:Equatable>( value:(Element)->V) -> [Element]
{
var result:[Element] = []
for element in self
{
if !result.contains({ value($0) == value(element) })
{ result.append(element) }
}
return result
}
}
You'll be able to clean up duplicates using :
var arrayCleaned = arrayOfModel.uniqueValues({$0.id!})
without needing to make the structure Equatable.
Please note that this is not going to be efficient if your array is very large and you might want to consider filtering insertions into your array at the source if possible.
I am learning Swift, I saw some code from a project like following:
struct ServiceType {
init(_ value: UInt)
var value: UInt
}
var External: ServiceType { get }
var Internal: ServiceType { get }
var Normal: ServiceType { get }
I understand that the above code created three computed properties External, Internal & Normal all with type ServiceType.
What I want to ask are:
Why the struct declaration declare a init() ? I didn't see this when I was reading a Swift programming book.
What does it mean to have an underscore _ followed by a space before value: UInt in the init(_ value: UInt) ?
Is it so that struct has a value property by default in Swift? The above code just explicitly set the type of value to UInt?
What does the empty get mean in each property? What value it returns?
Does the OS randomly assign UInt value to each property's value?
1.
A struct gets a default initializer if it doesn't provide one on its own:
struct ServiceType {
var value: UInt
}
// default initialize with an specified parameter name "value" (called external name)
let service = ServiceType(value: 4)
In this case the struct provides an initializer so you don't get the default one.
2.
The underscore says that you don't have an external name:
let service = ServiceType(4)
Replacing the underscore with a different name the name is now the external name:
struct ServiceType {
init(aval value: UInt)
var value: UInt
}
let service = ServiceType(aval: 4)
3.
If I understand you right structs in general don't have a value property by default and in this case a value property is declared to be of type Uint. var value = 0 would be inferred to be of type Int.
4.
If you have seen this code in a protocol { get } means that it only returns a value and does not set one ({ get set }).
The return value of these computed properties are all of type ServiceType.
5.
In Swift you don't get default values for Ints, Doubles, ... as in other languages (mostly 0) except for Optionals which are nil by default. So you have to make sure that the value is initialized before using it otherwise the compiler will complain.
All of this gets pretty clear if you read the initialization chapter of the Swift book.
I'm trying to cast a 'filtered' array of a protocol type
I have a series of structs (Assessment, Level and Gate) that conform to several different Protocols - Stageable, Repeatable and Testable:
protocol Stageable
{
var index : Int { get }
var steps : [Step] { get }
}
protocol Testable
{
var threshold : Float { get }
}
protocol Repeatable
{
var sessions: Int { get }
}
struct Gate : Stageable, Testable, Repeatable
{
private(set) var index : Int
private(set) var steps : [Step]
private(set) var threshold : Float
private(set) var sessions : Int
}
struct Assessment : Stageable, Testable
{
private(set) var index : Int
private(set) var steps : [Step]
private(set) var threshold : Float
}
struct Level : Stageable, Repeatable
{
private(set) var index : Int
private(set) var steps : [Step]
private(set) var sessions : Int
}
Step is another struct. There are no classes being used.
These structs are populated just before being added to an array.
An array takes the form usually of [Assessment, Gate, Level, Level]. All the structs data is populated from an XML file.
In some instances I only want to review the 'Levels' in the array, so I do:
// stages = [Assessment, Gate, Level, Level, ...]
let levels = stages.filter{ $0 is Level }
If I query this, it looks fine e.g. levels.count is what I expect.
However, if I now want to cast the array to [Level] it crashes with the following error:
fatal error: can't unsafeBitCast between types of different sizes
Is this because I'm casting from a protocol to a struct type? I also feel like I've missed the key benefits of Protocols here and there must be a better way to do this.
Currently using Xcode 7 beta 5.
Casting arrays of structs is problematic because structs are value types. That means each element of an array of structs takes up the size of the struct in memory. This is different for arrays of standard objects because these are passed by reference. Each element in an array of objects is a reference (a pointer to a specific memory area).
To demonstrate this, consider the following
class ABC {
private var i = 0
private var j = 1
private var k = 2
}
print(sizeof(UIViewController))
print(sizeof(UIImage))
print(sizeof(NSObject))
print(sizeof(ABC))
Each of the print statements outputs 8 on my platform which is the size of a memory address (which is obviously different from the amount of memory occupied by instances of this class).
On the other hand, when I take the code from your question and do
print(sizeof(Stageable))
print(sizeof(Level))
I get 40 and 24 respectively which are the actual sizes of instances of these structs in memory. That means an array of type [Stageable] consists of chunks of 40 byte elements whereas an array of type [Level] consists of chunks of 24 byte elements. As a result you cannot cast between such array types because that would require the array's memory to be rewritten.
As an alternative, you can use the map method to force type conversion:
let levels = stages.filter({ $0 is Level }).map({ $0 as! Level })
The above can also be simplified by leveraging the flatMap method:
let levels = stages.flatMap({ $0 as? Level })
Well, when you execute this code:
let levels = stages.filter{ $0 is Level }
your levels type become [Stageable]. Now, to convert [Stageable] to [Level] you can use this code:
var l = levels.map{ $0 as! Level }
I have two swift classes with two fairly similar properties (I have made the ones below the same for the purpose of this question) I want to use. I want to call the properties dynamically in the ViewDidLoad and I have used an array as shown below so I can change the index and choose which class to call. Unfortunately I get the error
Anyobject does not have a member named speciesType
What could I have missed or am I approaching the whole problem in the wrong way?
class Lion {
var age = 45
var speciesType = "Felidae"
}
class Lioness {
var age = 10
var speciesType = "Panthera"
}
var animal1 = Lion()
var animal2 = Lioness()
var animalKingDom = [animal1, animal2]
var colourChanging = animalKingDom[0].speciesType
Why dont you create a protocol that extracts out the common properties from both the classes.
class Lion {
var age = 45
var speciesType = "Felidae"
}
class Lioness {
var age = 10
var speciesType = "Panthera"
}
protocol LionType {
var speciesType: String { get set }
var age: Int { get set }
}
extension Lion: LionType { }
extension Lioness: LionType { }
var animal1 = Lion()
var animal2 = Lioness()
var animalKingDom: [LionType] = [animal1, animal2]
var colourChanging = animalKingDom[0].speciesType
I created a protocol LionType and added extension to both the classes to conform to the protocol. Since both the classes already have age and speciesType, the classes will remain unchanged. And finally, a small modification to your animalKingDom array could make it work easily.
By the way, I added a protocol LionType, I think it makes more sense to name it AnimalType which fits for all animals not just lions :)
I prefer the syntax where you declare the protocol before the classes, and let the classes explicit implement the protocol upon creation.
import Cocoa
protocol Animal {
var age: Int { get set }
var speciesType: String { get set }
}
class Lion: Animal {
var age: Int
var speciesType: String
init(age: Int, speciesType: String) {
self.age = age
self.speciesType = speciesType
}
}
class Lioness: Animal {
var age: Int
var speciesType: String
init(age: Int, speciesType: String) {
self.age = age
self.speciesType = speciesType
}
}
var l1 = Lion(age: 5, speciesType: "Cat")
var l2 = Lioness(age: 6, speciesType: "Doge")
var animalArray: [Animal] = [l1, l2]
for animal in animalArray {
print(animal.age)
print(animal.speciesType)
}
Also I'm not sure I would give a class for both male and female of an animal. But it totally depends on your business domain.
The compiler infer that the generic type of your array is 'AnyObject'.
Try removing one element from your array, and the compiler will find the good type of your array.
The compiler is not able to know that Lion and Lioness has a property in common.
You can either make a superclass or a protocol like GeneratorOne suggests.
I think that what you really want to do is have two instances of a single class (or better yet, if your real example is anything like this one, a struct). Let's keep using your Lion class. Two instances would then be
var lion = Lion()
lion.age = 45
lion.family = "Felidae"
var lioness = Lion()
lioness.age = 10
lioness.family = "Panthera"
(I'm ignoring the fact that your male and female are in different families. :))