Handling initialisation failure in Swift - ios

I have a custom class which I want to initialise from a JSON object using JSONCodable https://github.com/matthewcheok/JSONCodable
So I have
public var id: String
public var name: String?
public var imageURL: NSURL?
and a failable initialiser which conforms to the JSONCodable protocol
required public init?(JSONDictionary: JSONObject) {
let decoder = JSONDecoder(object: JSONDictionary)
do {
id = try decoder.decode("id")
name = try decoder.decode("name")
if let imageURLString: String = try decoder.decode("image") {
imageURL = NSURL(string: imageURLString)
}
}
catch let error as NSError{
NSLog("\(error.localizedDescription)")
return nil
}
}
I am getting a compiler error on the 'Return nil' statement:
All stored properties of a class instance must be initialized before returning nil from an initialiser.
Is there a way round this, apart from setting dummy values? One of the properties I want to include is read-only, I really don't want to create a setter just to get round the compiler.
I am using a class, not a struct as in the JSONCodable sample code, because I want to subclass.
One possibility is to have a non failing initialiser which throws an error, but this wouldn't conform to the JSONCodable protocol.
I'm fairly new to Swift, any pointers on how to handle this would be very welcome.

Fixed with the help of this answer: Best practice to implement a failable initializer in Swift (mentioned here https://github.com/matthewcheok/JSONCodable/issues/5)
So now I have
public var id: String!
id is now implicitly unwrapped so it has a default value of nil.

Related

common functions issues while converting code from objective c to swift

Currently I have been working on a task of converting code from objective c to swift. The work was going smooth until I occured with a common resuable code that works in objective c but I haven't getting any idea how should I do that in swift.
The scenario working in objective c is.
I have a common function in my dataManager class
- (void)saveRequest:(id)request forId:(NSNumber *)requestId {
WebRequest *requestData = [[WebRequest alloc] initWithEntity:[NSEntityDescription entityForName:WEB_REQUEST inManagedObjectContext:self.context] insertIntoManagedObjectContext:self.context];
requestData.data = [request toJSON];
requestData.requestId = requestId;
requestData.timestamp = [NSDate date];
[self save];
}
in my project the request classes are already created which contains the toJSON function.
from my controller according to user changes I created the request object and passes the request object to this function and this function calls the toJSON function in the request class and everything works in objective c.
But when I convert this function in swift then it didn't support id as function input variable and if I use Any in place of id then it gives an error that Any don't have any toJSON function.
As this function is common different request objects will come from different controllers.
I don't have any idea how should I go further from hear, If anyone have any idea please help me out
Your class should be like
class WebRequest:NSObject
{
var data :Data?
var requestId: NSNumber?
var timestamp: Date?
init(entity:String , insertIntoManagedObjectContext:NSManagedObjectContext)
{
//your code here
}
}
and your code will be as follows
func saveRequest(request:Request, requestId:NSNumber)
{
let requestData = WebRequest(entity: "entityName", insertIntoManagedObjectContext:self.context)
requestData.data = request.toJSON();
requestData.requestId = requestId;
requestData.timestamp = Date()
}
and Request class in which toJson() present
class Request: NSObject
{
//has some members
func toJSON()->Data
{
return Data()
}
}
There is an existing Swift protocol, Codable (or you can do just Encodable if you want, as Codable is merely Encodable and Decodable), which is designed explicitly for representing an object in JSON (or other formats).
You then use JSONEncoder (rather than JSONSerialization, for example) to encode the object into JSON. See Encoding and Decoding Custom Types:
Consider a Landmark structure that stores the name and founding year of a landmark:
struct Landmark {
var name: String
var foundingYear: Int
}
Adding Codable to the inheritance list for Landmark triggers an automatic conformance that satisfies all of the protocol requirements from Encodable and Decodable:
struct Landmark: Codable {
var name: String
var foundingYear: Int
}
You can then do:
let landmark = Landmark(name: "Big Ben", foundingYear: 1859)
do {
let data = try JSONEncoder().encode(landmark)
print(String(data: data, encoding: .utf8)!)
} catch {
print(error)
}
That will product JSON like so:
{
"name": "Big Ben",
"foundingYear": 1859
}
See that Encoding and Decoding Custom Types for more information.
But, if you make your types Codable/Encodable, you could then retire your toJSON method entirely. There’s no need to write code to encode JSON anymore.
If you’re looking for a more tactical edit to your project as you convert it from Objective-C to Swift, you could define your own protocol, say JsonRepresentable, that has a single method requirement, your toJSON (or to whatever you’ve renamed this method during your conversion process).
protocol JsonRepresentable {
func toJSON() -> Data
}
And then, for all of the types that have implemented this method, just add this conformance.
Ideally, go back to those individual files and move the method into an extension for that protocol, e.g., for your first object type:
extension RequestObject1: JsonRepresentable {
func toJSON() -> Data {
...
}
}
And for your second:
extension RequestObject2: JsonRepresentable {
func toJSON() -> Data {
...
}
}
Etc.
is not there a simpler way rather than changing it in whole project
I would suggest that the above is best, but, if you don’t want to go back to all of those individual type declarations, you can just add conformance with an empty extension right where you defined JsonRepresentable:
extension RequestObject1: JsonRepresentable { }
extension RequestObject2: JsonRepresentable { }
As long as those types have implemented that method, these extensions will let the compiler know about their conformance to your protocol.
Anyway, this method can then use this protocol:
func save(_ request: JsonRepresentable, requestId: Int) {
let requestData = ...
requestData.data = request.toJSON()
requestData.requestId = requestId
requestData.timestamp = Date()
save()
}

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.

Convert NSManagedObjects into structs in a "generic" way (Swift)

I have a CoreDataStore class which has two generic placeholders and can be used for each entity type in the model. The idea is that it fetches an NSManagedObject subclass (based on one of the generic types) from the store, converts it into the appropriate object (based on the other generic type) and returns that object.
The purpose of this behaviour is so I'm keeping the Core Data aspects encapsulated and avoiding passing NSManagedObject instances all around the app.
Example potential usage
This is purely how the usage might look to further demonstrate what I am trying to achieve.
let personStore = CoreDataStore<ManagedPerson, Person>()
let personData = personStore.fetchSomeObject() // personData is a value type Person
I have the following code, separated over several files but shown here in a modified fashion for simplicity.
import Foundation
import CoreData
// MARK: - Core Data protocol and managed object
protocol ManagedObjectProtocol { }
class ManagedPerson: NSManagedObject, ManagedObjectProtocol {
var title: String?
}
class ManagedDepartment: NSManagedObject, ManagedObjectProtocol {
var name: String?
}
// MARK: - Simple struct representations
protocol DataProtocol {
typealias ManagedObjectType: ManagedObjectProtocol
init(managedObject: ManagedObjectType)
}
struct Person {
var title: String?
}
struct Department {
var name: String?
}
extension Person: DataProtocol {
typealias ManagedObjectType = ManagedPerson
init(managedObject: ManagedPerson) {
self.title = managedObject.title
}
}
extension Department: DataProtocol {
typealias ManagedObjectType = ManagedDepartment
init(managedObject: ManagedDepartment) {
self.name = managedObject.name
}
}
class CoreDataStore<ManagedObject: ManagedObjectProtocol, DataObject: DataProtocol> {
func fetchSomeObject() -> DataObject {
var managedObject: ManagedObject // fetch an NSManagedObject
// Error here
return DataObject(managedObject: managedObject)
}
}
The error I am receiving is when I try to initialise the struct in fetchSomeObject:
Cannot invoke initializer for type 'DataObject' with an argument list of type '(managedObject: ManagedObject)'
Obviously the compiler can't figure out that the DataObject (which is restricted to types conforming to DataProtocol) can be initialised with a ManagedObject (which is restricted to types conforming to ManagedObjectProtocol) despite it being declared as such in DataProtocol.
Is there any way to achieve this functionality? Additionally is this a reasonable approach or am I completely off the wall with this?
Update
After a bit of digging it seems that Swift generics are invariant which I believe is causing what I'm running into.
Think your CoreDataStore again, for example, CoreDataStore<ManagedPerson, Department> doesn't make any sense. Why not? Because the Department is a DataProtocol without problem, but its corresponding typealias ManagedObjectType is not ManagedPerson.
The reason why your code won't compile is just the same. Here return DataObject(managedObject: managedObject) you can't initialize an DataObject from an armbitary ManagedObject, only a DataObject.ManagedObjectType is acceptable.
So what you need is a type constraint, add this where clause, your code should work:
class CoreDataStore<ManagedObject: ManagedObjectProtocol, DataObject: DataProtocol
where DataObject.ManagedObjectType == ManagedObject>

Swift only way to prevent NSKeyedUnarchiver.decodeObject crash?

NSKeyedUnarchiver.decodeObject will cause a crash / SIGABRT if the original class is unknown. The only solution I have seen to catching this issue dates from Swift's early history and required using Objective C (also pre-dated Swift 2's implementation of guard, throws, try & catch). I could figure out the Objective C route - but I would prefer to understand a Swift-only solution if possible.
For example - the data has been encoded with NSPropertyListFormat.XMLFormat_v1_0. The following code will fail at unarchiver.decodeObject() if the class of the encoded data is unknown.
//...
let dat = NSData(contentsOfURL: url)!
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)
//it will crash after this if the class in the xml file is not known
if let newListCollection = (unarchiver.decodeObject()) as? List {
return newListCollection
} else {
return nil
}
//...
I am looking for a Swift 2 only way to test whether the data is valid before attempting .decodeObject - since .decodeObject has no throws - which means that try - catch does not seem to be an option in Swift (methods without throws cannot be wrapped AFAIK). Or else an alternative way of decoding the data which will throw an error I can catch if the decode fails. I want the user to be able to import a file from iCloud drive or Dropbox - therefore it needs to be properly validated. I cannot assume that the encoded data is safe.
The NSKeyedUnarchiver methods .unarchiveTopLevelObjectWithData & .validateValue both have throws. Is there perhaps some way that these could be used? I cannot work out how to even begin to attempt to implement validateValue in this context. Is this even a possible route? Or should I be looking to one of the other methods for a solution?
Or does anyone know an alternative Swift 2 only way of addressing this issue? I believe that the key I am interested in is probably entitled $classname - but TBH I am out of my depth with respect to trying to work out how to implement validateValue - or even whether that would be the correct route to persevere with. I have the sense that I am missing something obvious.
EDIT: Here is a solution - thanks to rintaro's great answer(s) below
The initial answer solved the issue for me - i.e. implementing a delegate.
For now however I have gone with a solution built around rintaro's additional edited response as follows:
//...
let dat = NSData(contentsOfURL: url)!
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)
do {
let decodedDataObject = try unarchiver.decodeTopLevelObject()
if let newListCollection = decodedDataObject as? List {
return newListCollection
} else {
return nil
}
}
catch {
return nil
}
//...
When NSKeyedUnarchiver encounters unknown classes, unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:) delegate method is called.
The delegate may, for example, load some code to introduce the class to the runtime and return the class, or substitute a different class object. If the delegate returns nil, unarchiving aborts and the method raises an NSInvalidUnarchiveOperationException.
So, you can implement the delegate like this:
class MyUnArchiverDelegate: NSObject, NSKeyedUnarchiverDelegate {
// This class is placeholder for unknown classes.
// It will eventually be `nil` when decoded.
final class Unknown: NSObject, NSCoding {
init?(coder aDecoder: NSCoder) { super.init(); return nil }
func encodeWithCoder(aCoder: NSCoder) {}
}
func unarchiver(unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
return Unknown.self
}
}
Then:
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)
let delegate = MyUnArchiverDelegate()
unarchiver.delegate = delegate
unarchiver.decodeObjectForKey("root")
// -> `nil` if the root object is unknown class.
ADDED:
I didn't noticed that NSCoder has extension with more swifty methods:
extension NSCoder {
#warn_unused_result
public func decodeObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) -> DecodedObjectType?
#warn_unused_result
#nonobjc public func decodeObjectOfClasses(classes: NSSet?, forKey key: String) -> AnyObject?
#warn_unused_result
public func decodeTopLevelObject() throws -> AnyObject?
#warn_unused_result
public func decodeTopLevelObjectForKey(key: String) throws -> AnyObject?
#warn_unused_result
public func decodeTopLevelObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) throws -> DecodedObjectType?
#warn_unused_result
public func decodeTopLevelObjectOfClasses(classes: NSSet?, forKey key: String) throws -> AnyObject?
}
You can:
do {
try unarchiver.decodeTopLevelObjectForKey("root")
// OR `unarchiver.decodeTopLevelObject()` depends on how you archived.
}
catch let (err) {
print(err)
}
// -> emits something like:
// Error Domain=NSCocoaErrorDomain Code=4864 "*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked" UserInfo={NSDebugDescription=*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked}
another way is to fix the name of the class used for NSCoding. You simply have to use:
NSKeyedArchiver.setClassName("List", forClass: List.self before serializing
NSKeyedUnarchiver.setClass(List.self, forClassName: "List") before deserializing
wherever needed.
Looks like iOS extensions prefix the class name with the extension's name.
Actually, it's the reason which we should dig deeply matters. There's a possible, you create a archive path named xxx.archive, then you unarchive from the path(xxx.archive), now everything is ok. But if change target name, when you unarchive, the crash occurred!!! It's because archive&unarchive the different object(the truth is we archive&unarchive target.obj, not just the obj).
so simple way is to delete the archive path or just use a different archive path. And then we should consider how avoid the crash, try-catch is our helper mentioned by rintaro.
I was having same issue. Adding #objc to class declaration worked for me.
#objc(YourClass)
class YourClassName: NSObject {
}

Swift Compiler SegFault when initializing Object from Type

I am currently working on an iOS app using Swift. I have a custom DataSource that needs to know the Type of the model that it has to provide. I have a custom Protocol, that has one method.
protocol JSONModel {
init(json: JSON)
}
Then, there are several models that implement the protocol. All of them have different properties, but are the same otherwise.
class CurrentDownload: JSONModel {
let someProperty: String
required init(json: JSON) {
someProperty = json["someProperty"].stringValue
}
}
My DataSource has a property that is of type JSONModel.Type
private let modelClass: JSONModel.Type
When i try to initialize a new instance of my modelClass i get a segmentation fault. Initialization of the model is done by
let model = modelClass(json: modelJSON)
Unfortunately, the compiler crashes on that line.
Swift Compiler Error
Command failed due to signal: Segmentation fault: 11
1. While emitting IR SIL function
#_TFC14pyLoad_for_iOS21RemoteTableDataSourceP33_56149C9EC30967B4CD75284CC9032FEA14handleResponsefS0_FPSs9AnyObject_T_ for 'handleResponse' at RemoteTableDataSource.swift:59:13
Does anybody have an idea on how to fix this or on how to work around this issue?
I believe this problem isn't too hard.
Usually with Swift segmentation faults are when you try to set the value of a constant (let) quantity, or try to set the value of something that hasn't been properly declared.
I can spot one such instance here:
required init(json: JSON) {
bytesLeft = json["someProperty"].stringValue
}
There's two things wrong with this example. You have a (designated) initialiser which terminates without setting the property someProperty and you haven't declared the variable bytesLeft.
So now the problem (which I really should have spotted before) is that * modelClass* just is not a class (or otherwise initialisable type). To cannot directly access a protocol, you can only access a class conforming to a protocol. The compiler didn't spot this because you did something sneaky with .Type.
When I say access, I mean functions and properties, including initialisers.
Only a class can create an instance of a class, which has itself as the type of the instance, and protocols can't have bonafide instances themselves.
If you think carefully about it, it is impossible to well-define what you are trying to do here. Suppose we had the protocol
protocol JSONModel {
init(json: JSON)
}
but then two classes:
class CurrentDownload: JSONModel {
let someProperty: String
required init(json: JSON) {
//Some other code perhaps.
someProperty = json["someProperty"].stringValue
}
}
class FutureDownload: JSONModel {
let differentProperty: String
required init(json: JSON) {
//Different code.
differentProperty = json["differentProperty"].stringValue
}
}
whereas before one might argue that
JSONModel.Type(json: JSON)
(this code is equivalent to your code) should compile and run because we have given an implementation of init, we now can't deny that there is confusion - which implementation should we use here?? It can't choose one, and most would argue that it shouldn't try, and so it doesn't.
What you need to do is initialise with some class.
If you are looking for a minimal class that adheres to JSONModel and no more, you'll need to write such a class e.g.
class BasicJSONModel: JSONModel {
let someProperty: String
required init(json: JSON) {
//Some other code perhaps.
someProperty = json["someProperty"].stringValue
}
}
Then do
private let modelClass: BasicJSONModel//.Type not needed with a class
let model = modelClass(json: modelJSON)
of course this is probably silly and one may just write
let model = BasicJSONModel(json: modelJSON)
Or you could just write a common superclass if the protocol's only use is for this:
class SuperJSONModel {
let someProperty: String//This is not needed of course and you could just strip down to the init only.
init(json: JSON) {
//Some other code perhaps.
someProperty = json["someProperty"].stringValue
}
}
and then is you wanted to check that some object is "conforming" to the what was the JSONModel, you simply check that it is a subclass of SuperJSONModel.

Resources