Converting a string to a class in Swift - swift

I am using Alamofire to make a GET request and am using the ObjectMapper library to convert the response into its own class in Swift.
Alamofire.request(self.REST_METHOD!, self.REQUEST_URL, headers : ["Authentication_Token" : authToken]).responseJSON { response in
if response.response != nil && response.response?.statusCode == 200 {
let json = JSON((response.result.value as? NSDictionary)!)
let classType : AnyObject.Type = NSClassFromString(entityType)! as AnyObject.Type
//let model = Mapper<classType>.map(json.rawString())
}
}
The entityType variable can be one of many types i.e. User, Student, Teacher, Staff, etc. I am trying to dynamically create a class type and create the model based on that.
However it crashes on the line let classType : AnyObject.Type = NSClassFromString(entityType)! as AnyObject.Type giving the error message:
fatal error: unexpectedly found nil while unwrapping an Optional value
Also when I uncomment the line let model = Mapper<classType>.map(json.rawString()), it gives me a compiler error:
classType is not a type
What is wrong with the above code

You're getting the error:
fatal error: unexpectedly found nil while unwrapping an Optional value
Because NSClassFromString is failing and returning nil, which then you are unwrapping, thereby causing the error.
I'm guessing that entityType contains a class name along the lines of myClass. Swift now uses namespaces, so to create a class from a string, the string must contain AppName.myClass.
You could either hardcode your app name, or use the following code to get it:
NSBundle.mainBundle().infoDictionary!["CFBundleName"] as! String
There are other problems with your code too. When NSClassFromString succeeds, it will create an instance of the class. This cannot be cast to a type. Also, you can't pass a variable as a generic in Mapper<classType>, as Swift needs to know the class type at compile time. Instead you could change the Mapper class to take the type as a parameter. For example:
Mapper.map(classInstance.dynamicType, json.rawString())
Although now that I re-read your question, you're using a library for Mapper, and are therefore probably reluctant to change it.
And looking at the doco for ObjectMapper, it needs you to create a Mapper instance - ie. instead of Mapper<MyClass>.map you need Mapper<MyClass>().map.

Related

invalid initializer call with same type 'XXXX' as parameter

enum Seat: String{
case middle
case aisle
case window
case undefined
}
let s : Seat = Seat(rawValue: Seat.middle)
doing such gives me
error: invalid initializer call with same type 'Seat' as parameter
It seems like a simple error. I searched online but didn't find anything.
The error is quite obvious.
The rawValue parameter expects a String. But you're giving it the enum type itself.
You should either do:
let s: Seat = .middle
let s = Seat(rawValue: "middle") ?? .undefined
let s = Seat(rawValue: "middle")! // Perhaps this is better, per Alexander's comment
or just for demonstration purposes:
let s = Seat(rawValue: Seat.middle.rawValue) ?? .undefined
I created this error by changing the type of my s property from String to Seat. But since the change was made at a file different from the file where the enum was defined...I got confused.

Swift is using the wrong subscript overload. How can I get it to use the right one?

I'm writing UI testing code using XCTest. Here is my test method:
func testLandingUI() {
let app = XCUIApplication()
let headerExpectedMessage = "Title"
let headerLabel = app.staticTexts[headerExpectedMessage]
XCTAssert(headerLabel.exists)
}
I'm getting this error:
ExampleUITests.swift:38:19: error: value of type 'Any?' has no member 'exists'
XCTAssert(headerLabel.exists)
^~~~~~~~~~~ ~~~~~~
What's strange about this error is that I expected headerLabel to be of type XCUIElement, not Any?.
XCUIApplication.staticTexts is an XCUIElementQuery, which has a subscript method declared thusly:
open class XCUIElementQuery : NSObject, XCUIElementTypeQueryProvider {
...
open subscript(key: String) -> XCUIElement { get }
...
}
What I believe is happening is the subscript method in XCUIElementQuery is not being selected by Swift's overload resolution. Instead, it's selecting this category on NSObject:
#interface NSObject (MyCategory)
- (id)objectForKeyedSubscript:(NSString*)key;
- (void)setObject:(id)obj forKeyedSubscript:(NSString*)key;
#end
I verified that if I remove that category from my project, the error goes away. Assume that removing that category is not possible (because it's not). Is there any way to get Swift to use the correct subscript method?
Minimal test case that shows the problem: https://www.dropbox.com/s/f0fm5ennco7t2ua/SubscriptCategoryTest.zip?dl=1
(note that the error is in the UI tests, so press command-shift-U to see the error)
EDIT: It looks like the problem only shows up if the category defines setObject:forKeyedSubscript:. Interestingly, I get a slightly different error if both getter and setter are defined vs. just the setter.
Since the compiler is confused, you need to help it....
Given this example:
let example1: Any? = "1"
let example2: Any = "2"
You have two issues (it looks like you have an optional...)
if example1.exists { // won't work - I got your error message
}
if (example2 as AnyObject).exists { // works with a cast
}
I believe if you correct identify the type of the variable it will solve your problem. It is matching the NSObject category only because it didn't match something more specific.

Collection of NSFetchRequests in Swift 3

I have an object that uses a dictionary of key paths to fetch requests. Each fetch request represents a different entity. This is all fine and working under Swift 2.3 and below. However, when I try to convert my project to Swift 3, I run into issues with the new generics for fetch requests. Fetch requests tend to have the type that they return associated with them, but my dictionary needs to have fetch requests that return all kinds of types. When I try to create a dictionary of type [String:NSFetchRequest<NSManagedObject>] or [String:NSFetchRequest< NSFetchRequestResult >] with fetch requests that are specifically of type NSFetchRequest<Message>. (Message being an NSManagedObject subclass) I get a compiler error:
Cannot convert value of type 'NSFetchRequest< Message >' to expected
dictionary
value type 'NSFetchRequest'
How can I downcast one fetch request to a more general fetch request type?
Apparently you can cast from one fetch request type to another. It's kind of clunky though. I can't imagine there being a situation where the cast would fail. In ObjC you wouldn't even need the cast.
class A: NSManagedObject {}
class B: NSManagedObject {}
let fetchRequestA = NSFetchRequest<A>()
let fetchRequestB = NSFetchRequest<B>()
let fetchRequests: [String:NSFetchRequest<NSManagedObject>] = [
"a" : fetchRequestA as! NSFetchRequest<NSManagedObject>,
"b" : fetchRequestB as! NSFetchRequest<NSManagedObject>,
]

Use of undeclared type when calling insertNewObjectForEntityForName(_ :, inManagedObjectContext: )

I'm playing around with code from objC's Core Data book but have come up against a weird bug when attempting to insert a new object into the managed object context using the following code which is an extension on NSManagedObjectContext:
/**
Helper method - avoids manual downcast result of an insert action and entity does not have to be referenced by name
- returns: new entity (NSManagedObject)
*/
public func insertObject<A: ManagedObject where A: ManagedObjectType>() -> A {
NSLog("\(A.entityName) - \(self)")
guard let obj = NSEntityDescription.insertNewObjectForEntityForName(A.entityName, inManagedObjectContext: self) as? A else
{ fatalError("Failed to insert entity into context") }
return obj
}
The insert fails and an object is not instantiated. Inspecting the managed object context (self in this case) gives the following debug information:
Printing description of self:
expression produced error: /var/folders/__/c3n7c0bd35v5f7qxv11gcg280000gn/T/lldb/25862/expr10.swift:1:46: error: use of undeclared type 'CoreData'
$__lldb__DumpForDebugger(Swift.UnsafePointer<CoreData.NSManagedObjectContext>(bitPattern: 0x116b90fa0).memory)
^~~~~~~~
/var/folders/__/c3n7c0bd35v5f7qxv11gcg280000gn/T/lldb/25862/expr10.swift:1:45: note: while parsing this '<' as a type parameter bracket
$__lldb__DumpForDebugger(Swift.UnsafePointer<CoreData.NSManagedObjectContext>(bitPattern: 0x116b90fa0).memory)
Any ideas? I'm a Core Data newbie so I'm more than clueless!
OK - found the solution. An object was being created but it couldn't be cast to the NSManagedObject subclass as I'd failed to correctly set the class and class module for the entity definition within Xcode's data model definition. I've no idea why I got such a cryptic error message however It reminds me of working with Swift 1.0...!

Cannot convert value of type 'Int' to expected argument type '_?'

Note: I'm a rookie in Swift
I'm using Former.
I'm fetching data from a realm model.
let industries = realm.objects(Industry)
Then I try to define a list of InlinePickerItem from it:
$0.pickerItems = industries.map({ industry in
return InlinePickerItem(title: industry.name, value: industry.id)
})
But XCode keeps saying: Cannot convert value of type 'Int' to expected argument type '_?', pointing to industry.id.
Am I missing something? I don't know if the issue comes from Former or from something that I don't understand in Swift. For example, what kind of type is _??
UPDATE:
After #dfri comment, attempt was unsuccessful. From my small understanding of Swift, I get that Swift gets lost. So I extracted the initialisation of the list of InlinePickerItem from the closure.
let industries = realm.objects(Industry)
let inlinePickerItems = industries.map({ industry in
return InlinePickerItem(title: industry.name, displayTitle: nil, value: industry.id)
})
let catRow = InlinePickerRowFormer<ProfileLabelCell, String>(instantiateType: .Nib(nibName: "ProfileLabelCell")) {
$0.titleLabel.text = "CATEGORY".localized
}.configure {
$0.pickerItems = inlinePickerItems
}
The error is disappeared when calling InlinePickerItem(title: industry.name, displayTitle: nil, value: industry.id) but I get something new when assigning it to $0.pickerItems which now is:
Cannot assign value of type '[InlinePickerItem<Int>]' to type '[InlinePickerItem<String>]'
Hope this will provide you with some helpful hints.
Type mismatch when assigning array to different type array
After the re-factoring of your code (after "update") its now apparent what is the source of error.
Immutable catRow is of type InlinePickerRowFormer<ProfileLabelCell, String>. From the source of [InlinePickerRowFormer] we see the that the class and its property pickerItems is declared as follows
public class InlinePickerRowFormer<T: UITableViewCell, S where T: InlinePickerFormableRow>
: ... {
// ...
public var pickerItems: [InlinePickerItem<S>] = []
// ...
}
The key here is that for an instance InlinePickerRowFormer<T,S> its property pickerItems will be an array with elements of type InlinePickerItem<S>. In your example above S is String
let catRow = InlinePickerRowFormer<ProfileLabelCell, String>
/* |
S = String */
Hence pickerItems is an array of InlinePickerItem<String> instances.
You try, however, to append the immutable inlinePickerItems to pickerItems, which means you're trying to assign an array of InlinePickerItem<Int> instances to an array with elements of type InlinePickerItem<String>; naturally leading to a type mismatch.
You can solve this type mismatch by:
Setting your catRow immutable to be of type InlinePickerRowFormer<ProfileLabelCell, Int>.