How to reflect not managed properties of NSManagedObject - swift

I am searching how to get not managed property names and types of a NSManagedObject subclass.
here is few sample code to help me to ask my question :
#objc(Operation)
public class Operation : NSManagedObject {
#NSManaged var name: String
#NSManaged var amount: NSNumber
}
#objc(Account)
public class Account: NSManagedObject {
#NSManaged var bic: String
#NSManaged var number: String
#NSManaged var operations: Set<Operation>
#NSManaged var servicesSubscriptions: Set<ServiceSubcription>
// and more.
}
extension Account
{
public var lastOperation : Operation {
get
{
return self.operations.last
}
set(value)
{
self.operations.insert(value)
}
}
}
I have found many ways to get property names using reflect() function. reflect() do not work with NSManagedObject at all. (like this simple one)
edit
I have found examples with class_copyPropertyList function, that retrieve correctly property names, but don't found yet how to get types. Thank to Tom Harrington comment. (see that sample)
I have found many ways to get Attributes (or relations) of managed objects using NSEntityDescription. (like this one). Which work and get back bic and number, but not lastOperation.
edited
updated code sample to match better to reality
So my question is :
How to get back my lastOperation property, and its type, dynamically at run time ?
edit, what i am trying to do
I am parsing json, dnamically using reflection.
I need the type (or type name) of a property knowing only its name (i have "lastOperation", and need to get back Operation, or "Operation"). Once i get the type i can instanciate an object, then populate its own properties, using same mechanism (recursively).
Thank you for any help

When you get the list of properties using class_copyPropertyList, you can iterate through the list to look at each property in turn:
var propertyCount : UInt32 = 0
let properties = class_copyPropertyList(Account.self, &propertyCount)
for var i=0; i<Int(propertyCount); i++ {
let property = properties[i]
let propertyName = String(UTF8String: property_getName(property))
....
}
The type of each property is contained in one of the property attributes, as a string:
let propertyType = property_copyAttributeValue(property, "T")
let propertyTypeString = String(UTF8String: propertyType)
For your lastOperation property the string will look something like #\"Operation\". You'll have to clean up that string a little to get Operation.
I wrote a blog post a while ago describing something similar to what you're trying to do. The code is in Objective-C but all the functions, methods, etc are the same.

There is no way that I know of when it comes to a NSManagedObject. However, I would suggest creating title as a transient property inside of your model and then it will show up as part of the entity description.

Related

Is there a way to reuse model within itself?

I have an app that stores User (UserModel) Friend list. if a friend clicks one user, its type is the same type (UserModel). In Swift it wouldnt allow using the model recursively, giving me this error:
"Value type 'OwnerModel' cannot have a stored property that recursively contains it"
import Foundation
struct OwnerModel: Codable {
var ownerId: Int
var ownerEmail: String
var ownerUserName: String
var ownerCommonName: String
var ownerBirthDate: String
var ownerCountry: String
var ownerBdayReminderId: Int
var ownerIsVerified: Bool
var ownerIsOnline: Bool
var ownerIsEventGreeted: Bool
var ownerIsBirthdayGreeted: Bool
var ownerAllowGreeting: Bool
var ownerFriends: OwnerModel
}
Is there a way I can reuse the OwnerModel under ownerFriends?
This can't work because structs are value types. So each OwnerModel would have to have a OwnerModel inside it, which would have to have an OwnerModel inside it, which would have to have an OwnerModel inside it.... This can never resolve. Since you've marked this Codable, try to write the JSON you expect to encode this to.
That said, ownerFriends seems plural, which would suggest [OwnerModel], and that's not a problem, since you could have zero of them:
struct OwnerModel: Codable {
...
var ownerFriends: [OwnerModel]
}
Remember again, however, that structs are value types. So each OwnerModel is just a value. It's not a reference to any other object. If you want to refer to other owners, you may want to store IDs rather than the actual object (or use classes in order to create references).

How do I make CoreData models conform to a protocol?

I'm trying to make a persistence framework-agnostic codebase. i.e. I want protocol-oriented programming, but I'm having trouble making that work with CoreData.
Say I have a protocol:
public protocol BookItem {
var title: String { get }
var filename: String { get }
var createdAt: Date { get }
var content: BookItemContent? { get }
}
and another protocol
public protocol BookItemContent {
var bookItem: BookItem { get }
var data: Data { get }
}
When I make concrete types in CoreData, I might have:
class CDBookItem: NSManagedObject, BookItem {
#NSManaged public var title: String?
#NSManaged public var filename: String?
#NSManaged public var createdAt: NSDate?
#NSManaged public var content: CDBookItemContent?
}
class CDBookItemContent: NSManagedObject, BookItemContent {
#NSManaged public var data: NSData?
#NSManaged public var bookItem: CDBookItem?
}
I have 3 issues here:
1) Now I have multiple properties with different return types. (e.g. the .content property)
How do I best solve this? The worst and dirty way is to rename all the Core-Data attributes, then wrap them to conform.
The other way I can see is to manually generate the NSManagedObject subclasses, then change the flavour that the CoreData codegen is creating, so to remove the optionality. Still, then I'm left with content: BookItemContent and content: CDBookItemContent, so I don't know if there's an elegant way to do what I'm hoping to do here. I was hoping I could just declare that the NSManagedObject subclass conforms to its intended protocol type and the compiler figures this out.
2) Core Data codegen uses 'NSObject' types, instead of their modern equivalent. (i.e. NSData and not Data, NSDate and not Date). Can I just modify the codegen files to remove the "NS" and get free bridging?
3) Is it known that CoreData always creates optional object properties in the codegen, even if you don't want them to be? Is the solution literally to overwrite the manually generated files?
In the end, I'm finding Protocol-oriented Programming is not exactly as promised. I was hoping almost an entire codebase could be done in this way. In the end the solution was to make NSManagedObject subclasses conform to a protocol by having to explicitly write accessors for the protocol's properties that wrap some attribute on the core data model. i.e. return myManagedObject.contentObject as! BookItemContent

Alternatives to an array of T swift

In my Quiz app I initialize quizzes, and the providing class does not know the format of the questions before being provided them (although they are constrained by a QuestionProtocol):
public protocol QuestionProtocol {
init?(fields: [String] )
var description: String {get}
var question: String {get}
var solution: String {get}
var explainAnswer: String {get}
var answered: Int {get set}
var qa: String {get}
var qb: String {get}
var qc: String {get}
var qd: String {get}
}
And I can initialize the quizzes and return them easily enough through a method with the signature
public func initializeQuizzes<T: QuestionProtocol>(with type: T.Type, withCompletionHandler completion: ((Result<[Quiz<T>], Error>) -> Void)?)
However to provide these quizzes is expensive (an API call or a SQL retrieval) so I want to store these quizzes and retrieve them separately from a suitable function with signature
public func getNextQFromSet<T: QuestionProtocol>(with type: T.Type) -> (question: T, answers: [String])?
The problem I have is storing these questions which are of type T.
They are linked to a Quiz object:
public class Quiz<T> {
private let questions : [T]
private let name : String
init(name: String, questions: [T]) {
self.name = name
self.questions = questions
}
public func getQuestions() -> [T] {
return questions
}
func getName() -> String {
return name
}
}
So I'm able to store them as quizzes that conform to the QuestionProtocol
private var quizzes = [Quiz<QuestionProtocol>]()
But then I lose the extra information I want to store in the question.
I can store Any, but I believe that is bad practice
private var anyquizzes = [Quiz<Any>]()
Ideally I would like to store T i.e.
Quiz<T>
but that seems to be impossible in Swift.
Because these classes are in a pod they have no way of knowing about the internal workings of a Question, and are provided these at runtime hence the use of generics and the difficulties in storing these questions.
I can't think of a way to improve the design of the App (more specifically the Pod) - I want to initialize the quizzes once and once only and then run functions like getNextQFromSet() to retrieve a relevant question - which obviously depends on me knowing the type of the question (which I do not know before runtime).
For clarity here is a link to the Pod: https://github.com/stevencurtis/QuizManager
How can I store an array containing these questions without knowing the type?
To be short, I think it makes sense to remove QuestionProtocol and replace it with plain data structure struct Question.
Before I explain my point of view, I want to note that even though I looked at the pod, I still do not know all the requirements, so I might be wrong.
Let's try to have a look at the problem from design perspective instead of programming language perspective.
What is the reason of having QuestionProtocol? Could it be replaced with, let's say, object instead? Why do those properties should be polymorphic? Of course implementation details should be hidden, but hiding data is not about protocols or additional function layers, is about abstractions.
Let's convert QuestionProtocol to Question object for now and think about an abstraction. If there is a real abstraction, then there should an object that hides the data (details) and expose functions that manipulate that data. But there is no functions in Question object and it means that there is no real abstraction behind.
Finally, It means that Question entity most likely is a plain data structure with public properties and could be defined as struct Question.
Having this Question struct now, you can define quizzes as Quiz<Question> and use it to save and retrieve the data.
In addition, I think it worth to point out two things which could simplify and potentially improve design and implementation:
Why does SQLiteManager knows something about concrete question (depends on QuestionProtocol)? I think it makes sense to introduce some generic DBObject or at least plain dictionary [String: Any] which SQLiteManager would know how process and then insert. Then Repository could transform Question data structure into DBObject on some level of composition and pass it to SQLiteManager.
When using generics, in most cases there is no need to define additional type: T.Type parameter. Once generic is defined you can use it as [T], T.init, etc. If you still need a metatype (T.Type) you can get by T.self.
Hope this helps!
Update:
There is great example of Quiz app created with TDD and modular design: Quiz App. There is also a video series explaining design and creation process step by step.
How can I store an array containing these questions without knowing
the type?
To my knowledge you can't. As rraphael pointed out in his comment generics aren't resolved at runtime. Furthermore Arrays in swift are designed to hold a single type:
Specifically, you use the Array type to hold elements of a single type, the array’s Element type.
So whatever you do you'll have either an array of Any or maybe QuestionProtocol but nothing more dynamic than that : the type will be resolved at compilation time
You may be able to redesign your QuestionProtocol to suit your needs but without any information on the different types of question it's a bit difficult to help you more since it is an architecture matter.
You can use enum with associated values for describing types. For example:
struct QuestionTypeA { }
struct QuestionTypeB { }
struct QuestionTypeC { }
enum Question {
case typeA(question: QuestionTypeA)
case typeB(question: QuestionTypeB)
case typeC(question: QuestionTypeC)
}
And then:
public class Quiz {
private let questions : Question
private let name : String
...
And store an array of Quiz without generic
private var anyquizzes = [Quiz]()
You wouldn't be able to store a Quiz<T> and a Quiz<U> in the same array using either of those types. They're just not the same type.
If you have an Array<QuizProtocol>, you can just match against your known types in a switch-case statement:
var quizzes: [QuizProtocol] = ...
for quiz in quizzes {
switch quiz {
case let someQuiz as SomeQuiz:
...
case let someOtherQuiz as SomeOtherQuiz:
...
default:
... // couldn't cast to any known type; do some fallback logic
....
}
}
where SomeQuiz and SomeOtherQuiz conform to QuizProtocol (though strictly speaking, you could match against any type).

Swift, Core Data, Optional Integer16 and keyPath

I have an entity in CoreData which has an optional property of type Integer 16. It can genuinely be nil and in my application I want to refer to it as an Int? type. As Int? (or for that matter Int16?) isn't a recognised Objective-C type, the compiler throws a bit of a wobbly. I want to avoid using code like NSNumber?.intValue throughout so I've actually set up my ManagedObject type with custom accessors for this property. My question relates to identifying the property through #keyPath rather than a static string. In Core Data the field is named 'pin' on entity 'User'. Here's the code I have:
class User: NSManagedObject {
// MARK: - Properties
static let pinKey = "pin"
#NSManaged internal(set) var name: String
#NSManaged fileprivate var primitivePin: NSNumber?
internal(set) var pin: Int? {
get {
willAccessValue(forKey: #keyPath(pin)) // THIS LINE ERRORS
let value: Int? = primitivePin.map { $0.intValue }
didAccessValue(forKey: User.pinKey)
return value
}
set {
willChangeValue(forKey: User.pinKey)
primitivePin = newValue.map { NSNumber(value: Int16($0)) }
didChangeValue(forKey: User.pinKey)
}
}
}
The line in error is what I 'want' to achieve but of course the var pin isn't an obj-c type and the compiler complains, so I have defined the static constant pinKey as you can see. #keyPath feels like the right way to go about it, and the entity does have a field called pin, but in this scenario is the only option open to me to use a static value?
In #keyPath you have to specify property name. If you don't have defined property called pin, you will receive an error. In your case you have to use #keyPath(User.primitivePin). I believe this should work.
Also, i guess, calling map is redundant here. You can write directly let value = primitivePin?.intValue and so on.
The answer is....with custom properties/accessors #keyPath can't used as there is no defined #NSManaged property for it - as Maksym points out. However, you can't use the defined primitive for it either, instead using the property name as a String as also shown in the code above (User.pinKey)

Use class func on class parameter in Swift

I am trying to use a class func to set a title for a book, however it's not working. Please see my code below:
import Foundation
class Book: NSObject {
var bookTitle: String = ""
var bookPage: String = ""
override init(){
print("Book object has been created")
}
class func setPageTitle(title: String)
{
bookTitle = title //I get the error here
}
}
I want to make it mandatory to set a pageTitle when a Book object is created.
Can someone please help me ?
The best way to set any required property/attribute is when you initialize it. So try coding your class this way:
class Book: NSObject {
var bookTitle: String = ""
var bookPage: String = ""
init(title:String) {
bookTitle = title
print("Book object has been created")
}
}
Several notes:
Your error is because you declare class in your setPageTitle function. That makes no sense.
There are better (and other) ways to set the bookTitle, including after initialization. But you specifically wanted to make sure you have a title when initializing, so there you go.
There are definitely better ways to maintain the bookTitle attribute. (Most languages teach you to hold a price variable to start with.) I'm mostly trying to give you a way to initialize it with the class.
You probably don't need (or have) any superclass call to make, but you also may not need to make your Book class a NSObject either.