Swift protocol generics error(ORM implementation) - ios

I'm trying to implement a ORM layer on top of Couchbase Lite iOS 2.0, now it did removed the CBLModel apis which make it a little bit difficult to use.
I'm using a Reflection lib and its Mappable protocol(which is renamed to ORMMappable in the following code) to simplify the mapping apis.
Here's the error message:
let t = Self.cast_id_type(type: link, obj: value)
Cannot invoke 'cast_id_type' with an argument list of type '(type: ORMMappable.Type, obj: Any?)'
Expected an argument list of type '(type: D.Type, obj: Any?)'
And here's the problematic code
typealias MappableDictionary = [String : Any]
class IDString<T:ORMMappable> : NSString{
func load_object(){
}
}
struct MapMeta{
var ignores : [String] = []
var mapping : [String:ORMMappable.Type] = [:]
}
protocol ORMMappable {
var id : NSString {get set}
static var _meta : MapMeta{get}
init(dictionary: MappableDictionary) throws
}
extension ORMMappable {
init() throws{
try self.init(dictionary: [:] as MappableDictionary)
}
static func cast_id_type<D:ORMMappable>(type: D.Type,obj: Any?) -> IDString<D>?{
if let o = obj as? IDString<D>{
return o
}
return nil
}
init(dictionary: MappableDictionary) throws {
self = try construct { property in
let meta = Self._meta
if let value = dictionary[property.key] {
if let type = property.type as? ORMMappable.Type, let value = value as? MappableDictionary {
return try type.init(dictionary: value)
}
else if let link = meta.mapping[property.key]
{
let t = Self.cast_id_type(type: link, obj: value)
print(link)
//return t
return nil
}
else {
return value
}
} else {
return nil
//throw Error.missingRequiredValue(key: property.key)
}
}
}
}
A example of usage is
struct TestObject : ORMMappable{
static var _meta: MapMeta{
return MapMeta(ignores: [], mapping: ["link_id":TestObject2.self])
}
var id : NSString
var name : String?
var age : Int?
var link_id : IDString<TestObject2>?
}
IDString is holder for a link to other ORMMappable compatible class, mapping maps from String(property name) to a ORMMappable compatible class, and cast_id_type does check the mapping and trying to cast from the pointer of value to the StringID object. The error itself makes me quite confused here,
static func cast_id_type<D:ORMMappable>(type: D.Type,obj: Any?) -> IDString<D>?
D should be a ORMMappable compatible class, where I give is a value of a [String:ORMMappable.Type], but it rises ORMMappable.Type is not D.Type, how does this comes from?
Also I'm looking forward if there any better ways to do ORM in swift, currently the code does working with dynamic object creation, but when comes with ORM relation, it really drove me nuts here, just looking for ways to manage it in a easier and more manageable ways, where currently it looks like there are not much functionalities to do meta programming, a lot of other ORM libs still using objc, which is much more easier(but boilerplate) on dynamic instance creation or class inspection.
Thanks very much for the help, any hints will be real appreciated :)

Removed all generics solved the problem, it won't inferred in runtime environment.

Related

Use switch cases based on condition

I have made a file called Constants.swift. Within this, I have made a class like so...
public class WebServices {
static let getMyPlants : String = "plant/getPlants"
static let getMyOrganizations: String = "organization/getOrganizations"
}
Now whenever, I use an api anywhere in my project, I do Webservices.getMyPlants.
Now I also have a base-url for each of the API's. That is mentioned below public class WebServices.... like so..
struct envDev {
var BASEURL : String = "http://api-proj-dev.ii.the-co.com/api/"
}
Now, the base-url for Webservices.getMyOrganizations is different. I want to use a condition within struct envDev that if I have selected Webservices.getMyOrganizations, then I can give a different BASEURL. Something like...
//The below code isn't right. I just wrote it to represent the kind of solution I wish to have.
struct envDev {
var BASEURL : String = "http://api-proj-dev.ii.the-co.com/api/"
if Webservices.getMyOrganizations {
var BASEURL : String = "http://my second base-url.."
}
}
EDIT 1 Giving below the signature of APIHelper
class APIHelper: NSObject {
var API: NSString
var json: NSString
var receivedJSON: NSString?
var arrResult: NSMutableArray = []
let esmatBaseUrl = AppDelegate().currentUser //This is given in AppDelegate as `var currentUser = envDev()`
()
EDIT 2 Inclusion of baseUrl computed property in APIHelper & the error.
class APIHelper: NSObject {
var API: NSString
var json: NSString
var receivedJSON: NSString?
var arrResult: NSMutableArray = []
let esmatBaseUrl = AppDelegate().currentUser //This is given in AppDelegate as `var currentUser = envDev()`
()
var baseUrl: String {
esmatBaseUrl.baseUrl(forApi: API as String) // Here I'm getting the error as `Value of type 'envDev' has no member 'baseUrl'`
}
envDev has no way of knowing what happens in APIHelper, so you need a way to pass in the API from APIHelper to envDev. This means that BASEURL should not be a property, but a method:
func baseUrl(forApi api: String) -> String {
switch api {
case WebServices.getMyPlants: return "some url"
case WebServices.getMyOrganizations: return "some other url"
default: fatalError()
}
}
Then in APIHelper, you can add a baseUrl computed property that calls the above method:
var baseUrl: String {
esmatBaseUrl.baseUrl(forApi: API as String)
}
This would mean that you need to change all occurrences of esmatBaseUrl.BASEURL in your existing code to just baseUrl.
Also, I would suggest not using NSString, NSArray, etc in Swift. You should their Swift counterparts: String and [T].
I understood your query. You want to create an ENUM for your server-environment's, instead of hard-coding baseUrl's you probably want to use ENUMS to select different environments, right.
So accordingly, I've created an ENUM for you to add different server-environments so it will be feasible for you to use it frequently every-where.
private enum ServerEnv: String {
case stage, prod, test, my_plants, my_organization
var domainValue: String {
switch self {
case .test, .my_plants: return "http://api-testing-proj-dev.ii.the-co.com/api/"
case .stage: return "http://api-staging-proj-dev.ii.the-co.com/api/"
case .prod: return "http://api-production-proj-dev.ii.the-co.com/api/"
case .my_organization: return "http://api-my_organization-proj-dev.ii.the-co.com/api/"
}
}
}
Example :
let baseUrl = ServerEnv.my_organization.domainValue
Output => baseURL = "http://api-my_organization-proj-dev.ii.the-co.com/api/"
let baseUrl = ServerEnv.my_plants.domainValue
Output => baseURL = "http://api-testing-proj-dev.ii.the-co.com/api/"
I hope, I've solved your query here.
Happy Coding :-)

Same reference for all the objects in iOS swift

In swift 4.2 I am facing the problem while handling the two array objects, When I am removing objects from another array, the values are removed from the all the objects.
1) Below is my closure
func GetChatBotData(completion: #escaping (_ result: ChatV_1_Model) -> Void) {
var ChatBotData : ChatV_1_Model! = nil
ApiHelper.sharedSession.postLoacl("http://localhost:3000/posts/", postData: NSDictionary(), methodtype: Constant.API.httpGet) { (isError, data, errorDescription) in
DispatchQueue.main.async(execute: {
if isError == false {
ChatBotData = ChatV_1_Model.init(fromDictionary: data!)
completion(ChatBotData)
}
else {
//completion("Error to get result" as AnyObject)
completion(ChatBotData)
}
})
}
}
Now In my controller
var PKComponents = [Chatbot_V_1_DataModel]()
var ChatMessages = [Chatbot_V_1_DataModel]()
override func viewDidLoad() {
super.viewDidLoad()
GetChatBotData() {(result: ChatbotV_1_Model!) in
print("Call Plans: \(result!)")
self.PKComponents = result.data
self.ChatMessages = result.data
self.ChatMessages[0].component.removeAll()
}
In Viewdidload I am removing objects from self.ChatMessages array but it removes from all the objects like, PKComponents and result.data as well.
Note: I have seen the reference of the result is same as PKComponents and Chatmessages.
How to resolve this?
Here's the simplified example, where I can reproduce your problem:
class Component {
}
class SomeData {
var components: [Component]
init(components : [Component]) {
self.components = components
}
}
class Result {
var data: [SomeData]
init(data: [SomeData]) {
self.data = data
}
}
let someData = SomeData(components: [Component()])
let result = Result(data: [someData])
//problem begins here
let pkCompent = result.data
var chatMsgs = result.data
print(pkCompent[0].components.count)
chatMsgs[0].components.removeAll()
print(pkCompent[0].components.count)
Inorder to avoid the reference issue, convert SomeData to struct
struct SomeData {
var components: [Component]
init(components : [Component]) {
self.components = components
}
}
You have 2 suggestions.
1- Deep copy .
2- Use struct instead of class since its value type.
Incase of deep copy this is a simple example,
when you assign something to new instance use this way.
// Deep copy
var foo = Foo()
var otherFoo = Foo(foo)
rather than this.
var fee = foo // shallow copy still the same referance
Note: this is handled by swift you don't have to add any inits to the class.
component is what you are removing it from and not the Array to
be precise.
While arrays implementation in swift is using a Struct which is a
value type and not a Object type, what your array is holding, i.e
Object of Chatbot_V_1_DataModel might as well be a class thus, the
elements held in your array are references to object of type
Chatbot_V_1_DataModel.
the way you can work around this is by having Chatbot_V_1_DataModel
defined as a struct thus, a value type OR by making a deep copy of
you array and then using that copy in you closure as you modify it.
I am talking about something on these lines:
var copie = arr.map{$0.mutableCopy()}
better yet:
var PKComponents = [Chatbot_V_1_DataModel]()
var ChatMessages = [Chatbot_V_1_DataModel]()
override func viewDidLoad() {
super.viewDidLoad()
var copie = arr.map{$0.mutableCopy()} // use this copy now elsewhere!!!
GetChatBotData() {(result: ChatbotV_1_Model!) in
print("Call Plans: \(result!)")
self.PKComponents = result.data
self.ChatMessages = result.data
self.ChatMessages[0].component.removeAll()
}
This is the problem of deep copying. Either you write a complete initializer that copies everything and create a new object and use that instead of just assignment. Or use struct instead of class. But as a quick fix you can explicitly only copy the component array like this:
self.PKCOMPONENTS = results.data
self.PKCOMPONENTS.components = Array(results.data.components)

Using custom model class with Backendless in Swift

I'm trying to retrieve data from an online data storage using the func that I found online on the official Backendless docs! but when I try to use persona like a Lista(my own class) Object, I get the error: Could not cast value of type '__NSDictionaryM' (0x10c1ccfc0) to 'InLIsta_.Lista' (0x108439790).
I search over this site but the answer aren't specific for the Backendless case, so I hope that anyone can help me
this is my code (obviously I've declared all the var and let necessary to the code to run):
class Lista : NSObject {
var nome: String?
var pr: String?
var pagamento = 0
var entrato: Bool = false
var commenti: String?
var objectId: String?
var created: NSDate?
var updated: NSDate?
}
func findQ() {
Types.tryblock({ () -> Void in
let startTime = NSDate()
let found = self.backendless.persistenceService.of(Lista.ofClass()).find(self.query)
let currentPage = found.getCurrentPage()
print("Loaded \(currentPage.count) name objects")
print("Total name in the Backendless storage - \(found.totalObjects)")
for person in currentPage {
let persona = person as! Lista // here i get error
print("Restaurant <\(Lista.ofClass())> name = \(persona.nome)")
self.nomi.append(persona.nome!)
}
print("Total time (ms) - \(1000*NSDate().timeIntervalSinceDate(startTime))")
},
catchblock: { (exception) -> Void in
print("Server reported an error: \(exception as! Fault)")
}
)
}
The backendless persistence service has a method -(void)mapTableToClass:(NSString *)tableName type:(Class)type; that you need to call for each of your custom classes so they'll be used during the deserialisation.
self.backendless.persistenceService.mapTableToClass("Lista", type: Lista.self)
This needs to be done before any calls are made to use the persistence service.
Note that the classes, if not defined in obj-c, must be exported to obj-c. Note that this also means you can't have any optionals.
Ideally you should use the platform code generation to create your model class definitions to ensure all of the attributes are created with the appropriate types. A failure to map to your custom class could be caused by type mismatches or name mismatches. Optionals will always fail in the current SDK implementation.

How do I add different types conforming to a protocol with an associated type to a collection?

As an exercise in learning I'm rewriting my validation library in Swift.
I have a ValidationRule protocol that defines what individual rules should look like:
protocol ValidationRule {
typealias InputType
func validateInput(input: InputType) -> Bool
//...
}
The associated type InputType defines the type of input to be validated (e.g String). It can be explicit or generic.
Here are two rules:
struct ValidationRuleLength: ValidationRule {
typealias InputType = String
//...
}
struct ValidationRuleCondition<T>: ValidationRule {
typealias InputType = T
// ...
}
Elsewhere, I have a function that validates an input with a collection of ValidationRules:
static func validate<R: ValidationRule>(input i: R.InputType, rules rs: [R]) -> ValidationResult {
let errors = rs.filter { !$0.validateInput(i) }.map { $0.failureMessage }
return errors.isEmpty ? .Valid : .Invalid(errors)
}
I thought this was going to work but the compiler disagrees.
In the following example, even though the input is a String, rule1's InputType is a String, and rule2s InputType is a String...
func testThatItCanEvaluateMultipleRules() {
let rule1 = ValidationRuleCondition<String>(failureMessage: "message1") { $0.characters.count > 0 }
let rule2 = ValidationRuleLength(min: 1, failureMessage: "message2")
let invalid = Validator.validate(input: "", rules: [rule1, rule2])
XCTAssertEqual(invalid, .Invalid(["message1", "message2"]))
}
... I'm getting extremely helpful error message:
_ is not convertible to ValidationRuleLength
which is cryptic but suggests that the types should be exactly equal?
So my question is... how do I append different types that all conform to a protocol with an associated type into a collection?
Unsure how to achieve what I'm attempting, or if it's even possible?
EDIT
Here's it is without context:
protocol Foo {
typealias FooType
func doSomething(thing: FooType)
}
class Bar<T>: Foo {
typealias FooType = T
func doSomething(thing: T) {
print(thing)
}
}
class Baz: Foo {
typealias FooType = String
func doSomething(thing: String) {
print(thing)
}
}
func doSomethingWithFoos<F: Foo>(thing: [F]) {
print(thing)
}
let bar = Bar<String>()
let baz = Baz()
let foos: [Foo] = [bar, baz]
doSomethingWithFoos(foos)
Here we get:
Protocol Foo can only be used as a generic constraint because it has
Self or associated type requirements.
I understand that. What I need to say is something like:
doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) {
}
Protocols with type aliases cannot be used this way. Swift doesn't have a way to talk directly about meta-types like ValidationRule or Array. You can only deal with instantiations like ValidationRule where... or Array<String>. With typealiases, there's no way to get there directly. So we have to get there indirectly with type erasure.
Swift has several type-erasers. AnySequence, AnyGenerator, AnyForwardIndex, etc. These are generic versions of protocols. We can build our own AnyValidationRule:
struct AnyValidationRule<InputType>: ValidationRule {
private let validator: (InputType) -> Bool
init<Base: ValidationRule where Base.InputType == InputType>(_ base: Base) {
validator = base.validate
}
func validate(input: InputType) -> Bool { return validator(input) }
}
The deep magic here is validator. It's possible that there's some other way to do type erasure without a closure, but that's the best way I know. (I also hate the fact that Swift cannot handle validate being a closure property. In Swift, property getters aren't proper methods. So you need the extra indirection layer of validator.)
With that in place, you can make the kinds of arrays you wanted:
let len = ValidationRuleLength()
len.validate("stuff")
let cond = ValidationRuleCondition<String>()
cond.validate("otherstuff")
let rules = [AnyValidationRule(len), AnyValidationRule(cond)]
let passed = rules.reduce(true) { $0 && $1.validate("combined") }
Note that type erasure doesn't throw away type safety. It just "erases" a layer of implementation detail. AnyValidationRule<String> is still different from AnyValidationRule<Int>, so this will fail:
let len = ValidationRuleLength()
let condInt = ValidationRuleCondition<Int>()
let badRules = [AnyValidationRule(len), AnyValidationRule(condInt)]
// error: type of expression is ambiguous without more context

Create a Swift Object from a Dictionary

How do you instantiate a type dynamically based upon a lookup value in a dictionary in Swift?
Hopefully this is useful to others. It took some research to figure this out. The goal is to avoid the anti-pattern of giant if or switch statements to create each object type from a value.
class NamedItem : CustomStringConvertible {
let name : String
required init() {
self.name = "Base"
}
init(name : String) {
self.name = name
}
var description : String { // implement Printable
return name
}
}
class File : NamedItem {
required init() {
super.init(name: "File")
}
}
class Folder : NamedItem {
required init() {
super.init(name: "Folder")
}
}
// using self to instantiate.
let y = Folder.self
"\(y.init())"
let z = File.self
"\(z.init())"
// now put it in a dictionary.
enum NamedItemType {
case folder
case file
}
var typeMap : [NamedItemType : NamedItem.Type] = [.folder : Folder.self,
.file : File.self]
let p = typeMap[.folder]
"\(p!.init())"
let q = typeMap[.file]
"\(q!.init())"
Interesting aspects:
use of "required" for initializers
use of .Type to get the type for the dictionary value.
use of .self to get the "class" that can be instantiated
use of () to instantiate the dynamic object.
use of Printable protocol to get implicit string values.
how to init using a non parameterized init and get the values from subclass initialization.
Updated to Swift 3.0 syntax

Resources