Protocol Property Allowed With Realm? - swift3

I'm trying to model my data.
I have a class that contains an optional property of type ExcerciseContent.
import RealmSwift
class Excercise: Object {
var content: ExcerciseContent?
}
The idea is that an Excercise contains content, a duration, and one of two: an Audio or Text.
protocol ExcerciseContent {
var duration: Int { get }
}
protocol AudioExcerciseContent: ExcerciseContent {
var audio: String { get }
}
protocol TextExcerciseContent: ExcerciseContent {
var text: String { get }
}
I found a similar question, however I would like to know if this still applies, and what the response means by "Realm needs to know what the concrete object type that will be linked to is at initialization time."
I've declared the protocol, shouldn't Realm know the object type? Or is it that the object type could be different every time, and so that's why it can't be done?

Realm needs to know what the concrete object type that will be linked to is at initialization time..
Your content property should either be an another Realm Object or one of the supported property types.

Related

SwiftUI "Cannot use instance member 'numberOfDevice' within property initializer; property initializers run before 'self' is available" error

The bolded line (ie var text: String...) gives a "Cannot use instance member 'numberOfDevice' within property initializer; property initializers run before 'self' is available" error. Do I need an init? If so where? Is there a different solution?
struct PairView: View {
var theClass = BluetoothManager()
init() {theClass.viewDidLoad()}
var body: some View {
List {
ForEach(0..<BluetoothManager.peripheralArray.count) { number in //iterates thru 0 to array's count
ConnectionView(numberOfDevice: number) // create a ConnectionView for each number
}
}
}
}
//-------
struct ConnectionView: View {
var numberOfDevice: Int
**var text: String = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!**
// 'name' is a String property of the B.M. class's array's 'numberOfDevice index.'
var body: some View {
ZStack{
RoundedRectangle(cornerRadius: 10.0).fill(Color.blue)
Text(text).foregroundColor(Color.black)
}
}
}
You can use read-only computed property with short-hand.
var text: String {
return (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
}
The error you encountered means you can't use the numberOfDevice variable to instantiate another variable. However, you can use the number you pass to your init method.
Try the following:
struct ConnectionView: View {
var numberOfDevice: Int
var text: String
init(numberOfDevice: Int) {
self.numberOfDevice = numberOfDevice
self.text = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
}
...
}
Note: I don't recommend force-unwrapping (!). If possible try to provide a default value.
Also, BluetoothManager looks like a type and not like an instance of a class. Make sure you access the peripheralArray property on the valid object and not on the BluetoothManager type.
You can use lazy keyword for that:
lazy var text: String = (BluetoothManager.peripheralArray[numberOfDevice]?.name)!
What is lazy?
lazy means that it will postpone initialization until someone calls the variable and it will not possible if self is not initialized. So you will be sure self is ready before accessing that value.
Why?
When you call numberOfDevice, you are actually calling self.numberOfDevice, but swift is smart enough to let you not explicitly write self keyword.
The issue here is that self is not initialized yet when you are assigning a value to a variable.
So you need to make sure the variable is initialized BEFORE accessing self.

Realm Migration: Move an Object from one Object to another Object

I have three objects:
class Customer: Object {
dynamic var solution: Solution!;
...
}
class Solution: Object {
dynamic var data: Data!;
...
}
class Data: Object {
...
}
Now i need to move the Data Object from Solution to Customer so that it becomes:
class Customer: Object {
dynamic var solution: Solution!;
dynamic var data: Data!;
...
}
I have no idea how I have to implement my Realm Migration method so that everything works fine and that I wont lose data.
I did some experiments with the Realm migrations sample app and came up with this potential solution:
In a migration block, you can only interact with your Realm file via the migration object. Any attempts to directly access the Realm file mid-migration will result in an exception.
That being said, it's possible to have nested calls to migration.enumerateObjects referencing different Realm model object classes. As such, it should simply be a matter of initially enumerating through the Customer objects, and in each iteration, enumerate through the Solution objects to find the corresponding one with the right data value. Once found, it should be possible to set the Customer object with the data from the Solution object.
Realm.Configuration.defaultConfiguration = Realm.Configuration(
schemaVersion: 1,
migrationBlock: { migration, oldSchemaVersion in
if (oldSchemaVersion < 1) {
migration.enumerateObjects(ofType: Customer.className()) { oldCustomerObject, newCustomerObject in
migration.enumerateObjects(ofType: Solution.className()) { oldSolutionObject, newSolutionObject in
//Check that the solution object is the one referenced by the customer
guard oldCustomerObject["solution"].isEqual(oldSolutionObject) else { return }
//Copy the data
newCustomerObject["data"] = oldSolutionObject["data"]
}
}
}
}
})
I feel I need to stress that this code is by no means tested and guaranteed to work in its present state. So I recommend you make sure you thoroughly test it on some dummy data you wouldn't miss beforehand. :)
Swift 4, Realm 3
I had to migrate a Realm object that linked to another object. I wanted to remove the explicit link and replace it with an object ID. TiM's solution got me most of the way there, and just needed a little refinement.
var config = Realm.Configuration()
config.migrationBlock = { migration, oldSchemaVersion in
if oldSchemaVersion < CURRENT_SCHEMA_VERSION {
// enumerate the first object type
migration.enumerateObjects(ofType: Message.className()) { (oldMsg, newMsg) in
// extract the linked object and cast from Any to DynamicObject
if let msgAcct = oldMsg?["account"] as? DynamicObject {
// enumerate the 2nd object type
migration.enumerateObjects(ofType: Account.className()) { (oldAcct, newAcct) in
if let oldAcct = oldAcct {
// compare the extracted object to the enumerated object
if msgAcct.isEqual(oldAcct) {
// success!
newMsg?["accountId"] = oldAcct["accountId"]
}
}
}
}
}
}

Creating a QML type whose properties are read-only except for initialization?

I want to implement a QML type in C++. Said QML type is supposed to be loaded from a Component or similar, like so:
Component {
id: jesseMaker
JesseType {
id: doge
something: "wow"
oops: 67
yes: "hurray"
}
}
Notice how there's nothing unusual about this initialization. However, I also want the following JavaScript to be invalid at the denoted points:
function somewhereElse() {
var thing = jesseMaker.createObject(this, {"something": "yay"}); // OK
thing.oops = 12; // Should be an ERROR, as if this Q_PROPERTY had no WRITE
}
To achieve my stated goals, how can I define the relevant properties?

QML: How to use a dynamically created component with a customized internal object?

I'm looking for a way to dynamically create a component and object, and use the component. It seems like most of the examples available, such as those in the Qt documentation or other StackOverflow posts, are related to using the object returned from createObject(), whereas I want to use the component which contains the (customized) object.
I've stripped out a lot of extraneous detail (e.g. the CustomContainer gets pushed onto/popped off of StackViews), but the following code hopefully illustrates what I'm trying to do... Basically, I would like to have the CustomControl rectangle with foo = 10 and bar = 10, but it seems to load with the defaults instead. There will be multiple "custom control" types and multiple "custom container" objects so I need to be able to support this generically.
The Qt documentation talks about creation contexts, which I assume is my problem, but I'm not sure how to fix this. I'd prefer a purely QML solution, but C++ is fine if that's where the solution lies.
Main.qml:
CustomContainer {
id: myCustomContainer
}
CustomContainer {
id: myOtherCustomContainer
}
function addCustomControl( control, args ) {
var newComponent = Qt.createComponent( control )
var newObj = newComponent.createObject( myCustomContainer, args )
return newComponent
}
myCustomContainer.loaderSource = addCustomControl( "CustomControl.qml", { "foo": 10, "bar": 10 } )
myOtherCustomContainer.loaderSource = addCustomControl( "CustomControl.qml", { "foo": 20, "bar": 20 } )
CustomControl.qml:
Rectangle {
property int foo: 5
property int bar: 5
}
CustomContainer.qml:
Item {
property Component loaderSource
onLoaderSourceChanged: {
myLoader.sourceComponent = loaderSource
}
Loader {
id: myLoader
onSourceComponentChanged: {
doStuff()
}
}
}
The component does not "contain the object". The component is a prototype for objects to be instantiated. Think of it like a "type" or a class or struct in C++ vs an instance of that type.
Your code creates the component, and then creates an object from it with modified values for the properties, but the component still has its default properties, so using it as a source component will produce objects with default properties.
Furthermore, a Loader will do automatic dynamic instantiation for you. So you don't need to combine both manual and automatic, either do it manually, or leave the loader to do it.
Last, but not least, when components are instantiated by a StackView they will automatically fill it and their size will be bound to it, so it will automatically change as the StackView size changes. So just use an Item and put your content in there and layout it. Only the root item's size will be bound to the StackView size, its children items will have their own sizes.

Derived Class Method of Generic Class Template not being called

I have a generic class for making and processing JSON API requests. I pass in the TParam and TResult template parameters but when I use a derived type it's implementation is not being called.
Here is some code you can throw in a playground to illustrate:
import Cocoa
// Base class for parameters to POST to service
class APIParams {
func getData() -> Dictionary<String, AnyObject> {
return Dictionary<String, AnyObject>()
}
}
// Base class for parsing a JSON Response
class APIResult {
func parseData(data: AnyObject?) {
}
}
// Derived example for a login service
class DerivedAPIParams: APIParams {
var user = "some#one.com"
var pass = "secret"
// THIS METHOD IS CALLED CORRECTLY
override func getData() -> Dictionary<String, AnyObject> {
return [ "user": user, "pass": pass ]
}
}
// Derived example for parsing a login response
class DerivedAPIResult: APIResult {
var success = false
var token:String? = ""
// THIS METHOD IS NEVER CALLED
override func parseData(data: AnyObject?) {
/*
self.success = data!.valueForKey("success") as Bool
self.token = data!.valueForKey("token") as? String
*/
self.success = true
self.token = "1234"
}
}
class APIOperation<TParams: APIParams, TResult: APIResult> {
var url = "http://localhost:3000"
func request(params: TParams, done: (NSError?, TResult?) -> ()) {
let paramData = params.getData()
// ... snip making a request to website ...
let result = self.parseResult(nil)
done(nil, result)
}
func parseResult(data: AnyObject?) -> TResult {
var result = TResult.self()
// This should call the derived implementation if passed, right?
result.parseData(data)
return result
}
}
let derivedOp = APIOperation<DerivedAPIParams, DerivedAPIResult>()
let params = DerivedAPIParams()
derivedOp.request(params) {(error, result) in
if result? {
result!.success
}
}
The really weird thing is that only the DerivedAPIResult.parseData() is not called, whereas the DerivedAPIParams.getData() method is called. Any ideas why?
UPDATE: This defect is fixed with XCode 6.3 beta1 (Apple Swift version 1.2 (swiftlang-602.0.37.3 clang-602.0.37))
Added info for a workaround when using XCode 6.1 (Swift 1.1)
See these dev forum threads for details:
https://devforums.apple.com/thread/251920?tstart=30
https://devforums.apple.com/message/1058033#1058033
In a very similar code sample I was having the exact same issue. After waiting through beta after beta for a "fix", I did more digging and discovered that I can get the expect results by making the base class init() required.
By way of example, here is Matt Gibson's reduced example "fixed" by adding the proper init() to ApiResult
// Base class for parsing a JSON Response
class APIResult {
// adding required init() to base class yields the expected behavior
required init() {}
}
// Derived example for parsing a login response
class DerivedAPIResult: APIResult {
}
class APIOperation<TResult: APIResult> {
init() {
// EDIT: workaround for xcode 6.1, tricking the compiler to do what we want here
let tResultClass : TResult.Type = TResult.self
var test = tResultClass()
// should be able to just do, but it is broken and acknowledged as such by Apple
// var test = TResult()
println(test.self) // now shows that we get DerivedAPIResult
}
}
// Templated creation creates APIResult
let derivedOp = APIOperation<DerivedAPIResult>()
I do not know why this works. If I get time I will dig deeper, but my best guess is that for some reason having required init is causing different object allocation/construction code to be generated that forces proper set up of the vtable we are hoping for.
Looks possibly surprising, certainly. I've reduced your case to something rather simpler, which might help to figure out what's going on:
// Base class for parsing a JSON Response
class APIResult {
}
// Derived example for parsing a login response
class DerivedAPIResult: APIResult {
}
class APIOperation<TResult: APIResult> {
init() {
var test = TResult()
println(test.self) // Shows that we get APIResult, not DerivedAPIResult
}
}
// Templated creation creates APIResult
let derivedOp = APIOperation<DerivedAPIResult>()
...so it seems that creating a new instance of a templated class with a type constraint gives you an instance of the constraint class, rather than the derived class you use to instantiate the specific template instance.
Now, I'd say that the generics in Swift, looking through the Swift book, would probably prefer you not to create your own instances of derived template constraint classes within the template code, but instead just define places to hold instances that are then passed in. By which I mean that this works:
// Base class for parsing a JSON Response
class APIResult {
}
// Derived example for parsing a login response
class DerivedAPIResult: APIResult {
}
class APIOperation<T: APIResult> {
var instance: T
init(instance: T) {
self.instance = instance
println(instance.self) // As you'd expect, this is a DerivedAPIResult
}
}
let derivedOpWithPassedInstance = APIOperation<DerivedAPIResult>(instance: DerivedAPIResult())
...but I'm not clear whether what you're trying should technically be allowed or not.
My guess is that the way generics are implemented means that there's not enough type information when creating the template to create objects of the derived type from "nothing" within the template—so you'd have to create them in your code, which knows about the derived type it wants to use, and pass them in, to be held by templated constrained types.
parseData needs to be defined as a class func which creates an instance of itself, assigns whatever instance properties, and then returns that instance. Basically, it needs to be a factory method. Calling .self() on the type is just accessing the type as a value, not an instance. I'm surprised you don't get some kind of error calling an instance method on a type.