How to disable default nil-initialization of Optional - swift

Let's say I have these two structures:
struct DataOne {
let id: String
// ...
}
struct DataTwo {
let id: String
// ...
}
And there is a separate conversion function:
extension DataOne {
func convertToDataTwo() -> DataTwo {
.init(
id: self.id,
// ...
)
}
}
At some point, the var name: String? field is added to both structures:
struct DataOne {
let id: String
var name: String?
// ...
}
struct DataTwo {
let id: String
var name: String?
// ...
}
But the assembly does not swear because the field is optional.
And when converting, the name field is lost.
Is it possible to disable the autofill of the option or to call the warnings?
I tried to find such a rule in SwiftLint, but I didn't find it.

If your var's type is written T? with no default value, and Swift synthesizes a memberwise init for your type, then the synthesized init uses a default value of nil for that var.
However, if your var's type is written Optional<T> with no default value, then the synthesized init does not use a default value.
So write this instead:
struct DataOne {
let id: String
var name: Optional<String>
// ...
}
struct DataTwo {
let id: String
var name: Optional<String>
// ...
}
Or write out your init instead of letting the compiler synthesize it:
struct DataOne {
let id: String
var name: String?
init(id: String, name: String?) {
self.id = id
self.name = name
}
}
struct DataTwo {
let id: String
var name: String?
init(id: String, name: String?) {
self.id = id
self.name = name
}
}
You can use Xcode's Editor > Refactor > Generate Memberwise Initializer command to write most of the init for you, then delete the = nil default:

Related

Swift protocol default values are not changable

I have such a protocol properties with default values. But with the current implementation if I create an instance of AssetViewAttributes with some other values for avgPrice, precision they still have the default values. How can I change them?
struct Crypto: AssetViewAttribures {
let name: String
let logo: URL
let symbol: String
let avgPrice: String
let precision: Int
}
struct Commodity: AssetViewAttribures {
let name: String
let logo: URL
let symbol: String
let avgPrice: String
let precision: Int
}
struct Fiat: AssetViewAttribures {
let name: String
let logo: URL
let symbol: String
}
protocol AssetViewAttribures {
var name: String { get }
var logo: URL { get }
var symbol: String { get }
}
extension AssetViewAttribures {
var avgPrice: String { get { return "" } set {} }
var precision: Int { get{ return 0 } set{} }
}
var type1: AssetViewAttribures = Crypto(name: "name", logo: URL(string: "https://pixabay.com/de/illustrations/online-maus-web-internet-weltweit-523234/")!, symbol: "symbol", avgPrice: "123", precision: 2)
type1.avgPrice // "" instead of "123"
var type1: AssetViewAttribures = Crypto(name: "name", logo: URL(string: "https://pixabay.com/de/illustrations/online-maus-web-internet-weltweit-523234/")!, symbol: "symbol", avgPrice: "123", precision: 2)
type1.avgPrice
This would call the getter declared in the protocol extension, which just returns "". This is because Crypto.avgPrice has no relation to the avgPrice declared in the protocol extension. You can't "override" a member in an extension, because extensions are dispatched statically. The compiler sees that test is of type AssetViewAttributes, finds the default getter you have declared in the extension, and that's what it will call.
To fix this, you need to add avgPrice as a requirement of the protocol:
protocol AssetViewAttributes {
...
var avgPrice: String { get }
}
This causes Swift to find avgPrice declared in the protocol, and dispatches it dynamically. If the implementing class happens to implement avgPrice, that implementation will be called. If not, then the default implementation is called.

Struct object initialize with not needed object

I have the following ClassRoom Struct
struct ClassRoom: Codable {
let name: String
let classId: String
let date: String
}
While I am forming a ClassRoom object, I do not need date object.
// do not need date object here!,
let classRoom = ClassRoom(name: "Math", classId: "12343")
Missing argument for parameter 'date' in call
But I need date object when I decode the ClassRoom.
You need to make your date be optional.
Two approaches:
You can keep using the compiler synthesized memberwise initializer. For it to use nil as a default value, you need to make the date variable mutable:
struct ClassRoom: Codable {
let name: String
let classId: String
var date: String? = nil
}
If you want to keep it mutable, you'll have to add your own initializer, with a defaulted parameter, like so:
struct ClassRoom: Codable {
let name: String
let classId: String
let date: String?
init(name: String, classId: String, date: String? = nil) {
self.name = name
self.classId = classId
self.date = date
}
}

Passing generic parameters in a generic class

Is it possible to get something like below using generics in swift? Any help would be appreciated more than anything else.
class ABC {
var title: String?
}
class Option: <T> {
var name: String?
var data: T?
}
let object1 = ABC()
let option = Option(name: "Test", data: object1)
// Getting data again
let data = option.data
Here is how you can use Generics in Swift.
protocol PPP {
var title: String? {get set}
}
class ABC: PPP {
var title: String?
}
class XYZ: PPP {
var title: String?
}
class Option<T> {
var name: String?
var data: T?
init(name: String?, data: T?) {
self.name = name
self.data = data
}
}
let object1 = ABC()
let option1 = Option(name: "Test1", data: object1)
let object2 = XYZ()
let option2 = Option(name: "Test2", data: object2)
Since classes in Swift doesn't have a default initializer, so create one that accepts 2 parameters - String? and T?.
You can use type(of:) method to identify the type of an object.
print(type(of: option1.data)) //ABC
print(type(of: option2.data)) //XYZ
You can use protocol to access title in both ABC and XYZ.
Conform ABC and XYZ to protocol PPP and implement its title property in both ABC and XYZ.
print(option1.data?.title)
print(option2.data?.title)

What is the simplest way to instantiate a new Codable object in Swift?

I have a Codable class:
class Task: Codable {
var name: String
}
When I try to instantiate it:
let newTask = Task()
allTasks.append(newTask)
It gives me error:
Missing argument for parameter 'from' in call
All I want is to insert a new object (newTask) into an array. What is the simplest way to do this?
You can inherit the initializer from NSObject:
class Task: NSObject, Codable {
var name: String = ""
}
let newTask = Task()
If you don't want to inherit NSObject, then just create your own initializer:
class Task: Codable {
var name: String?
init() {
}
}
If you don't want to make name optional (or set it to a default), it has to be initialized in init() such as:
class Task: Codable {
var name: String
init(withName name: String) {
self.name = name
}
}
let newTask = Task(withName: "ikevin8me")
Yet another solution is to use struct
struct Task: Codable {
var name: String
}
let task = Task(name: "myname")
Your Task class doesn't provide its own initializer so it gets the one defined in the Codable protocol (which it gets from the Decodable protocol).
Either add your own explicit init that takes a name parameter or change your class to a struct. Either way, you need to create a Task by passing in a name value so the name property can be initialized.
None of this addresses the fact that the code you posted makes no use of Codable so maybe there is no need for your class (or struct) to conform to Codable.
The Task class doesn't provide any initializer to initialize an object that's why it's taking initializer from Codable protocol, Provide your own initializer to initialize an object.
Usage:
1.With Class
class Task: Codable {
var name: String
init(_ name : String) {
self.name = name
}
}
var task = Task("Jarvis")
2.With struct:
struct Task: Codable {
var name: String
}
let task = Task(name: "jarvis")
I would not assign a default value to the property but implement the expected init method and a convenience variant that didn't take any arguments or alternatively have a factory method that creates an "empty" Task
Here is the code with both options
class Task: Codable {
var name: String
init(_ name: String) {
self.name = name
}
convenience init() {
self.init("")
}
static func emptyTask() -> Task {
return Task("")
}
}
You could instantiate a object from json (for example when you use an API) like this:
struct Person: Codable {
var firstName: String
var lastName: String
var age: Int
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case age = "age"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
firstName = try container.decode(String.self, forKey: .firstName)
lastName = try container.decode(String.self, forKey: .lastName)
age = try container.decode(Int.self, forKey: .age)
}
init(data: Data) {
let decoder = JSONDecoder()
do {
let userDecoded = try decoder.decode(Person.self, from: data)
self = userDecoded
}
catch {
age = 0
firstName = ""
lastName = ""
}
}
func fullName() -> String {
return "\(self.firstName) \(self.lastName)"
}
}

init new class instance with args object parameter which conforms to a protocol

I've a class and a protocol into myModel.swift
public protocol IModelArgs{
var name: String { get set};
var surname: String { get set};
}
public class Model {
var name: String;
var surname: String;
init(args: IModelArgs) {
self.name = args.name;
self.surname = args.surname;
}
}
IModelArgs is the protocol of arguments object passed to Model constructor.
Into another file I need to create the instance of Model class, but I'm not able to pass args object to constructor: What I'm wrong?
let myM = Model(args: ("T1","T2"));
The main problem in your case that ("T1","T2") is a tuple and not the object that conform your protocol. In your case it should look like this:
struct ArgsObject: IModelArgs {
var name: String
var surname: String
}
let myM = Model(args: ArgsObject(name: "someName", surname: "someSurname"))
But if you want to use the protocol only to pass an object to the constructor, you do not need to do this. Create struct for it like this:
struct ArgsObject {
let name: String
let surname: String
}
class Model {
var name: String
var surname: String
init(args: ArgsObject) {
self.name = args.name
self.surname = args.surname
}
}
let myM = Model(args: ArgsObject(name: "someName", surname: "someSurname"))
Few optional comments
Don't use ; and protocol names like ISomething, it's not the Java