Getting no initialisation error in swift class - swift

Hi am new to swift i am string to store user credential in user default from the struct object in a LoginViewController. Below is my code,
class User: NSObject {
//MARK: Properties
var name: String
var email: String
var loginData: LoginData
//MARK: Initialization
init(name: String, email:String, loginData: LoginData) {
self.name = name
self.email = email
self.loginData = loginData
}
}
Here LoginData is a structure
struct LoginData {
var username: String
var password: String
}
while submitting register data am assigning values as
let logindata = LoginData (username: username, password: password)
let v = User(name: nameval, email: emailval, loginData:logindata)
In LoginViewContoller I have a switch button to save user credential to NSUserdefaults I created
var logindataValue: LoginData
And storing in user defaults as follows
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(logindataValue.username, forKey: "username")
defaults.setObject(logindataValue.password, forKey: "password")
defaults.synchronize()
But in LoginViewController class showing an error as “LoginViewController has no initializers”
How to prevent this error? Is this the right approach to do in this scenario?

Swift is complaining that you aren't initializing the var logindataValue: LoginData in your LoginViewContoller. You can solve this a couple of ways.
Make logindataValue an Optional:
In your LoginViewController change the definition of logindataValue to this:
var logindataValue: LoginData?
It will be given a default initial value of nil. You'll have to unwrap it when you use it, but that's okay.
Give logindataValue a different default value:
In your LoginViewController change the definition of logindataValue to this:
var logindataValue = LoginData(username: "", password: "")
I don't recommend this approach. It doesn't seem right to create an empty LoginData object with meaningless values. It would be better to use an Optional value. But if there is some other meaningful default value that you could give, then this might work.
Add an initializer for LoginViewController:
This is the most complicated, as it is not easy to create custom initializers for subclasses of UIViewController. You will have to add an initializer that includes a logindataValue parameter that you can use to initialize logindataValue. But you will also have to override init?(coder: NSCoder) which is a pain in the you-know-what and probably not what you need.
So my advice is to make logindataValue an Optional.

Related

enum encoded value is nil while storing the class object in UserDefaults. Codable Protocol is already inherited

I am new to iOS and trying to store User object in UserDefaults. So that when the app is launched again, I can check user type and based on it, I need to navigate to relevant screen.
For that, I have created a User class as below (Codable) and it has one userType enum property!
enum UserType: Int, Codable {
case userType1 = 0
case userType2 = 1
case notDetermined = 2
init(from decoder: Decoder) throws {
let label = try decoder.singleValueContainer().decode(Int.self)
self = UserType(rawValue: label) ?? .notDetermined
}
}
class User: Codable {
public var userFullName: String? = ""
public var userType: UserType? //= .notDetermined
enum CodingKeys: String, CodingKey {
case userFullName
}
}
In my view Controller class, I am creating a new instance for User object and trying to store in user defaults as below:
let newUser = User()
newUser.userFullName = "Test"
newUser.userType = userTypeBtn.isSelected ? .userType1 : .userType2
when I print the newUser's userType, I can see proper value whichever is selected. But after that, when I am trying to store it in userDefaults as below, it returns nil for userType property.
do {
let encoded = try JSONEncoder().encode(newValue)
UserDefaults.standard.set(encoded, forKey: UserDefaultKey.currentUser)
UserDefaults.standard.sync()
} catch {
print("Unable to Encode User Object: (\(error))")
}
when I tried to print this encoded variable, and decoded it in console
JSONDecoder().decode(User.self, from: encoded).userType
it prints nil.
Please help me how can I store optional enum property in UserDefaults and retrieve it when needed using Codable
You should include userType in your CodingKeys enum:
enum CodingKeys: String, CodingKey {
case userFullName
case userType
}
Or just delete the CodingKeys enum entirely, since by default, all the properties are included as coding keys. The keys in the CodingKeys enum determines what the synthesised Codable implementation will encode and decode. If you don't include userType, userType will not be encoded, so it will not be stored into UserDefaults.
I am not getting it from Server and userType is an external property outside the JSON response
This is fine, because userType is optional. If the JSON does not have the key, it will be assigned nil. This might be a problem if you are also encoding User and sending it to the server, and that the server can't handle extra keys in the request, in which case you need two structs - one for storing to/loading from UserDefaults, one for parsing/encoding server response/request.
Remember to encode a new User to UserDefaults when you try this out, since the old one still doesn't have the userType encoded with it.
Observations
Having a custom implementation for Decodable part of enum UserType: Int, Codable is probably not the best idea. Swift compiler supports encoding/decoding enum X: Int out of the box without having you to write custom implementation for it. (In fact, starting with Swift 5.5, Swift compiler can now do this for enums that have cases with associated values as well.)
You should try to avoid having cases like .notDetermined. Either user has a type that's well defined or user.type is nil. You can easily define convenience getters on user itself to know about it's type.
Swift allows nesting of types, so having User.Kind instead of UserType is more natural in Swift.
Following implementation takes care of all of these points.
import Foundation
class User: Codable {
enum Kind: Int, Codable {
case free = 1
case pro = 2
}
public var fullName: String?
public var kind: Kind?
}
let newUser = User()
newUser.fullName = "Test"
newUser.kind = .free
do {
let encoded = try JSONEncoder().encode(newUser)
UserDefaults.standard.set(encoded, forKey: "appUser")
if let fetched = UserDefaults.standard.value(forKey: "appUser") as? Data {
let decoded = try JSONDecoder().decode(User.self, from: fetched)
print(decoded)
}
}
Above code includes definition, construction, encodeAndStore, fetchAndDecode and it does everything you need without any custom implementation.
Bonus
Above code does not print a nice description for the User. For that, you can add CustomStringConvertible conformance like this.
extension User: CustomStringConvertible {
var description: String {
"""
fullName: \(fullName ?? "")
kind: \(kind?.description ?? "")
"""
}
}
extension User.Kind: CustomStringConvertible {
var description: String {
switch self {
case .free: return "free"
case .pro: return "pro"
}
}
}
If you try print(decoded) after implementing this, you will clearly see what you want to see for User instance.
User.kind can be nil and I don't want to handle it with if let every time I need to check this from different screens in the app.
No worries, it can be simplified to this.
extension User {
var isFreeUser: Bool { kind == .free }
var isProUser: Bool { kind == .pro }
}

How do I reference fields in a self defined NSObject?

Looking for assistance since I am missing something! I have defined a new object called "User".
class User: NSObject {
var userID: String!
var fullname: String!
var imagePath: String!
}
Now I want to iterate through each object one at a time checking the values of some of the fields.
func retrieveUsers() {
let ref = Database.database().reference()
ref.child("users").queryOrderedByKey().observeSingleEvent(of: .value, with: { (snapshot: DataSnapshot) in
let users = snapshot.value as! [String: AnyObject]
self.user.removeAll()
for (key,value) in users {
// HOW DO I REFERENCE A FIELD IN THE USER OBJECT
}
}
Keep in mind that in your code, your users object is of type [String:AnyObject] since you're grabbing it from a database (Firebase?).
So, you have an untyped Dictionary to deal with.
You can reference fields on the dictionary by doing users[key] where key is a String.
Judging by your naming of variables, it looks like you're expecting users to be an array rather than a dictionary, but then you're casting it as a dictionary. Without knowing your database schema it's hard to say what's actually happening.
But, you most likely want to actually turn your [String:AnyObject] into your actual data type. There are a number of approaches to this, but you may have to write your own decoder.
You may want to add more information about what database you're actually using.
Update: Including an example method for turning your dictionary into your object:
class User: NSObject {
var userID: String
var fullname: String
var imagePath: String
required init(withDictionary dict : [String: AnyObject]) {
userID = (dict["userID"] as? String) ?? ""
fullname = (dict["fullname"] as? String) ?? ""
imagePath = (dict["imagePath"] as? String) ?? ""
}
}
Note that I'm not handling failures -- just putting in empty strings.
You can also look into storing making your model Codable compliant and try to convert in and out of JSON. See: How can I use Swift’s Codable to encode into a dictionary?

How to mock and test a UserDefaults computed property that store String?

I am trying to mock UserDefaults to be able to test its behaviour.
What is the best way to do it without corrupting the real computed property that save the user token key?
class UserDefaultsService {
private struct Keys {
static let token = "partageTokenKey"
}
//MARK: - Save or retrieve the user token
static var token: String? {
get {
return UserDefaults.standard.string(
forKey: Keys.token)
}
set {
UserDefaults.standard.set(
newValue, forKey: Keys.token)
}
}
}
You can subclass UserDefaults :
(source)
class MockUserDefaults : UserDefaults {
convenience init() {
self.init(suiteName: "Mock User Defaults")!
}
override init?(suiteName suitename: String?) {
UserDefaults().removePersistentDomain(forName: suitename!)
super.init(suiteName: suitename)
}
}
And in UserDefaultsService instead of directly accessing UserDefaults.standard you can create property based on the target that you are running. In production/staging you can have UserDefaults.standard and for testing you can have MockUserDefaults
You should add PREPROCESSOR Flag before using them
#if TESTING
let userDefaults: UserDefaults = UserDefaults.standard
#else
let userDefaults: UserDefaults = MockUserDefaults(suiteName: "testing") ?? UserDefaults.standard
#endif
One way of doing it is to wrap your UserDefaults in a protocol and expose what you need.
Then you create a an actual class which conforms to that protocol and which uses UserDefaults
You can then instantiate your UserDefaultsService with that class.
When you need to test, you can create a mock conforming to the same protocol and use that instead. That way you won't "pollute" your UserDefaults.
The above might seem like a bit of a mouthful so lets break it down.
Note that in the above I removed the "static" part as well, it didn't seem necessary, and it made it easier without it, hope that is OK
1. Create a Protocol
This should be all you are interested in exposing
protocol SettingsContainer {
var token: String? { get set }
}
2. Create an Actual Class
This class will be used with UserDefaults but it is "hidden" behind the protocol.
class UserDefaultsContainer {
private struct Keys {
static let token = "partageTokenKey"
}
}
extension UserDefaultsContainer: SettingsContainer {
var token: String? {
get {
return UserDefaults.standard.string(forKey: Keys.token)
}
set {
UserDefaults.standard.set(newValue, forKey: Keys.token)
}
}
}
3. Instantiate UserDefaultsService With That Class
Now we create an instance of your UserDefaultsService which has an object conforming to the SettingsContainer protocol.
The beauty is that you can change the provided class later on...for instance when testing.
The UserDefaultsService does not know - or care - whatever the SettingsContainer actually does with the value, as long as it can give and take a token, then the UserDefaultsService is happy.
Here's how that looks, note that we are passing a default parameter, so we don't even have to pass a SettingsContainer unless we have to.
class UserDefaultsService {
private var settingsContainer: SettingsContainer
init(settingsContainer: SettingsContainer = UserDefaultsContainer()) {
self.settingsContainer = settingsContainer
}
var token: String? {
get {
return settingsContainer.token
}
set {
settingsContainer.token = newValue
}
}
}
You can now use a new UserDefaultsService like so:
let userDefaultsService = UserDefaultsService()
print("token: \(userDefaultsService.token)")
4 Testing
"Finally" you say :)
To test the above, you can create a MockSettingsContainer conforming to the SettingsContainer
class MockSettingsContainer: SettingsContainer {
var token: String?
}
and pass that to a new UserDefaultsService instance in your test target.
let mockSettingsContainer = MockSettingsContainer()
let userDefaultsService = UserDefaultsService(settingsContainer: mockSettingsContainer)
And can now test that your UserDefaultsService can actually save and retrieve data without polluting UserDefaults.
Final Notes
The above might seem like a lot of work, but the important thing to understand is:
wrap 3rd party components (like UserDefaults) behind a protocol so you are free to change them later on if so needed (for instance when testing).
Have dependencies in your classes that uses these protocols instead of "real" classes, that way you - again - are free to change the classes. As long as they conform to the protocol, all is well :)
Hope that helps.
A very good solution is to not bother creating a mock or extracting a protocol. Instead init a UserDefaults object in your tests like this:
let userDefaults = UserDefaults(suiteName: #file)
userDefaults.removePersistentDomain(forName: #file)
Now you can go ahead and use the UserDefaults keys you already have defined in an extension and even inject this into any functions as needed! Cool. This will prevent your actual UserDefaults from being touched.
Brief article here
Instead of mocking UserDefaults, you should check the value you persist in UserDefaults, should get retrieved from the same user defaults by accessing using same key. the test case should look like this.
func testWhenTokenIsSavedInUserDefaults_ReturnSameTokenAndVerify {
let persistenceService = UserDefaultsService()
persistenceService.token = "ABC-123"
let defaults = UserDefaults.standard
let expectedValue = defaults.value(forKey: "partageTokenKey")
XCTAssertEquals(persistenceService.token, expectedValue)
}

Making Realm & Unbox play nice

I am learning to parse JSON in Swift, coming from Android/Java, and I am using Unbox by John Sundell to help me with this, which reminds me of GSON.
Reference: Unbox pod
I use Realm as a database to store data locally.
Reference: Realm.io
It would be great to find a workflow to parse a class with JSON and save it to Realm. I don't want to have a struct that implements Unboxable AND a class that implements Object (Realm), because then I have to reflect the two. That isn't too much work for my current project, but it is kinda ugly...
Did any of you try a similar workflow?
I don't think you need two separate types. My suggestion is to create your objects as Swift classes that inherit from Realm's Object class, and then also conform them to the Unboxable protocol that Unbox offers. (Although the examples on Unbox's page use struct models, there's nothing in the code or documentation that indicates that classes wouldn't work.)
Realm model objects work just like any other classes: in addition to defining whatever properties on the objects you'd like stored in the database, you can also define methods and initializers, and even specify properties that you want Realm to ignore. This allows you to create an object that both serves as a Realm model and also a JSON model compatible with Unbox.
A more concise approach that doesn't require to override required initialisers (based on a tweet by Marin Todorov):
class Car: Object, Unboxable {
dynamic var vendor: String = ""
dynamic var modelName: String = ""
dynamic var electric: Bool = false
required convenience init(unboxer: Unboxer) throws {
self.init()
self.vendor = try unboxer.unbox(key: "vendor")
self.modelName = try unboxer.unbox(key: "modelName")
self.electric = try unboxer.unbox(key: "electric")
}
}
Here is an example that works perfectly for me:
class ProviderRealm: Object, Unboxable {
dynamic var identifier: String = "demo"
dynamic var name: String?
dynamic var logo: String?
/// Initializer used for unboxing of JSON string
required init(unboxer: Unboxer) throws {
self.identifier = (try? unboxer.unbox(key: "identifier")) ?? "demo"
self.name = try? unboxer.unbox(key: "name")
self.logo = try? unboxer.unbox(key: "logo")
super.init()
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
required init() {
super.init()
}
required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}
override static func primaryKey() -> String? {
return "identifier"
}
}

Swift dynamictype initialisation with dynamic protocol type

I have a number of structs which implement a Resource protocol. This defines that they must have a variable extendedInfo which conforms to ExtendedInfo protocol to provide a way to initialise them with json via init(json: [String: AnyObject]. I'm trying to provide a way to dynamically instantiate these, with JSON, providing the right type of ExtendedInfo and assign it to the struct's extendedInfo variable. However, I'm getting a Argument labels '(json:)' do not match any available overloads error when trying to instantiate them via their dynamicType
protocol Resource {
associatedtype ExtendedInfoTypeAlias: ExtendedInfo
var extendedInfo: ExtendedInfoTypeAlias? { get set }
}
protocol ExtendedInfo {
init(json: [String: AnyObject])
}
struct User: Resource {
typealias ExtendedInfoTypeAlias = UserExtendedInfo
let name: String = "Name"
var extendedInfo: UserExtendedInfo?
}
struct UserExtendedInfo: ExtendedInfo {
let age: Int?
init(json: [String: AnyObject]) {
age = json["age"] as? Int
}
}
let user = User()
let sampleJSON = ["age": 50]
let userExtendedInfo = user.extendedInfo.dynamicType.init(json: sampleJSON) // Argument labels '(json:)' do not match any available overloads
user.extendedInfo = userExtendedInfo
Any ideas guys? Thanks
First of all, you don't need to explicitly define the type of ExtendedInfoTypeAlias in your struct implementation – you can just let it be inferred by the type you provide for extendedInfo.
struct User: Resource {
let name: String = "Name"
var extendedInfo: UserExtendedInfo?
}
Second of all, you can just use the protocol's associated type of your given struct's dynamicType in order to use your given initialiser. For example:
user.extendedInfo = user.dynamicType.ExtendedInfoTypeAlias.init(json: sampleJSON)
print(user.extendedInfo) // Optional(Dynamic_Protocols.UserExtendedInfo(age: Optional(50)))
As for why your current code doesn't work, I suspect it's due to the fact that you're getting the dynamicType from an optional – which is preventing you from calling your initialiser on it.
I did find that the following works, even when extendedInfo is nil. (This is a bug).
user.extendedInfo = user.extendedInfo!.dynamicType.init(json: sampleJSON)
Change:
let user = User()
To:
var user = User()
and try this:
user.extendedInfo = UserExtendedInfo(json: sampleJSON)