Swift. Is it possible to determining property time at runtime - ios

I have the next problem:
I have a class which can contain some data of different type but the type of the data is not known at compile time.
It looks like this
class Data {
var dataType: PodDataType = PodDataType.None
var componentsCount: UInt32 = 0
var stride: UInt32 = 0
var data // the data type of this field is unknown until dataType property is initialized
}
And I have another class which have multiple Data instances it looks like this:
class Mesh {
var verticesCount: UInt32 = UInt32.max
var facesCount: UInt32 = UInt32.max
var UVWChannelsCount: UInt32 = UInt32.max
var vertexIndexes: Data?
var stripLength: UInt32 = UInt32.max
var stripsCount: UInt32 = UInt32.max
var vertex: Data?
var normals: Data?
var tangents: Data?
var binormals: Data?
// and so on ...
}
Every time I create an new object of Data it should contain different data type in its 'data' field. What is the best way to solve such issues?
My thoughts:
1) AbstractFactory pattern: I can create base class Data and many subclasses like DataInt, DataFloat etc. but this means that I need to create subclass for every supporting type (int32, int8 and 15 types more).
2) Generics: I can define Data like this class Data, but after that my Mesh class become dependent from the Data class because there is no way to define generic class without type parameter.
3) UnsafePointer I can store data using this pointer but I not sure will the data persist if the NSData object from which I read the info is deallocated. Also what about the performance type casting from UnsafePointer to actual type.
Any other practices of this situation are highly appreciated.
P.S. I am developing parser for Pod file which stores 3D graphics data.
POD File Format Specification can be found here as reference.

Why not using Any?:
class Data {
var dataType: PodDataType = PodDataType.None
var componentsCount: UInt32 = 0
var stride: UInt32 = 0
var data: Any?
}
But I think this is the good use case for enum with associated values:
enum PodData {
case Ints([Int])
case Floats([Float])
// ...
case None
}

The usual approach to this in Swift is to make a property for each type and just use one for each instance. Simple, safe, and very little redundancy.
The only other viable alternative is to find a data format that can describe all the data types you want, e.g. String. But you will have to invest time to engineer reliable conversions for reading and writing. There is obviously a price in efficiency you have to pay.

In my opinion you could make use of first and second approach combined.
Make use of Abstract Factory pattern with concrete class for primitive class with Generics. It will make your life a bit easier.
class BaseData {
}
class PrimitiveData<T> : BaseData {
}
class UserDefinedData : BaseData {
}

Related

How to use Generics to handle dictionaries and arrrays

I am new to using generics but as according to my requirement I need to use it.
I have like 10 apis in which
4 returns array of custom objects(like multiple Person object data([Person]))
4 returns simple a object(like Company object data(Company))
two return simple dictionary
so what I'm trying to do is to create a common Response class
class Response<T>: NSObject {
#objc var responseData = T
}
But it is giving error on this line.
How should I suppose to use it so that it fulfills the requirements.
first of all generics cannot be represented in objc. so you need to use only Swift
class Response<T> {
var responseData: T!
}
you can use then T like this:
let response = Response<[String]>()
so response.responseData will be an array of String

Swift: Store multiple Types in a single array

How do I store multiple Types in a single array?
I'm doing the following:
var vehicles = [string]()
let mustang = Car() // car is a class name
mustang.brandName = "Ford"
mustang.modelName = "Mustang" // string type
mustang.modelYear = 1968
mustang.isConvertibible = true
mustang.isHatchback = false
mustang.hasSunroof = false // bool type
mustang.numberOfDoors = 2 // int type
mustang.powerSource = "gas engine"
// Add it to array
vehicles.append(mustang.brandName)
vehicles.append(mustang.modelName)
vehicles.append(mustang.modelYear) // Error: int type not supported
vehicles.append(mustang.isConvertibible) // Error: bool type not supported
vehicles.append(mustang.brandName)
vehicles.append(mustang.brandName)
How should I achieve this? I'm new to Swift / iOS.
Instead of creating string array you can create the array of car like this and store directly car object
var cars = [Car]()
let mustang = Car() // car is a class name
mustang.brandName = "Ford"
mustang.modelName = "Mustang" // string type
mustang.modelYear = 1968
mustang.isConvertibible = true
mustang.isHatchback = false
mustang.hasSunroof = false // bool type
mustang.numberOfDoors = 2 // int type
mustang.powerSource = "gas engine"
cars.append(mustang)
Or if you want to store different types of object then you cant create array of AnyObject, So that it will store Any type of instance or object inside that array.
var arr = [AnyObject]()
let car = Car()
let bike = Bike()
arr.append(car)
arr.append(bike)
Swift 5
Using protocol oriented programming
A little more swifty solution could be to use protocols. This is type safe, so you won't have to deal with unwrapping and casting optionals.
1. Define a protocol
protocol ModeOfTransportation {
// Define anything in common between objects
// Example:
var color: UIColor { get set }
}
2. Make your models conform to the protocol
(The compiler will ensure you give it the correct types: ex. color)
struct Car: ModeOfTransportation {
var color = UIColor.red
}
struct Bicycle: ModeOfTransportation {
var color = UIColor.blue
}
3. Give your array the protocol as a type
class Example {
var myTransportList: [ModeOfTransportation] = []
func addModesOfTransport() {
let car = Car()
let bike = Bicycle()
myTransportList.append(car)
myTransportList.append(bike)
}
}
I think you're doing something really wrong.
Arrays are designed to hold only one type of stuff. If you really want to hold different types of stuff. Here's some possible methods:
you can try creating an array of AnyObjects:
-
var vehicles = [AnyObject]()
This won't work if the type you want to store does not conform to AnyObject. If you have such a type, you will have to use Any.
you can create an array of strings and convert all the values you want to store to a string
You can just create an array of Cars to store all the properties of a car. Then you can add more cars to it later on. I think this is what you intended.
Per the Swift documentation:
AnyObject can represent an instance of any class type.
Any can represent an instance of any type at all, including function types.
So you could make an array of
var vehicles = [AnyObject]()
and this would take objects (class instances) of any type.
However, the documentation goes on to say:
Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work with in your code.
So, ideally you are specific about the type your array can hold.

Declare an array of Int in Realm Swift

How can I declare an array of integers inside RLMObject?
Like :
dynamic var key:[Int]?
Gives the following error :
Terminating app due to uncaught exception 'RLMException', reason: ''NSArray' is not supported as an RLMObject property. All properties must be primitives, NSString, NSDate, NSData, RLMArray, or subclasses of RLMObject. See https://realm.io/docs/objc/latest/api/Classes/RLMObject.html for more information.'
Lists of primitives are not supported yet unfortunately. There is issue #1120 to track adding support for that. You'll find there some ideas how you can workaround that currently.
The easiest workaround is create a object to hold int values. Then the model to have a List of the object.
class Foo: Object {
let integerList = List<IntObject>() // Workaround
}
class IntObject: Object {
dynamic var value = 0
}
Fortunately arrays of primitive types are now supported in Realm 3.0 and above. (Oct 31 2017)
You can now store primitive types or their nullable counterparts (more specifically: booleans, integer and floating-point number types, strings, dates, and data) directly within RLMArrays or Lists. If you want to define a list of such primitive values you no longer need to define cumbersome single-field wrapper objects. Instead, you can just store the primitive values themselves!
class MyObject : Object {
#objc dynamic var myString: String = ""
let myIntArray = List<Int>()
}
Source: https://realm.io/blog/realm-cocoa-reaches-3-0/
The accepted offer is very costly in term of memory.
You might get a List of very big "n" of objects.
It's not a matter of right and wrong but I think it's good to write here a different workaround.
Another approach:
I decided to use a single string to represent an Int array.
In my Realm class I defined a variable:
dynamic var arrInt: String? = nil
And use it very easily:
let arrToSave = [0, 1, 33, 12232, 394]
<MY_CUSTOM_REALM_CLASS>.arrInt = arrToSave.map { String(describing: $0) }.joined(separator: ",")
And the way back:
let strFetched = <MY_CUSTOM_REALM_CLASS>.arrInt
let intArray = strFetched.components(separatedBy: ",").flatMap { Int($0) }
Will be happy to hear your feedback, as I think this approach is better.
As the error message states, you have to use RLMArray - or rather it's swift equivalent List.
See: Realm docs

Subclassing v.s. Protocols

Lets begin with the Class approach:
class LoginCredentials {
var id : String
init(userID:String) {
self.id = userID
}
}
then we will have the following:
class FacebookLoginCredentials : LoginCredentials {
var token : String
init(userID:String,userToken:String) {
self.token = userToken
super.init(userID: userID)
}}
And
class TwitterLoginCredentials : LoginCredentials {
var token : String
var secret : String
init(userID:String,userToken:String,secret : String) {
self.token = userToken
self.secret = secret
super.init(userID: userID)
}
}
The second Approach is the Protocol Oriented if I am not wrong
protocol LoginCredentials {
var id : String { get }
}
then we will have :
struct FacebookLoginCredentials : LoginCredentials {
var id: String
var token : String
init(userID:String,userToken:String) {
self.id = userID
self.token = userToken
}
}
And
struct TwitterLoginProfile : LoginCredentials {
var id: String
var token : String
var secret : String
init(userID:String,userToken:String,secret : String) {
self.token = userToken
self.secret = secret
self.id = userID
}
}
I just need to know which one is more Swift ?
Ultimately, neither of these approaches is "more Swift". In Swift, you will sometimes want to use inheritance and other times you will want to use protocols. The real decision point for these two approaches is:
Do you want value type semantics (structs and protocols) or do you want reference type semantics (classes and protocols). I usually default to value type semantics because they are safer but there are definitely circumstances where reference type semantics are important. You can read more about that here: Why Choose Struct over Class.
Either or is acceptable in swift.
Here is how you want to distinguish from the two.
When working with Protocols, you want to treat these as if they were blue prints for your objects.
Ballplayers must know what a ball is, so any person that wants to be a Ballplayer must know what a ball is.
You have a set of rules you want certain objects to follow, make a protocol.
If you want to make objects that have functionality, and you want the children to inherent this functionality, and then have more functionality, then do the inheret class structure.
Dad can throw a ball at 100MPH
Junior can throw a ball at 100MPH and throw a curve ball.
This would be done with a class, not protocol
Structure instances are always passed by value, and class instances are always passed by reference. This means that they are suited to different kinds of tasks. As you consider the data constructs and functionality that you need for a project, decide whether each data construct should be defined as a class or as a structure.
As a general guideline, consider creating a structure when one or more of these conditions apply:
The structure’s primary purpose is to encapsulate a few relatively simple data values.
It is reasonable to expect that the encapsulated values will be copied rather than referenced when you assign or pass around an instance of that structure.
Any properties stored by the structure are themselves value types, which would also be expected to be copied rather than referenced.
The structure does not need to inherit properties or behavior from another existing type.
Examples of good candidates for structures include:
The size of a geometric shape, perhaps encapsulating a width property and a height property, both of type Double.
A way to refer to ranges within a series, perhaps encapsulating a start property and a length property, both of type Int.
A point in a 3D coordinate system, perhaps encapsulating x, y and z properties, each of type Double.
In all other cases, define a class, and create instances of that class
to be managed and passed by reference. In practice, this means that
most custom data constructs should be classes, not structures.
Why Choose Struct Over Class?

Swift - Downcasting an array of protocols to type crashes with unsafeBitCast Fatal Error

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 }

Resources