I have generic method to create object that extend protocol FromResponse.
extension FromResponse {
static func object<T>(_ response: [String: Any]?) -> T? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
let obj: T = T()
return obj
}
}
So whenever I want to call it from anywhere in a code there is no issue. Let's say:
let myObject: MyObject? = MyObject.object(response)
Work's perfectly. But sometimes I'm getting array of objects from my response so I would like to have generic parser as well:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? where T: FromResponse, T: NSObject {
guard let response = response else { return nil }
var returnArray: [T] = [T]()
for singleResponse in response {
if let object: T = T.object(singleResponse) {
returnArray.append(object)
}
}
return returnArray
}
So I expect from this method to return array of MyObject, but In fact I'm getting compiler error when I'm calling this:
let myObjects: [MyObject]? = MyObject.objects(response)
It says:
Generic parameter 'T' could not be inferred
Well, I know what does it mean but I did specify type, so this error should not happen. Also when I do this:
var typ: [MyObject] = [MyObject]()
for singleResponse in (response as? [[String: Any]])! {
let pack: MyObject? = MyObject.object(singleResponse)
typ.append(pack!)
}
It works!
Why? How to have parser that returns array of generics objects?
I don't know for sure why Swift says “Generic parameter 'T' could not be inferred”, but my guess is it has to do with array covariance.
What's covariance? Consider this:
class Base { }
class Sub: Base { }
func f(_ array: [Base]) { }
Can you pass an [Sub] to f? In Swift, you can. Because Sub is a subtype of Base, [Sub] is a subtype of [Base]. (This is called “covariance”.) So you can pass a [Sub] anywhere that a [Base] is allowed:
f([Sub]())
// No errors.
And you can return a [Sub] where a [Base] is expected:
func g() -> [Base] { return [Sub]() }
// No errors.
And you can assign a [Sub] to a [Base] variable:
let bases: [Base] = [Sub]()
// No errors.
So back to your code:
static func objects<T>(_ response: [[String: Any]]?) -> [T]? ...
let myObjects: [MyObject]? = MyObject.objects(response)
Certainly MyObject.objects(_:) must return a type that can be treated as [MyObject]?. But any subtype of [MyObject]? is also acceptable. The type is not tightly constrained. I guess this is why Swift doesn't like it.
The fix is to tell Swift explicitly what type you want, using a pattern you'll see in many places in the Swift standard library:
static func objects<T>(ofType type: T.Type, from response: [[String: Any]]?) -> [T]? ...
// Note that you might not actually have to use the `type` parameter
// in the method definition.
let myObjects = MyObject.objects(ofType: MyObject.self, from: response)
It's not clear why this method is on the MyObject class at all. Perhaps you should make it a method on [[String: Any]]:
extension Collection where Element == [String: Any] {
func objects<T>(ofType type: T.Type) -> [T]? ...
}
let myObjects = response.objects(ofType: MyObject.self)
Related
I am using ObjectMapper library to map my JSON object to Swift object. The traditional method of library is working fine for me like below code.
tmpArray1 = Mapper<UserModel>().mapArray(JSONArray: result1)
tmpArray2 = Mapper<CompanyModel>().mapArray(JSONArray: result2)
Now, I want to create a generic method to return dynamic object according to argument which i pass in that function. I want somewhat like below.
tmpArray1 = WrapperClass.shared.getModelObject(objType: UserModel, data: Any)
tmpArray2 = WrapperClass.shared.getModelObject(objType: CompanyModel, data: Any)
Here is my WrapperClass.swift class code:
open class WrapperClass: NSObject {
static let shared = WrapperClass()
open func getModelObject(objType: Mappable, data: Any) -> Any? {
// Need Help Here
return <dynamic object>
}
}
I don't know my approach is 100% right but i want somewhat like whatever object type i pass in the argument of the function i want same object type in return with mapped with ObjectMapper object. I am using Swift 4.0 version.
You can find the ObjectMapper
here.
Update:
I have tried below thing but it will not work, show an error
func getModelObject<T: Mappable>(modelType: T.Type, data: Any) -> [T]? {
if data is Array<Any> {
return Mapper<modelType>().mapArray(JSONArray: data as! [[String: Any]])
//ERROR: Use of undeclared type 'modelType'
}
return nil
}
You can achieve that by combination of generics and Type. It allows you to instantiate the mappable object with generic T (no Mapper<...> here):
func getModelObject<T: Mappable>(objType: T.Type, data: Any) -> T? {
if let data = data as? [String: Any] {
return T(JSON: data)
} else if let data = data as? String {
return T(JSONString: data)
}
return nil
}
Example of usage:
class User: Mappable {
var name: String!
var age: Int!
required init?(map: Map) {}
func mapping(map: Map) {
name <- map["name"]
age <- map["age"]
}
}
let json = "{\"name\":\"Bob\",\"age\":100}"
if let user = WrapperClass.shared.getModelObject(objType: User.self, data: json) {
print(user.name, user.age)
}
Answer with Mapper<...>:
func getModelObject<T: Mappable>(data: Any) -> [T]? {
if let data = data as? [[String: Any]] {
return Mapper<T>().mapArray(JSONArray: data)
}
return nil
}
I'm using HandyJSON library to convert json to object and i made a static function to do so and it works as i want, here is the code
static func objectFromJSONstring<T : HandyJSON>(object: T.Type, JSONString : String) -> T? {
if let obj = object.deserialize(from: JSONString) {
return obj
}
return nil
}
But when i tried to do the same for an Array of objects
static func arrayOfObjectsFromJSONstring<T : HandyJSON>(objt : T.Type, JSONString: String, objectPath: String) -> [T?]? {
if let obj = [objt].deserialize(from: JSONString) {
return obj
}
return nil
}
I get this message :
Static member 'deserialize' cannot be used on instance of type '[T.Type]'.
I want to know what i'm doing wrong and how to fix it while keeping the same implementation of passing a generic object class as a parameter.
Thanks..
Just use the generic type parameter T instead of the variable to which the argument type is being passed.
if let obj = [T].deserialize(from: JSONString) { ... }
I have this code, but it shows error:
extension Collection {
func removingOptionals() -> [Element] {
var result = [Element](); // Error: cannot call value of non-function type '[Self.Element.Type]'
self.forEach({ (element) in if let el = element { result.append(el); } });
return result;
}
}
If I removed the (), the error becomes: Expected member name or constructor call after type name.
This code is supposed to transform [String?] into [String] by discarding all the null values. Or any other optional data types.
How can I do this?
You can use flatMap {} for this, instead of creation own function. Here is example of usage:
let strings: [String?] = ["One", nil, "Two"]
print(strings.flatMap { $0 })
And result will be ["One", "Two"]
You can continue to use the flatMap behavior of the Optional as the other answer shows, but it's going to be deprecated on the next Swift iteration.
If you want to add the extension to the collection type, you need to be a create a type to box the Optional (You can't extend Collection if the type is generic, like Optional).
protocol OptionalType {
associatedtype Wrapped
func map<U>(_ f: (Wrapped) throws -> U) rethrows -> U?
}
extension Optional: OptionalType {}
extension Collection where Iterator.Element: OptionalType {
func removeNils() -> [Iterator.Element.Wrapped] {
var result: [Iterator.Element.Wrapped] = []
result.reserveCapacity(Int(self.count))
for element in self {
if let element = element.map({ $0 }) {
result.append(element)
}
}
return result
}
}
I would like to make generic class which will be able to take Parsable type, or Array of Parsable type. Logic for both are almost the same so i don't want to make two different class for this operation. Is it possible to solve it using Swift generics, or protocol associatedtype types?
protocol Parsable: class {
associatedtype Type
static func objectFromDictionary(dictionary: Dictionary<String, AnyObject>, inContext context: NSManagedObjectContext) -> Type?
func importFromDictionary(dictionary: Dictionary<String, AnyObject>)
}
class ParseOperation<T: Parsable>: NSOperation {
func execute() -> T {
}
}
class ParseOperation<T where T: SequenceType, T.Generator.Element == Parsable>: NSOperation {
func execute() -> T {
// Do parsing
}
}
This i how i would like to work:
class ParseOperation<T where T: SequenceType, T.Generator.Element == Parsable OR T: Parsable>: NSOperation {
func execute() -> T {
// Do parsing
}
}
In my current implementation i am using enum which looks little bit ugly:
class ParseOperation<T where T: NSManagedObject, T:Parsable>: NSOperation {
var responseToParse: AnyObject?
var parseType: ParseType
var parsedObjects: [T]?
init(parseType: ParseType) {}
func execute() {
var objects: [NSManagedObject] = []
if self.parseType == .Single {
if let responseToParse = self.responseToParse as? Dictionary<String, AnyObject>,
let parsedObject = T.objectFromDictionary(responseToParse, inContext: localContext) {
objects.append(parsedObject)
}
} else if self.parseType == .Array {
if let responseToParse = self.responseToParse as? Array<Dictionary<String, AnyObject>> {
for dictionary in responseToParse {
if let parsedObject = T.objectFromDictionary(dictionary, inContext: localContext) {
objects.append(parsedObject)
}
}
}
}
self.parsedObjects = objects
...
}
}
I modified #RonaldMartin 's answer to show how ParsableArray might help you. It don't need to take input of Parsable elements, just implement parse function this way:
protocol Parsable {
associatedtype T
static func parse(input: AnyObject) -> T?
}
struct ParsableArray<TElement where TElement: Parsable>: Parsable {
static func parse(input: AnyObject) -> [TElement.T]? {
guard let arrayInput = input as? [AnyObject] else {
return nil
}
return arrayInput.flatMap(TElement.parse)
}
}
I've renamed objectFromDictionary to parse because it's need to take AnyObject not the Dictionary to be able to parse array. You can add context or whatever you like to parse method, of course.
If Parsable done this way then ParseOperation becomes very simple:
class ParseOperation<T where T: Parsable>: NSOperation {
let input: AnyObject
var result: T.T?
init(input: AnyObject) {
self.input = input
}
override func main() {
result = T.parse(input)
}
}
Then, you can parse arrays this way (note: this is only to demonstrate how to create ParseOperation; S is just some Parsable struct):
let op = ParseOperation<ParsableArray<S>>(input: [["1": 5, "2": 6], ["3": 10]])
op.main()
var r: [S]? = op.result
I hope, this will help.
As far as I know, the type constraint system is not designed to handle OR constraints. However, it should still be possible to do what you're asking.
One approach is to represent both singleton Parsables and collections of Parsables under a single type that you can use to constrain ParseOperation. The neatest way to do this would be to extend Array (or SequenceType, CollectionType, etc.) to conform to the Parsable type as well, but this is not yet possible as of Xcode 7.3. You can use the same workaround from that linked question and add an intermediate class to represent Parsable arrays:
class ParsableArray: Parsable {
let array: [Parsable]
init(array: [Parsable]) {
self.array = array
}
// Parsable conformance...
}
Now, you can just use the original protocol for your type constraint:
class ParseOperation<T: Parsable>: NSOperation {
func execute() -> T {
// Do parsing
}
}
Ideally you should be able to do this:
// This DOES NOT compile as of XCode 7.3
extension Array: Parsable where Element: Parsable {
// Parsable implementation
}
However it currently doesn't work.
Currently you have to rely on a wrapper struct:
struct ParsableArray<Element: Parsable>: Parsable {
let array: [Element]
init(_ array: [Element]) {
self.array = array
}
// Parsable implementation
}
You can also implement a convenience method for [Parsable] arrays:
extension Array where Element: Parsable {
func toParsable() -> ParsableArray<Element> {
return ParsableArray(self)
}
}
So you could execute your method like this:
let array = [Parsable]()
parse(array.toParsable()) // Equivalent to: parse(ParsableArray(array))
I want to query a custom object from an NSArray, here is a function I wrote:
func retrieveObject (objects: [CustomClass], identifier : String) -> CustomClass {
var retrievedObject : [CustomClass] = objects.filter({
return $0.identifier == identifier
})
return retrievedObject.first!
}
When I use it, the resulted object seems to have lost most of the property values in that object:
let obj : CustomClass = self.retrieveObject(objectList as! [CustomClass], "one")
println("\(obj.propertyA)")
The result shows "", while printing the object from the original object list shows value:
println("\(objectList.first!.propertyA)")
What seems to be the issue?
More Information:
The objectList above is a result of an asynchronous web request, let's assume that the objects in it are problem-free because they return the correct property value when printed.
Code of one step above before the array filter:
private var objectList : [AnyObject]!
private var object : CustomClass
self.webRequest(request, onSuccess: {(objects: [AnyObject]!) -> Void in
self.objectList = objects
self.object = self.retrieveObject(self.objectList, identifier: "one")
//I tried passing both self.objectList and objects
})
Problem Solved
This is not an issue with Swift or whatever. This is a data issue. The above code works fine.
Not sure, there doesn't seem to be anything wrong with the code you've provided us. I've recreated it in playgrounds and seems to print normally.
Code:
class CustomClass {
let identifier: String
let propertyA = "Printing!"
init(identifier: String) {
self.identifier = identifier
}
}
let objectList = [CustomClass(identifier: "one")]
func retrieveObject (objects: [CustomClass], identifier: String) -> CustomClass {
return objects.filter { $0.identifier == identifier }.first!
}
let object = retrieveObject(objectList, "one")
println("\(object.propertyA)") // Prints "Printing!"
println("\(objectList.first!.propertyA)") // Prints "Printing!"
EDIT:
Simplified it a bit
Except for line:
var retrievedObject : [CustomClass] = objects.filter({
return $0.identifier == identifier
})
that should be:
var retrievedObject : [CustomClass] = objects.filter { $0.identifier == identifier;};
In your version, you pass a strange value for filter argument (because of misplaced parenthesis) that does not match awaited filter argument type: (T) -> Bool
I tested Jacobson's code and I confirm it is working.