Is there any way to get the list of attributes of a class without any instantiated object? - swift

I already know that we can get the list of attributes of a object using reflection in Swift, but all the examples that I found and have implemented so far uses a instantiate object. Something like that:
func attributeList() -> [String] {
var attributeList = [String]()
let serializableMirror = Mirror(reflecting: self) //using instantiate object
for childMirror in serializableMirror.children {
if let label = childMirror.label {
attributeList.append(label)
}
}
return attributeList
}
My question is, there is any way to get the attributes of a class without any reference to it? Some kind of static method where I pass my desired class type and get the attributes list of it.

In all implementations of reflection that I've worked with you need an object for the reflection to work on.
Whether it's one you provide or one the system creates for you, you have to have a concrete object.

Related

RealmSwift initialize list : Cannot specialize a non-generic definition

Hi I have an exception like "Cannot specialize a non-generic definition"
when i'm trying to initialize List in Realm object. Does anyone know how to fix this issue? swift 3.2
class Dog: Object {
#objc dynamic var name = ""
#objc dynamic var age = 0
}
class Event : Object{
dynamic var event_id = 0
dynamic var date : String?
dynamic var name : String?
dynamic var remind : Remind?
dynamic var event_status = 0
let dogs = List<Dog>() "Cannot specialize a non-generic definition"
required convenience init?(map: Map){ self.init() }
}
I wanted to give the solution to this question some context. In Swift, namespacing is implicit on a module level. If you have external modules, such as RealmSwift, which has a type List you can still have a type named List in your own module. You can even have multiple external modules with the type List.
My reasoning for the error you presented was as follows
In Realm, what you posted is exactly how you're supposed to declare a list
Your example compiled in the context of my project which already uses Realm Lists
The error indicated suggests that the swift compiler knows that List is a valid type and in fact knows about a function named List.init(), however, the compiler is telling you that the function isn't generic.
The only answer at that point, is to assume the compiler is using a different definition of List than you are intending. The only way this happens is if the scope you're in (your project or another type), has also defined a type List, since internal types take precedence over external types. This precedence is extended further with type nesting. If you'd like to keep Realm's List as the default list, you can nest your List in another structure to provide a namespace of sorts.
The following example is as concise as possible given the complex situation.
import RealmSwift
let myGlobalDogs = List()
let myGlobalNestedListDogs = MyClass.List()
let globalDogs = RealmSwift.List<Dog>()
class List { }
class MyClass {
class List { }
let dogs = RealmSwift.List<Dog>()
let myNestedListDogs = List() // type: MyModule.MyClass.List
let myDogs = MyModule.List() // type: MyModule.List
}
class MyOtherClass {
let dogs = RealmSwift.List<Dog>()
let myNestedListDogs = MyClass.List() // type: MyModule.MyClass.List
let myDogs = List() // type: MyModule.List
}
Fortunately, types/functions are usually different enough that you can't inadvertently use the wrong one without encountering an error eventually, such as the one you encountered.

Querying for a matching array property in Realm

Consider the following, using Realm Swift:
class Shelf : Object {
let products = List<Product>()
}
and
class Product : Object {
let string: String = ""
let Shelves = LinkingObjects(fromType: Shelf.self, property: "products")
}
Now the question is, is it possible to perform a query like:
"What are all the shelves that match EXACTLY a specific product list?" (no more, no less).
I could not find a way to compare an array.
EDIT:
If I try doing this:
let results = realm.objects(Shelf.self).filter("%# == products",products)
I get the error:
Invalid predicate:
Key paths that include an array property must use aggregate operations
Thank you.
Each Realm model class inheriting from Object conforms to the Equatable protocol by default due to the fact that Object inherits from NSObject, which conforms to Equatable. If the elements of a List conform to the Equatable protocol, the List itself conforms to it as well.
Hence, you should be able to compare List<Product> instances by using the == function and you can also directly compare Shelf objects.
However, be aware that Realm overrides the == function, see Realm object's Equatable is implementation.
If you want a custom comparison, you can override the == function yourself.
Edit: I don't think this can be done using Realms filter method. However, if you don't mind getting an Array back as a result and not a Result collection, the below method can help you.
let searchedProducts = List([product1, product2, product3]) //these are Product instances
let results = Array(realm.objects(Shelf.self)).filter{
if $0.products.count != searchedProducts.count {
return false
}
for product in $0.products {
if searchedProducts.index(of: product) == nil {
return false
}
}
return true
}
The above code has been tested in Playground with a similar class and returned correct results.

When does instantiation happens when you pass an object as an argument

Really basic question. I'm currently trying to code in a more Object Oriented way but there is something that I don't fully understand. I believe this applies to multiple programming languages not only Swift because I have seen this in some PHP code too.
I was under the impression that in order for you to access methods in a class you would need to create an instance but I'm not sure how this happens when you pass objects as parameters.
In the code below the moveDog(move:Animal) method gets an Animal object as a parameter, what I don't know understand is how can you access methods form the Animal object without instantiating the class first (print(move.run()))?
Does instantiation happens when declaring local parameters moveDog(move:Animal)? Can we say that it is the same as if we would do let move = Animal()?
class Animal{
func run(){
print("Running...")
}
}
class Dog{
func moveDog(move:Animal){
//how can the run method be called without
//having to instantiate Animal?
print(move.run())
}
}
let dog = Animal()
let buddy = Dog()
buddy.moveDog(dog)
The initialisation happens when you actually create it, e.g. let dog = Animal() or let buddy = Dog().
A quick note, you can make this code better by using the same run() function by using inheritance like so:
class Animal{
func run(){
print("Running...")
}
}
class Dog: Animal{
override func run() {
super.run()
}
}
let dog = Animal() //You don't need to instantiate this to use the Dog class
let buddy = Dog()
buddy.run()
In swift parameters are normally passed by reference (except structs) rather than being copied. So there's no initialisation for passing parameters.

Getting runtime error for having an array present even though it is not flagged for persistence

I wanted to create a list var on my Realm object class that would not persist. However, I am still receiving a runtime error even though this var is not pre-fixed with dynamic.
Is this intended? Why would Realm still care even when its not persisting?
class Test: Object {
var test = []
}
Yes, this is intended. The dynamic keyword is required for the standard property types, but Lists are structured differently, and do not normally require it.
To absolutely, explicitly set your object as non-persisted, you just need to override Object's ignoredProperties method and specify the property in there:
override static func ignoredProperties() -> [String] {
return ["test"]
}

Best way to serialize optional property which has a custom class

I would to use custom classes with optional types in realm.
In order to serialize and work with a CLLocation instance my idea is this:
class MyClass : Object {
dynamic var _coordinates :NSData?
var coordinates :CLLocation? {
get {
if _coordinates == nil {
return nil
}
// we can cache it too to avoid deserialization every time
let coordinatesObj : CLLocation = NSKeyedUnarchiver.unarchiveObjectWithData(_coordinates!) as! CLLocation
return coordinatesObj
}
set(newCoordinates) {
if newCoordinates == nil {
_coordinates = nil
} else {
_coordinates = NSKeyedArchiver.archivedDataWithRootObject(newCoordinates!)
}
}
}
...
}
Is there a better way?
Should we have some sort of protocol in Realm which allow us to return optional NSData for a specific property?
Another idea is to have a custom method like ignoredProperties which can be used to implement the logic to convert an object to NSData? and viceversa for a set of custom properties.
I don't really think there is a better way than that. If you're looking to serve more complex objects than the ones that Realm supports, you'll always need to implement some additional logic to serialize/deserialize that data.
Personally, I'm a fan of the latter method you suggested: add a new property, mark it as ignored, and then manually implement its accessors to perform the necessary logic on the related Realm-backed property.
At this time there is not another way or workaround to manage my issue.
However I've found another much clearner way to handle this needs.
In fact the main concept is the same but it uses flatMap in order to avoid some boilerplate checks.
Hope it helps.
class MyClass: Object {
dynamic var _coordinates: NSData?
var coordinates: CLLocation? {
get {
return _coordinates.flatMap(NSKeyedUnarchiver.unarchiveObjectWithData) as? CLLocation
}
set {
_coordinates = newValue.flatMap(NSKeyedArchiver.archivedDataWithRootObject)
}
}
}