Use Realm subclass as custom function parameter - ios

I am using Realm and it has function that take as an argument a class inheriting from Object (not NSObject), e.g.
realm.objects(Class)
I want to make a function that also takes Class as parameter.
I want make function that will take a class and forward it to Realm's function. I tried something like this, but it doesn't work:
func test(type: AnyClass) {
let realm = ..
realm.objects(type)
}
The Swift compiler reports an error saying that it cannot cast from AnyClass to Object.Type
I found a similar question here: Pass a Swift class as parameter, and then call a class method out of it
But maybe there is a simpler solution that doesn't require protocol implementation for every class? I want the function to make the decision of what to do by itself depending on what class it receives.
I want it to look like realm.objects(Class).

You can use the same signature: objects declaration looks like this:
func objects<T: Object>(type: T.Type) -> Results<T>
So you need a generic function constraining the type to subclasses of Realm.Object:
func test<T: Object>(type: T.Type) {
let realm = ...
realm.objects(type)
}
Then, assuming that your class (MyClass in this sample) inherits from Realm.Object, you can call it like this:
class MyClass: Object {}
test(MyClass)
If your method takes an object of that type, you can infer the type:
func test<T: Object>(element: T) {
let realm = ...
realm.objects(T.self)
}

Related

Swift init from unknown class which conforms to protocol

I'm currently working on updating a large project from Objective-C to Swift and I'm stumped on how to mimic some logic. Basically we have a class with a protocol which defines a couple functions to turn any class into a JSON representation of itself.
That protocol looks like this:
#define kJsonSupport #"_jsonCompatible"
#define kJsonClass #"_jsonClass"
#protocol JsonProtocol <NSObject>
- (NSDictionary*)convertToJSON;
- (id)initWithJSON:(NSDictionary* json);
#end
I've adapted that to Swift like this
let JSON_SUPPORT = "_jsonCompatible"
let JSON_CLASS = "_jsonClass"
protocol JsonProtocol
{
func convertToJSON() -> NSDictionary
init(json: NSDictionary)
}
One of the functions in the ObjC class runs the convertToJSON function for each object in an NSDictionary which conforms to the protocol, and another does the reverse, creating an instance of the object with the init function. The output dictionary also contains two keys, one denoting that the dictionary in question supports this protocol (kJsonSupport: BOOL), and another containing the NSString representation of the class the object was converted from (kJsonClass: NSString). The reverse function then uses both of these to determine what class the object was converted from to init a new instance from the given dictionary.
All of the classes are anonymous to the function itself. All we know is each class conforms to the protocol, so we can call our custom init function on it.
Here's what it looks like in ObjC:
Class rootClass = NSClassFromString(obj[kJsonClass]);
if([rootClass conformsToProtocol:#protocol(JsonProtocol)])
{
Class<JsonProtocol> jsonableClass = (Class<JsonProtocol>)rootClass;
[arr addObject:[[((Class)jsonableClass) alloc] initWithJSON:obj]];
}
However, I'm not sure how to make this behavior in Swift.
Here's my best attempt. I used Swiftify to try and help me get there, but the compiler isn't happy with it either:
let rootClass : AnyClass? = NSClassFromString(obj[JSON_CLASS] as! String)
if let _rootJsonClass = rootClass as? JsonProtocol
{
weak var jsonClass = _rootJsonClass as? AnyClass & JsonProtocol
arr.add(jsonClass.init(json: obj))
}
I get several errors on both the weak var line and the arr.add line, such as:
Non-protocol, non-class type 'AnyClass' (aka 'AnyObject.Type') cannot be used within a protocol-constrained type
'init' is a member of the type; use 'type(of: ...)' to initialize a new object of the same dynamic type
Argument type 'NSDictionary' does not conform to expected type 'JsonProtocol'
Extraneous argument label 'json:' in call
Is there any way for me to instantiate from an unknown class which conforms to a protocol using a custom protocol init function?
You will likely want to rethink this code in the future, to follow more Swift-like patterns, but it's not that complicated to convert, and I'm sure you have a lot of existing code that relies on behaving the same way.
The most important thing is that all the objects must be #objc classes. They can't be structs, and they must subclass from NSObject. This is the major reason you'd want to change this to a more Swifty solution based on Codable.
You also need to explicitly name you types. Swift adds the module name to its type names, which tends to break this kind of dynamic system. If you had a type Person, you would want to declare it:
#objc(Person) // <=== This is the important part
class Person: NSObject {
required init(json: NSDictionary) { ... }
}
extension Person: JsonProtocol {
func convertToJSON() -> NSDictionary { ... }
}
This makes sure the name of the class is Person (like it would be in ObjC) and not MyGreatApp.Person (which is what it normally would be in Swift).
With that, in Swift, this code would be written this way:
if let className = obj[JSON_CLASS] as? String,
let jsonClass = NSClassFromString(className) as? JsonProtocol.Type {
arr.add(jsonClass.init(json: obj))
}
The key piece you were missing is as? JsonProtocol.Type. That's serving a similar function to +conformsToProtocol: plus the cast. The .Type indicates that this is a metatype check on Person.self rather than a normal type check on Person. For more on that see Metatype Type in the Swift Language Reference.
Note that the original ObjC code is a bit dangerous. The -initWithJSON must return an object. It cannot return nil, or this code will crash at the addObject call. That means that implementing JsonProtocol requires that the object construct something even if the JSON it is passed is invalid. Swift will enforce this, but ObjC does not, so you should think carefully about what should happen if the input is corrupted. I would be very tempted to change the init to an failable or throwing initializer if you can make that work with your current code.
I also suggest replacing NSDictionary and NSArray with Dictionary and Array. That should be fairly straightforward without redesigning your code.

Dependency injection with associated types causing arguments without type names (undescores) in Swift

The situation is following: I'm using a protocol to inject dependencies and the best way I found to implement this in Swift is to use the associatedtype keyword. I am also using protocol composition since some implementations of TestProtocol need more than one dependency.
protocol TestProtocol: class {
associatedtype Dependencies
func inject(_ dependency: Dependencies)
}
protocol HasSomething {
var something: Something { get set }
}
protocol HasSomethingElse {
var somethingElse: SomethingElse { get set }
}
To use this I found that I'll need to use generics like this:
class TestService<T> where T: TestProtocol, T.Dependencies == TestService {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
self.testProtocol?.inject(self)
}
}
Now when I want to use this service somewhere else and I'm trying to initiate it I get following problem:
The parameter is displayed as _ and not as the protocol name TestProtocol.
Let's say I would use this code in a library. How would a user know (without reading the documentation of course) what type could be used in this context when he is not even knowing what protocol he has to implement?
Is there a better way on how to use dependency injection with the type actually being displayed to the user, or am I doing something wrong in the where clause of the TestService class, or is this simply not possible in the current versions of Swift?
There is nothing wrong with your code, this is simply not possible.
class TestService<T> where T: TestProtocol
The where clause means T could be anything, with the constraint that the given object must conform to TestProtocol.
The Xcode autocomplete feature only displays the resolved type when available, but it doesn't show the constraints on a generic, and unfortunately there is nothing you can do about that.
You have the exact same issue in the swift standard library, with Dictionary for example
public struct Dictionary<Key, Value> where Key : Hashable {
public init(dictionaryLiteral elements: (Key, Value)...) {
// ..
}
}
The generic Key as a constraint to Hashable, but Xcode still shows _ in the autocomplete list.
I guess Swift developers are use to this behaviour, so it won't be a big issue, even if your code is embedded in a library.
How would a user know (without reading the documentation of course) what type could be used in this context when he is not even knowing what protocol he has to implement?
Because Xcode is pretty clear about the protocol requirement.
If I try to initialize the TestService with a String I'll get the error:
Referencing initializer 'init(with:)' on 'TestService' requires that 'String' conform to 'TestProtocol'
Which is pretty self explanatory.
Actually at the time of init(with testProtocol: T) Compiler doesn't know about T of course because it is generic
if you provide directly class it will show you in suggestion
For example
class TestService<T:Something> {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
}
}
Now you will see compiler know that it need SomeThing at T
For your case For TestProtocol You can replace with with something user readable world. for next time compiler will give you provided type as suggestion
For Example
class TestService<T:TestProtocol> {
weak var testProtocol: T?
init(with testProtocol: T) {
self.testProtocol = testProtocol
}
func add(t:T) {
}
}
class Test {
init() {
let t = Something()
let ts = TestService(with: t)
}
}
In Test class you can type ts.add now it knows

How to create Singleton in swift with arguments

I learn the Swift Language and i need to create a manager like a Parse sdk.
For exemple when you initialize your Parse in app you write
Parse.setApplication("...", applicationId:"...")
And later you can write code like this
Parse.doSomething()
The method doSomething() use initial context.
Can you show me in my class should look like? I try some singleton exemple, but a have MyClass.sharedAttribute.doSomething() in case
What you have shown is no indication of singletons whatsoever, it sounds and looks more like a static class with static members and properties:
class MyStatic {
static var appIdA : String?
class func setApplicationId(a : String) {
appIdA = a
}
class func doSomething() {
print(appIdA)
}
}
MyStatic.setApplicationId("blabla")
MyStatic.doSomething() // prints Optional("blabla")
Of course there is the possibility that internally the class is a singleton, but Parse does not seem to be one, just looking at the functions it exposes.
The code comments even state
/*!
The `Parse` class contains static functions that handle global configuration
for the Parse framework.
*/

Send class type after "as!" as an argument

I need to "read" ViewController, which was sent as an argument to a function, as a VC of the specific class. So I need something like that (we get a class also from a function arguments):
let vc = vc_from_func_args as! type_from_func_args
I can pass a class to let's say isMemberOfClass() by doing that:
let klass: AnyClass = MyClass.self
vc.isMemberOfClass(klass)
But I can't do the same thing with "as" expression. It gives me an error:
klass is not a type
How can we pass class (type?) after "as" as a variable?
Given your comments, this is exactly what protocols are for. If you want a thing you can call pop on, then make that a requirement for your function. If it's easy to list all the things you need, then just put them in your protocol:
protocol Stackable {
var parent: UIViewController {get set}
var child: UIViewController {get set}
}
func push(vc: Stackable) {
// do the work
}
If you really need this to be a UIViewController that also happens to be Stackable, that's fine, too:
func pop<VC: UIViewController where VC: Stackable>(vc: VC) {
// do the work
}
Then just mark your view controllers as conforming to Stackable:
class SomeViewController: UIViewController, Stackable {
var parent: UIViewController
var child: UIViewController
...
}
If you find yourself doing a lot of as! or AnyClass, you're probably on the wrong track in Swift.
How about something like that...
if let checkedClass: MyFirstClass = vc_from_func_args as? MyFirstClass {
//It only hits here if it is MyFirstClass
}
if let checkedClass: MySecondClass = vc_from_func_args as? MySecondClass {
//It only hits here if it is MySecondClass
}
if let checkedClass: MyThirdClass = vc_from_func_args as? MyThirdClass {
//It only hits here if it is MyThirdClass
}
Also you are tying to instantiate a little bit wired :-)
Change this
let klass: AnyClass = MyClass.self
vc.isMemberOfClass(klass)
To something like this
vc.isMemberOfClass(MyClass)
You don't need to create an instance to check if another object is kind of a class :-) But just use my code from above... Its even better than this one
I discovered the same issue a little while ago, and ended up writing a downcast global function, and a protocol called Castable that includes the asType function:
protocol Castable: class {
func asType<T>(t: T.Type, defaultValue: T?, file: StaticString,
function: StaticString, line: UWord) -> T?
}
Basically, you can take any class object and write myObject.asType(newType) and it performs the cast. If the cast fails, it logs the failure to the console, reporting the types you were casting to and from, and the file name, method name, and line number where the method was called. I use it for debugging.
At any rate, the way that the asType and downcast functions are written, you can pass the type that you are casting to as a named variable, which is what your original question wanted to do.
The complete code for downcast, the Castable protocol, and the asType function are available at this link.

Override var conforming to a protocol with a var conforming to a child of the overridden var protocol

This is my inheritance structure
Protocols
protocol BaseProtocol {
}
protocol ChildProtocol: BaseProtocol {
}
Classes
class BaseClass: NSObject {
var myVar: BaseProtocol!
}
class ChildClass: BaseClass {
override var myVar: ChildProtocol!
}
I'm receiving a compiler error:
Property 'myVar' with type 'ChildProtocol!' cannot override a property with type 'BaseProtocol!'
What is the best approach to achieve this?
UPDATE
I updated the question trying to implement the solution with generics but it does not work :( This is my code (now the real one, without examples)
Protocols
protocol TPLPileInteractorOutput {
}
protocol TPLAddInteractorOutput: TPLPileInteractorOutput {
func errorReceived(error: String)
}
Classes
class TPLPileInteractor<T: TPLPileInteractorOutput>: NSObject, TPLPileInteractorInput {
var output: T!
}
And my children
class TPLAddInteractor<T: TPLAddInteractorOutput>: TPLPileInteractor<TPLPileInteractorOutput>, TPLAddInteractorInput {
}
Well, inside my TPLAddInteractor I can't access self.output, it throws a compiler error, for example
'TPLPileInteractorOutput' does not have a member named 'errorReceived'
Besides that, when I create the instance of TPLAddInteractor
let addInteractor: TPLAddInteractor<TPLAddInteractorOutput> = TPLAddInteractor()
I receive this other error
Generic parameter 'T' cannot be bound to non-#objc protocol type 'TPLAddInteractorOutput'
Any thoughts?
#tskulbru is correct: it can't be done, and this has nothing to do with your protocols. Consider the example below, which also fails…this time with Cannot override with a stored property 'myVar':
class Foo {
}
class Goo: Foo {
}
class BaseClass: NSObject {
var myVar: Foo!
}
class ChildClass: BaseClass {
override var myVar: Foo!
}
To understand why, let's reexamine the docs:
Overriding Properties
You can override an inherited instance or class property to provide
your own custom getter and setter for that property, or to add
property observers to enable the overriding property to observe when
the underlying property value changes.
The implication is that if you are going to override a property, you must write your own getter/setter, or else you must add property observers. Simply replacing one variable type with another is not allowed.
Now for some rampant speculation: why is this the case? Well, consider on the one hand that Swift is intended to be optimized for speed. Having to do runtime type checks in order to determine whether your var is in fact a Foo or a Bar slows things down. Then consider that the language designers likely have a preference for composition over inheritance. If both of these are true, it's not surprising that you cannot override a property's type.
All that said, if you needed to get an equivalent behavior, #tskulbru's solution looks quite elegant, assuming you can get it to compile. :)
I don't think you can do that with protocols
The way i would solve the problem you are having is with the use of generics. This means that you essentially have the classes like this (Updated to a working example).
Protocols
protocol BaseProtocol {
func didSomething()
}
protocol ChildProtocol: BaseProtocol {
func didSomethingElse()
}
Classes
class BaseClass<T: BaseProtocol> {
var myProtocol: T?
func doCallBack() {
myProtocol?.didSomething()
}
}
class ChildClass<T: ChildProtocol> : BaseClass<T> {
override func doCallBack() {
super.doCallBack()
myProtocol?.didSomethingElse()
}
}
Implementation/Example use
class DoesSomethingClass : ChildProtocol {
func doSomething() {
var s = ChildClass<DoesSomethingClass>()
s.myProtocol = self
s.doCallBack()
}
func didSomething() {
println("doSomething()")
}
func didSomethingElse() {
println("doSomethingElse()")
}
}
let foo = DoesSomethingClass()
foo.doSomething()
Remember, you need a class which actually implements the protocol, and its THAT class you actually define as the generic type to the BaseClass/ChildClass. Since the code expects the type to be a type which conforms to the protocol.
There are two ways you can go with your code, depending what you want to achieve with your code (you didn't tell us).
The simple case: you just want to be able to assign an object that confirms to ChildProtocol to myVar.
Solution: don't override myVar. Just use it in ChildClass. You can do this by design of the language Swift. It is one of the basics of object oriented languages.
Second case: you not only want to enable assigning instances of ChildProtocol, you also want to disable to be able to assign instances of BaseProtocol.
If you want to do this, use the Generics solution, provided here in the answers section.
If you are unsure, the simple case is correct for you.
Gerd

Resources