Why use an initializer? [duplicate] - swift

This question already has answers here:
Why do we need to specify init method?
(3 answers)
Closed 6 years ago.
Newbie, coming from PHP. Be gentle.
Here's a swift struct with an initializer
struct Book {
let title: String
let author: String
let price: String
let pubDate: String
init(title: String, author: String, price: String, pubDate: String){
self.title = title
self.author = author
self.price = price
self.pubDate = pubDate
}
}
let HarryPotter = Book(title: "Harry Potter", author: "JK Rowling",
price: "30$", pubDate: "January 10th, 1998")
And here's a swift struct without an intializer
struct Book {
let title: String
let author: String
let price: String
let pubDate: String
}
let HarryPotter = Book(title: "Harry Potter", author: "JK Rowling",
price: "30$", pubDate: "January 10th, 1998")
If these both do the same thing, then what is the advantage of using an initializer?

In the second case, you're still using an initializer. You're using the default initializer, which was generated for you because you haven't specified any of your own.
Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values.
To answer the more general question of what initializes for: they encapsulate the initialization of an instance, and guarantee that you can never obtain an instance in a "half-baked" state. They're Swift's equivalent of PHP Constructors.

In your specific case, nothing, because all your initialiser is doing is setting the values. But, it's possible that you could have other code in the initialiser which actually does something.

If you want to include some logic beforehand you could do that in the constructor/initializer.

This is a bit of a guess since I'm a Ruby developer rather than Swift, but there might be times when you would want to run more (arbitrary) code inside of your initializer besides just assigning the argument values to instance variables. For example:
def initialize(attr)
#attr = attr
puts "I'm initializing!"
end
In the above case I've assigned the attribute and printed to stdout. I'm guessing in Swift all of that attribute assignment is being done automatically so if all you want to do is assign attributes you can avoid writing the initializer yourself and let Swift handle it.

Related

How to get a property of a tuple with a string?

I'm starting in Swift, I come from JavaScript, and some things are a bit confusing for me, is there something similar to this in Swift?:
JS:
var user = {name: "Chris Answood"}
user["name"]
JavaScript doesn't have built-in tuple support. You just created an object and get the value of the name property inside this object. You have used user["name"] but user.name would also be possible.
To achieve this in Swift you can create a struct:
struct User {
name: String
}
let user = User(name: "Chris Answood")
print(user.name)
Unlike JavaScript, Swift has support for tuples. This is an easy example:
let user = (firstName: "Chris", lastName: "Answood")
print(user.firstName)

Swift equivalent of a simple JavaScript object declaration

Considering the following JavaScript object:
const car = {type:"Fiat", model:"500", color:"white"};
What would be the equivalent structure in Swift language, written in such a short line of code?
You can also do:
let car = (type: "Fiat", model: "500", color: "white")
But in my opinion, it is much cleaner to define your objects by using structs.
Swift is a strongly typed language. If you want to initialize an object, you have to define its type first. Javascript is weakly typed and apart of that it doesn't even distinguish between objects and dictionaries (or arrays).
In Swift, you could write
let car = ["type": "Fiat", "model": "500", "color": "white"]
to create a [String: String] dictionary, but that would be a bad idea in this case. Instead, you should properly declare a type:
struct Car {
let type: String
let model: String
let color: String
}
and then initialize it:
let car = Car(type: "Fiat", model: "500", color: "white")
which is almost the same as in Javascript, only with types.
Types give you more power and "shorter" does not mean "better".

Initialize a variable with nil vs implicitly unwrapped optional

In Swift Guide https://docs.swift.org/swift-book/LanguageGuide/AutomaticReferenceCounting.html
there is an example of using Implicitly Unwrapped Optionals
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
And I am wondering if it is possible and what difference it would make if I made a small change:
var capitalCity: City = nil
Your code will simply not compile. Here's why.
By specifying var capitalCity: City, you're explicitly saying that your variable is non-optional. It can only ever hold a valid City. It can never be optional, so it can never hold nil.
Meanwhile, anytime you use ? or !, you are defining an optional type. These can be nil, and in fact are nil by default.
So in your example from the guide, var capitalCity: City! is saying the following:
capitalCity is declared as an implicitly unwrapped optional type (the !), meaning that you, as the developer, are declaring it to always have a valid City type whenever it is called.
That said, until you set a City to it, it defaults to being nil.
When you should use optionals comes down to how you want to use your properties. In the original example, from what little we see, capitalCity is only set in the class init. So there wouldn't be a reason for it to be optional. So for that example, it could exist like the other property: let capitalCity: City.
A lot depends on how you want the properties to exist and work when you're using your object. For instance, making something optional signals at least two things:
The property may not have a valid value in the class' lifecycle.
You may not be setting it when you initialize your class. (Remember, an optional initializes to nil)
I hope this helps you understand optionals some more.

Swift initialise property at struct initialisation without initialiser

In swift, structs have an automatically generated memberwise initializer.
This means the following struct can be initialised without me having to write an init.
struct Activity {
let name: String
let desc: String
let category: Category
let subcategory: Subcategory
let emoji: Character
let coordinate: CLLocationCoordinate2D
let creationTime: Date = Date()
let activityTime: Date
let id: UUID = UUID()
var comments: [Comment] = []
}
I have one single property called emojiwhich is computed by the subcategory. In other words, the value for emoji depends on the value of subcategory.
However this means that the value of emoji can only be assigned after the initialisation of subcategory.
How should I do this in code?
Approach 1:
Provide my own initialiser
init(name: String, desc: String, category: Category, subcategory: Subcategory,
coordinate: CLLocationCoordinate2D, activityTime: Date) {
self.name = name
self.desc = desc
self.category = category
self.subcategory = subcategory
self.coordinate = coordinate
self.activityTime = activityTime
self.emoji = AllCategories.categories[category]?[subcategory] ?? "❌"
}
I don't like this approach as it adds a lot of unecessary code that will only grow if I add more properties... I would like to use the generated initialiser of the struct. On the other hand, the code is still very simple.
Approach 2:
Use a lazy varthat is only computed when called.
lazy var emoji: Character = {
AllCategories.categories[category]?[subcategory] ?? "❌"
}()
I also don't really like this approach, I find it overly complex for what I am trying to do. Also this makes emojia varinstead of letwhich it is not, I want it to remain a constant. On the other hand, I can continue using the automatically generated initialiser.
Questions:
What other possibilities do I have?
If there are none, which of the 2 approches is the best?
This sounds like a great chance to use computed properties:
var emoji: Character {
AllCategories.categories[category]?[subcategory] ?? "❌"
}
Although it is declared a var, you can't actually set it. It's just how computed properties must be declared.
The expression AllCategories.categories[category]?[subcategory] ?? "❌" will be evaluated every time you use the property. It's not a too time-consuming expression, so IMO it's fine.

Type of expression is ambiguous without more context Swift

I am getting a 'Type of expression is ambiguous without more context ' on this part of code from a project I am trying to upgrade to latest Swift version. I can't seem to figure it out. I tried different things but can't get it to work.
The problem is on the syntax of this line
let imageToDeleteParameters = imagesToDelete.map { ["id": $0.id, "url": $0.url.absoluteString, "_destroy": true] }
Full code:
extension TutorialCreationRequest: WebserviceParametrable {
func toParameters() -> [String: AnyObject] {
let imageParameters = images.map { ["url": $0] }
let imageToDeleteParameters = imagesToDelete.map { ["id": $0.id, "url": $0.url.absoluteString, "_destroy": true] }
return [
"title": title,
"is_draft": isDraft,
"difficulty": difficulty,
"duration": duration,
"cost": cost,
"user_id": userId,
"description": description,
"to_sell": toSell,
"images": [imageParameters, imageToDeleteParameters].flatMap { $0 }
]
}
}
This happens when you have a function with wrong argument names.
Example:
functionWithArguments(argumentNameWrong: , argumentName2: )
and You declared your function as:
functionWithArguments(argumentName1: , argumentName2: ){}
This usually happens when you changed the name of a Variable. Make sure you refactor when you do that.
This can happen if any part of your highlighted method or property is attempting to access a property or method with the incorrect type.
Here is a troubleshooting checklist, make sure:
the type of arguments match in the call site and implementation.
the argument names match in the call site and implementation.
the method name matches in the call site and implementation.
the returned value of a property or method matches in the usage and implementation (ie: enumerated())
you don't have a duplicated method with potentially ambiguous types such as with protocols or generics.
the compiler can infer the correct type when using type inference.
A Strategy
Try breaking apart your method into a greater number of simpler method/implementations.
For example, lets say you are running compactMap on an array of custom Types. In the closure you are passing to the compactMap method, you initialize and return another custom struct. When you get this error, it is difficult to tell which part of your code is offending.
For debugging purposes, you can use a for in loop instead of compactMap.
instead of passing the arguments, directly, you can assign them to constants in the for loop.
By this point, you may come to a realization, such as, instead of the property you thought you wanted to assign actually had a property on it that had the actual value you wanted to pass.
Not an answer to this question, but as I came here looking for the error others might find this also useful:
For me, I got this Swift error when I tried to use the for (index, object) loop on an array without adding the .enumerated() part ...
The compiler can't figure out what type to make the Dictionary, because it's not homogenous. You have values of different types. The only way to get around this is to make it a [String: Any], which will make everything clunky as all hell.
return [
"title": title,
"is_draft": isDraft,
"difficulty": difficulty,
"duration": duration,
"cost": cost,
"user_id": userId,
"description": description,
"to_sell": toSell,
"images": [imageParameters, imageToDeleteParameters].flatMap { $0 }
] as [String: Any]
This is a job for a struct. It'll vastly simplify working with this data structure.
I had this message when the type of a function parameter didn't fit. In my case it was a String instead of an URL.
Explicitly declaring the inputs for that mapping function should do the trick:
let imageToDeleteParameters = imagesToDelete.map {
(whatever : WhateverClass) -> Dictionary<String, Any> in
["id": whatever.id, "url": whatever.url.absoluteString, "_destroy": true]
}
Substitute the real class of "$0" for "WhateverClass" in that code snippet, and it should work.
I got this error when I put a space before a comma in the parameters when calling a function.
eg, I used:
myfunction(parameter1: parameter1 , parameter2: parameter2)
Whereas it should have been:
myfunction(parameter1: parameter1, parameter2: parameter2)
Deleting the space got rid of the error message
In my case, this error message shown when I don't added optional property to constructor.
struct Event: Identifiable, Codable {
var id: String
var summary: String
var description: String?
// also has other props...
init(id: String, summary: String, description: String?){
self.id = id
self.summary = summary
self.description = description
}
}
// skip pass description
// It show message "Type of expression is ambiguous without more context"
Event(
id: "1",
summary: "summary",
)
// pass description explicity pass nil to description
Event(
id: "1",
summary: "summary",
description: nil
)
but it looks always not occured.
I test in my playground this code, it show warning about more concrete
var str = "Hello, playground"
struct User {
var id: String
var name: String?
init(id: String, name: String?) {
self.id = id
self.name = name
}
}
User(id: "hoge") // Missing argument for parameter 'name' in call
For me the case was Type inference
I have changed the function parameters from int To float but did not update the calling code, and the compiler did not warn me on wrong type passed to the function
Before
func myFunc(param:Int, parma2:Int) {}
After
func myFunc(param:Float, parma2:Float) {}
Calling code with error
var param1:Int16 = 1
var param2:Int16 = 2
myFunc(param:param1, parma2:param2)// error here: Type of expression is ambiguous without more context
To fix:
var param1:Float = 1.0f
var param2:Float = 2.0f
myFunc(param:param1, parma2:param2)// ok!
My problem were the parameters without default value
I changed
let contaPadrao = RedeConta(
agencia: cPadrao?.agencia,
conta: cPadrao?.conta,
dac: cPadrao?.dac
)
to
let contaPadrao = RedeConta(
agencia: cPadrao?.agencia ?? "",
conta: cPadrao?.conta ?? "",
dac: cPadrao?.dac ?? ""
)
You have two " " before the =
let imageToDeleteParameters = imagesToDelete.map { ["id": $0.id, "url": $0.url.absoluteString, "_destroy": true] }
In my case it happened with NSFetchedResultsController and the reason was that I defined the NSFetchedResultsController for a different model than I created the request for the initialization (RemotePlaylist vs. Playlist):
var fetchedPlaylistsController:NSFetchedResultsController<RemotePlaylist>!
but initiated it with a request for another Playlist:
let request = Playlist.createFetchRequest()
fetchedPlaylistsController = NSFetchedResultsController(fetchRequest: request, ...
In my case, I ran into this error when I was creating a distribution build, and a class was referring to Debug only context method.
Something like this. Try compiling the below class for the Distribution build.
class MyClass {
func sayHello() {
helloWorld()
}
#if DEBUG
func helloWorld() {
print("Hello world")
}
#endif
}
Make sure if there is any delegate methods are available with extension, then implement those and error will disappear.
In my case, the arguments I was passing had optional String values. Providing a default value to fall back on ( in case the value is nil ) solved this issue for me.
I changed this -
router?.pushToDetailsScreen(
gitHubRepositoryOwnerName: gitHubRepositoryDetails?[index].owner?.login,
gitHubRepositoryName: gitHubRepositoryDetails?[index].name,
avatarUrl: gitHubRepositoryDetails?[index].owner?.avatar_url)
to this -
router?.pushToDetailsScreen(
gitHubRepositoryOwnerName: gitHubRepositoryDetails?[index].owner?.login ?? "",
gitHubRepositoryName: gitHubRepositoryDetails?[index].name ?? "",
avatarUrl: gitHubRepositoryDetails?[index].owner?.avatar_url ?? "")
This error could be shown due to multiple reasons. One of the most prominent reasons is a type mismatch. For example,
Suppose, parameter icons is a type of array and we passed an Enum as IconDismiss.
## Wrong
text.config = TextField.Config(isClearable: true, icons: IconDismiss)
## Correct
text.config = TextField.Config(isClearable: true, icons: [IconDismiss])
In my case it was very similar to what #scottyBlades said. I had changed a member variable name and not updated it in a callback and that caused the confusing/unhelpful error.
class SomeClass {
var newVarName: String
func doSomething() {
Async.makeCall { result in // << error shows here
self.oldVarName = result // not changing it to newVarName was the cause
}
}
}
As theEye's answer it is not an answer to this question, but as I also came here looking for the error im posting my case as others might find this also useful:
I got this error message when I was by error trying to calculate a value of two different types.
In my case I was trying to divide a CGFloat by a Double