Swift optional chaining with non-nil value - swift

I have the following program which works fine -
class Person {
var pet: Pet?
}
class Pet {
var name: String
var favoriteToy: Toy?
init(name: String) {
self.name = name
}
}
class Toy {
var name: String
init(name: String) {
self.name = name
}
}
let q = Person()
// Pet(name:"goofy")
// Toy(name:"teddy")
if let someToy = q.pet?.favoriteToy?.name {
print("This person's pet likes \(someToy).")
} else {
print("This person's pet does not have a favorite toy")
}
Prints:
This person's pet does not have a favorite toy
How to use the above code modified to print "This person's pet likes teddy."?
I know I will have not be able to use if let as there will be nothing to unwrap. So I have to write something like this instead of if let:
let someToy = q.pet.favoriteToy.name
print("This person's pet likes \(someToy).")
Should print "This person's pet likes the teddy."
I also know I have to put non-nil value something like this:
class Person {
var pet = Pet ()
}
Still I am having initialization problems. How to go about it?

This should solve your immediate needs...
let man = Person()
let goofy = Pet(name: "goofy")
let squeaky = Toy(name: "squeaky toy")
goofy.favoriteToy = squeaky
man.pet = goofy
But if a person is usually going to be initialized with a pet and a toy, and both of those classes are initialized with a string, then you might want to define a convenience initializer:
class Pet {
var name: String
var favoriteToy: Toy?
init(name: String) {
self.name = name
}
}
class Toy {
var name: String
init(name: String) {
self.name = name
}
}
class Person {
var pet: Pet?
convenience init(petName: String, toyName: String) {
self.init()
let toy = Toy(name: toyName)
let pet = Pet(name: petName)
pet.favoriteToy = toy
self.pet = pet
}
}
func test() {
let bill = Person(petName: "goofy", toyName: "squeaky ball")
guard let toyName = bill.pet?.favoriteToy?.name else { return }
print(toyName)
}
test()
One more thing: optional chaining can be combined with a guard statement to great effect in Swift.

Use ? = nil in your initializer to pass in some optional parameters.
class Person {
var pet: Pet?
init(pet: Pet? = nil) {
self.pet = pet
}
}
class Pet {
var name: String
var favoriteToy: Toy?
init(name: String, toy: Toy? = nil) {
self.name = name
self.favoriteToy = toy
}
}
class Toy {
var name: String
init(name: String) {
self.name = name
}
}
let q = Person(pet: Pet(name: "goofy", toy: Toy(name: "teddy")))
// Pet(name:"goofy")
// Toy(name:"teddy")
if let someToy = q.pet?.favoriteToy?.name {
print("This person's pet likes \(someToy).")
} else {
print("This person's pet does not have a favorite toy")
}

Related

[Core Data]: data: <fault>

The questions parameter in the function is full, but I cannot transfer assignments to questionsCD.questions?.questionList.
The id and title variables are not nil. Those variables are working in a healthy way.
In Console output, questions return nil.
I cannot assign the questionList variable in the QuestionsNSSecureCoding class, so I think it returns nil. Why ?
You can examine the Core Data Entity image to see the questionsCD.questions?.questionList in more detail.
Core Data Save Function:
func saveSelectedQuestion(questionTitle: String, id: String, questions: [QuestionList]) {
let questionsCD = QuestionCD(context: persistentContainer.viewContext)
questionsCD.title = questionTitle
questionsCD.id = id
questionsCD.questions?.questionList = questions
print("nil test: \(questionsCD.questions?.questionList ?? [])")
do {
try persistentContainer.viewContext.save()
} catch let error {
print("Failed to save selected category: \(error.localizedDescription)")
}
}
Core Data Get Function:
func getSelectedQuestion(questionID: String) -> [QuestionCD] {
let fetchRequest: NSFetchRequest<QuestionCD> = QuestionCD.fetchRequest()
let search = NSPredicate(format: "id == %#", questionID)
print("search: \(search)")
fetchRequest.predicate = search
fetchRequest.returnsObjectsAsFaults = false
print("request predicate: \(String(describing: fetchRequest.predicate))")
do {
return try persistentContainer.viewContext.fetch(fetchRequest)
} catch let error {
print("get hata: \(error.localizedDescription)")
return []
}
}
Console Output:
selectedQuestionCD [<QuestionCD: 0x2800e8e60> (entity: QuestionCD; id: 0x9d00d28ec7359eb0 <x-coredata://089E80AC-0E4F-4303-BF8F-47C31EC70ED4/QuestionCD/p2>; data: {
id = "agustos_test_1";
questions = nil;
title = "A\U011fustos Test 1";
})]
Core Data Entity:
Questions NSSecure Coding:
public class QuestionsNSSecureCoding: NSObject, NSSecureCoding {
public static var supportsSecureCoding: Bool = true
var questionList: [QuestionList]
required init(questions: [QuestionList]) {
self.questionList = questions
}
public func encode(with coder: NSCoder) {
coder.encode(questionList, forKey: "questionList")
}
required public init?(coder: NSCoder) {
questionList = coder.decodeObject(of: NSArray.self, forKey: "questionList") as? Array<QuestionList> ?? []
}
}
Questions Value Transformer:
#objc(QuestionsValueTransformer)
final class QuestionsValueTransformer: NSSecureUnarchiveFromDataTransformer {
static let name = NSValueTransformerName(rawValue: String(describing: QuestionsValueTransformer.self))
override static var allowedTopLevelClasses: [AnyClass] {
return [QuestionsNSSecureCoding.self]
}
public static func register() {
let transformer = QuestionsValueTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}
My Custom Model:
class QuestionContainer: Codable {
var questionCategories: [Question]
init(questionCategories: [Question]) {
self.questionCategories = questionCategories
}
}
class Question: Codable, Identifiable {
var title: String
var id: String
var questions: [QuestionList]
init(title: String, id: String, questions: [QuestionList]) {
self.title = title
self.id = id
self.questions = questions
}
}
class QuestionList: Codable, Identifiable {
var id: String
init(id: String) {
self.id = id
}
}
I think the problem is that questionCD.questions is nil, so the line
questionsCD.questions?.questionList = questions
does nothing. You need to create an instance of QuestionsNSSecureCoding with the correct questions array, and assign that to the questions property:
questionsCD.questions = QuestionsNSSecureCoding(questions: questions)

How to initialize stored property which is of type associatedtype protocol

protocol Identifiable {
associatedtype ID
func identifier() -> ID
}
protocol PersonProtocol: Identifiable {
var name: String { get }
var age: Int { get }
}
class Person: PersonProtocol {
let name: String
let age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func identifier() -> String {
return "\(name)_\(age)"
}
}
I tried to a declare & initialise stored property as let owner: PersonProtocol in class Car but it gave an error:
`PersonProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Hence I tried following piece of code to do the same, but I am not sure if this is the correct way to do this.
Need suggestions.
class Car<T: PersonProtocol>{
let owner: T
init<U: PersonProtocol>(owner: U) where U.ID == String {
self.owner = owner as! T // I am force casting `U` as `T`. is this forcecasting justified ?
}
func getID() -> String {
owner.identifier() as! String // is this forcecasting justified ?
}
}
class Car<U,T: PersonProtocol> where T.ID == U{
let owner: T
init(owner: T) {
self.owner = owner
}
func getID() -> U {
owner.identifier()
}
}
let person = Person(name: "John Snow", age: 34)
let car = Car<String, Person>(owner: person)

Can't cast a value from a subclass Swift

I made a lot of research but I didn't find an answer to my question. Others talk about basic issues with Swift classes. Still I have an issue with my own classes. I also read courses about classes but it didn't help me.
I have two classes; one of them inherit from the other.
Here is my classes code :
class GlobalUser {
var uid: String!
var publicName: String!
var pushID: String!
var firstName: String!
var lastName: String!
var example1: [String:String]!
var fullName: String! {
get {
return firstName + " " + lastName
}
}
init(document: DocumentSnapshot) {
guard let data = document.data() else {
print("Missing user information during initialization.")
return
}
self.uid = document.documentID
self.publicName = (data["publicName"] as? String)!
self.pushID = (data["pushID"] as? String)!
self.example1 = (data["example1"] as? [String : String])!
let name = data["name"] as? [String:String]
self.firstName = (name!["firstName"])!
self.lastName = (name!["lastName"])!
}
}
class InterestingUser: GlobalUser {
var code: Int?
var example: [String:String]?
var number: Int! {
get {
return example.count
}
}
override init(document: DocumentSnapshot) {
super.init(document: document)
}
}
And then I try to cast a GlobalUser to a InterestingUser like this :
if let interestingUser = user as? InterestingUser {
...
}
But this cast always fails...
Any idea? Thanks in advance for your help.
The error you're experiencing is due to this statement from your question: 'And then I try to cast a GlobalUser to a InterestingUser like this...' and is due to inheritance.
Your GlobalUser class is the superclass. Your InterestingUser is a subclass of your GlobalUser.
So your InterestingUser class 'knows' about the GlobalUser because it is it's parent and you can cast InterestingUser as? GlobalUser but not the other way around.
Example:
if let interstingUser = InterestingUser() as? GlobalUser {
// this will succeed because InterestingUser inherits from GlobalUser
}
if let globalUser = GlobalUser() as? InterestingUser {
// this will fail because GlobalUser is not a subclass of InterestingUser
}
Here's some playground code for you to test with:
class GlobalUser {
}
class InterestingUser: GlobalUser {
}
class Demo {
func comparison() {
let interesting = InterestingUser()
let global = GlobalUser()
if let intere = interesting as? GlobalUser {
print("Interesting is global as well")
}
if let global = global as? InterestingUser {
print("Global is interesting")
}
}
}
let demo = Demo()
demo.comparison()
// prints 'Interesting is global as well'

Swift Return from initializer without initializing all stored properties

I have looked everywhere and can't find a reason why this code isn't working. I am trying to initialize my User class which will work with Firebase so I need to initialize a dictionary to be able to retrieve the JSON data. I've done this a bunch of times and haven't had any issues but for some reason this time xcode is giving me the "Return from initializer without initializing all stored properties". From what I can see all my properties are initialized. Am I missing something? Thank you in advance for your hep!
enum UserAccessLevel: String {
case unknown = "Unknown"
case executiveProducer = "Executive Producer"
case topTierProducer = "Top Tier Producer"
case lowTierProducer = "Low Tier Producer"
case crew = "Crew Member"
case vendor = "Vendor"
case talent = "Talent"
}
enum Access: String {
case granted = "true"
case denied = "false"
}
enum FIRUserData: String {
case firstName = "firstName"
case lastName = "lastName"
case city = "city"
case state = "state"
case profileImage = "profileImage"
case userName = "userName"
case accessLevel = "accessLevel"
}
protocol User {
var firstName: String { get set }
var lastName: String { get set }
var city: String { get set }
var state: USAState { get set }
var profileImage: String { get set }
var userName: String { get set }
var accessLevel: UserAccessLevel { get set }
func createUser() throws
func signInUser() throws
func signOutUser()
func archiveProject()
func activateProject()
func deleteProject()
}
class UserType: User {
var firstName: String
var lastName: String
var city: String
var state: USAState
var profileImage: String
var userName: String
var accessLevel: UserAccessLevel
init(firstName: String, lastName: String, city: String, state: USAState, profileImage: String, u accessLevel: UserAccessLevel) {
self.firstName = firstName
self.lastName = lastName
self.city = city
self.state = state
self.profileImage = profileImage
self.userName = "\(firstName).\(lastName)"
self.accessLevel = accessLevel
}
init(userName: String, userData: Dictionary <String, Any>) {
self.userName = userName
if let firstName = userData[FIRUserData.firstName.rawValue] {
self.firstName = firstName as! String
}
if let lastName = userData[FIRUserData.lastName.rawValue] {
self.lastName = lastName as! String
}
if let city = userData[FIRUserData.city.rawValue] {
self.city = city as! String
}
if let state = userData[FIRUserData.state.rawValue] {
self.state = state as! USAState
}
if let profileImage = userData[FIRUserData.profileImage.rawValue] {
self.profileImage = profileImage as! String
}
if let accessLevel = userData[FIRUserData.accessLevel.rawValue] {
self.accessLevel = accessLevel as! UserAccessLevel
}
}
In your second init for your UserType class, Xcode doesn't think that all of the class properties are guaranteed to be set, because if statements can fail.
The solution is to include an else block for all of your if statements:
init(userName: String, userData: Dictionary <String, Any>) {
self.userName = userName
if let firstName = userData[FIRUserData.firstName.rawValue] as? String {
self.firstName = firstName
} else {
firstName = //something else
}
//do the same thing with other if statements
}
The second initializer doesn't initialize the fields if there is a value missing from the userData dictionary. You need to decide what's going to happen to UserType's fields in that case.

Swift 2.2 singleton

I am new in Swift. I am trying to parse some JSON data from web service and want a singleton class of user.But I got stuck to create the singleton. Here is my code:
import Foundation
class User {
private var success: String
private var userId: String
private var name: String
private var gender: String
private var email: String
private var userObject = [User]()
class var sharedInstane:User {
struct Singleton {
static var onceToken: dispatch_once_t = 0
static var instance:User? = nil
}
dispatch_once(&Singleton.onceToken){
Singleton.instance = User()
}
return Singleton.instance!
}
private init(success: String, userId: String, name: String, gender: String, email: String)
{
self.success = success
self.userId = userId
self.name = name
self.gender = gender
self.email = email
}
convenience init(dictionary: [String:AnyObject]) {
let success = dictionary["success"] as? String
let userId = dictionary["userId"] as? String
let name = dictionary["name"] as? String
let gender = dictionary["gender"] as? String
let email = dictionary["email"] as? String
self.init(success: success!, userId: userId!, name: name!, gender: gender!, email: email!, )
}
func callWebserviceToLoadUserInfo (url:String, param:[String:AnyObject],completeHandler:(Bool?,String) -> ())
{
let connection = ServerConnection()
connection.getJSONDataForWebService(url, params: param) { (response, error) in
// code goes here
var responseDict = response as! [String : AnyObject]
responseDict = responseDict["responseDict"] as! [String : AnyObject]
if responseDict["success"] as! String == "1" {
for dict in responseDict {
let user = User(dictionary: (dict as! [String:AnyObject]))
self.userObject.append(user)
}
print("user : \(self.userObject[0].name)")
}else{
// error goes here
}
}
}
}
Can any one please help me how should I do this code?
The singleton in the single line sample code.
class TheOneAndOnlyKraken {
static let sharedInstance = TheOneAndOnlyKraken()
private init() {} //This prevents others from using the default '()' initializer for this class.
}
For more details.
Using Krakendev's single-line singleton code, cited by Maheshwar, and turning your convenience init into an instance function to be called with User.sharedInstance.initialize(dictionary):
import Foundation
class User {
// Here you declare all your properties
// "private var" and all that jazz
static let sharedInstance = User()
private init() {
// If you have something to do at the initialization stage
// you can add it here, as long as it does not involve
// arbitrary values that you would pass as parameters.
}
func initialize(dictionary: [String:AnyObject]) {
// Transfer the values of the dictionary to each `self.property`.
// Be careful while using `as?` as you may have to deal with
// optionals. No need to call `self.init` at the end, because
// this is now a regular `func`.
}
// Add the rest of your stuff here
}
One note about how you were working inside of that convenience initializer: if you do property = SomeClass.someMethod().someProperty as? SomeType, then property will be of type SomeType?, or Optional(SomeType). According to The Swift Programming Language,
The conditional form, as?, returns an optional value of the type you are trying to downcast to.
While User was not instantiated at least one time sharedInstance will return nil. After the first successful instantiation of the User, sharedInstance starts return it and that's became impossible to instantiate another one User as singleton pattern requires it. Consider this:
class User {
private static var sharedUser: User?
class var sharedInstance: User? {
return sharedUser
}
private init(success: String, userId: String, name: String, gender: String, email: String)
{
//User initialization code here
User.sharedUser = self
}
convenience init?(dictionary: [String:AnyObject]) {
guard User.sharedUser == nil else {
return nil
}
//dictionary parsing code is here
self.init(success: success!, userId: userId!, name: name!, gender: gender!, email: email!)
}
}
Client's code:
User.sharedUser
//return nil
let dict: [String:AnyObject] = ["success": "success", "userId":"userId", "name":"name", "gender":"gender","email":"email"]
User(dictionary: dict)
//creates User
User.sharedUser
//returns just created user
User(dictionary: dict)
//return nil
You should think about making this two classes, so that User is your model class and then create a manager to handle all the users (which seems to be your goal).
So in User remove the sharedInstane part and create a second singleton class, e.g. called UserManager, with the standard way to create a singleton in Swift. Then you can keep the way you're creating your user and in the end just assign it to the singleton:
class UserManager {
static let sharedInstance = UserManager()
var users = [User]()
}
// in your code:
...
for dict in responseDict {
let user = User(dictionary: (dict as! [String:AnyObject]))
UserManager.sharedInstance.users.append(user)
}
...