Implicitly Unwrapped Optional when a constant that cannot be defined during initialisation, But Error Comes - swift

By Drewag in his answer to the question
Every member constant must have a value by the time initialization is complete. Sometimes, a constant cannot be initialized with its correct value during initialization, but it can still be guaranteed to have a value before being accessed.
Using an Optional variable gets around this issue because an Optional is automatically initialized with nil and the value it will eventually contain will still be immutable. However, it can be a pain to be constantly unwrapping a variable that you know for sure is not nil. Implicitly Unwrapped Optionals achieve the same benefits as an Optional with the added benefit that one does not have to explicitly unwrap it everywhere.
The following code defines two classes, Country and City, each of which stores an instance of the other class as a property. Every country must have a capital city and every city must always belong to a country.
class Country {
let name: String
var capitalCity: City! //why I can't use let!
init(name: String, capitalName: String){
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
deinit {
print("\(name) has been de-initialized the city \(capitalCity.name) is gone with with the country")
}
}
class City{
let name: String
unowned let country: Country
init(name: String, country: Country){
self.name = name
self.country = country
}
deinit {
print("The city \(name) has been de-initialized ")
}
}
var country = Country(name: "Canada", capitalName: "Ottawa")
}
However, if I changed the line var capitalCity: City! into let capitalCity: City!, the compiler given out the following error warning.
Question: Isn't that we can use Implicitly Unwrapped Optional when a constant that cannot be defined during initialization? What's the error here?

The important part is here:
City(name: capitalName, country: self)
You are definitely using self in the expression which is executed before the assignment to the property capitalCity.
If you want to use self in any of the expressions, it needs to be in the second phase of two phase initialization, which means all properties needs to be initialized before the usage of self.
With using var, Swift assigns a default initial value nil for the property capitalCity. So the property can be considered as "already initialized", so, you can use self after you have initialized another property name.
(You know giving nil to a let-constant of ImplicitlyUnwrappedOptional is ridiculous.)
By the way private(set) var is often used in similar cases:
private(set) var capitalCity: City!

Related

Swift: how closure captures variables of value type?

Take a look at the following code snippet
struct Person{
var name: String
let surname: String
var closure: (()->())?
init(name: String, surname: String){
self.name = name
self.surname = surname
}
}
var person = Person(name: "John", surname: "Lennon")
let cl = {
print(person.name)
}
person.name = "Bill"
cl()
print(person.name)
the output of the above snippet is
Bill
Bill
Can somebody explain how this happens? I thought that since closure is reference type and Person is a value type then when the closure is created it gets its own copy of the Person(since value types are copied on pass), so modifying outer Person should not affect Person that is captured by closure, but it seems that it doesn't work in this way. I'm new to swift and value types, so please don't judge my question too hard.Thank you P.S. I know that we can capture value variable explicitly using capture list and in this case modifying outer variable doesn't affect captured variable. The question is no about this. The question is about the fact that I thought that it should be have the same way even without explicit capture
The behaviour you expect only works when you explicitly pass in a variable to a closure like this:
var person = Person(name: "John", surname: "Lennon")
let cl: (Person) -> () = { person in
print(person.name)
}
cl(person)
person.name = "Bill"
cl(person)
When you implicitly capture a variable in a closure, that variable is always passed by reference. If you want to capture variables by value, you need to explicitly pass them in.

How do I use an existing property in a property wrapper when self hasn't been initialized? (SwiftUI)

I have a struct with two variables inside property wrappers. One of the variables is supposed to be computed from the other. When I try to do this, I get the following error:
Cannot use instance member 'name' within property initializer; property initializers run before 'self' is available.
I tried assigning a temporary value to these variables, and then re-assigning them within a custom init() function, but that doesn't seem to work ether. I made a simplified version of the code to see if I could isolate the issue.
import SwiftUI
struct Person {
#State var name: String = ""
#State var nameTag: NameTag = NameTag(words: "")
init(name: String) {
// not changing name and nameTag
self.name = name
nameTag = NameTag(words: "Hi, my name is \(name).")
}
}
class NameTag {
var words: String
init(words: String) {
self.words = words
}
}
var me = Person(name: "Myself")
// still set to initial values
me.name
me.nameTag.words
I noticed that when I changed nameTag to an #ObservedObject, rather than #State, it was able to be re-assigned correctly. Although I don't believe I can change name to #ObservedObject. Could anyone tell me what I'm doing wrong?
To use property wrappers in initializers, you use the variable names with preceding underscores.
And with State, you use init(initialValue:).
struct Person {
#State var name: String
#State var nameTag: NameTag
init(name: String) {
_name = .init(initialValue: name)
_nameTag = .init( initialValue: .init(words: name) )
}
}
Here's what a #State property really looks like, as your tear down levels of syntactic sugar:
name
_name.wrappedValue
$name.wrappedValue
_name.projectedValue.wrappedValue
You can't use the underscore-name outside of the initial type definition.

Swift, Core Data, Optional Integer16 and keyPath

I have an entity in CoreData which has an optional property of type Integer 16. It can genuinely be nil and in my application I want to refer to it as an Int? type. As Int? (or for that matter Int16?) isn't a recognised Objective-C type, the compiler throws a bit of a wobbly. I want to avoid using code like NSNumber?.intValue throughout so I've actually set up my ManagedObject type with custom accessors for this property. My question relates to identifying the property through #keyPath rather than a static string. In Core Data the field is named 'pin' on entity 'User'. Here's the code I have:
class User: NSManagedObject {
// MARK: - Properties
static let pinKey = "pin"
#NSManaged internal(set) var name: String
#NSManaged fileprivate var primitivePin: NSNumber?
internal(set) var pin: Int? {
get {
willAccessValue(forKey: #keyPath(pin)) // THIS LINE ERRORS
let value: Int? = primitivePin.map { $0.intValue }
didAccessValue(forKey: User.pinKey)
return value
}
set {
willChangeValue(forKey: User.pinKey)
primitivePin = newValue.map { NSNumber(value: Int16($0)) }
didChangeValue(forKey: User.pinKey)
}
}
}
The line in error is what I 'want' to achieve but of course the var pin isn't an obj-c type and the compiler complains, so I have defined the static constant pinKey as you can see. #keyPath feels like the right way to go about it, and the entity does have a field called pin, but in this scenario is the only option open to me to use a static value?
In #keyPath you have to specify property name. If you don't have defined property called pin, you will receive an error. In your case you have to use #keyPath(User.primitivePin). I believe this should work.
Also, i guess, calling map is redundant here. You can write directly let value = primitivePin?.intValue and so on.
The answer is....with custom properties/accessors #keyPath can't used as there is no defined #NSManaged property for it - as Maksym points out. However, you can't use the defined primitive for it either, instead using the property name as a String as also shown in the code above (User.pinKey)

Confusion about type casting in swift

I was toying in the playground in xcode 7.3.1 with swift. I am a bit confused about the type casting in swift.
So, here is a bit of code that I tried.
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
var movieItem = Movie(name: "GOT", director: "RRMartin")
movieItem.dynamicType //Movie.Type
(movieItem as? MediaItem).dynamicType //Optional<MediaItem>.Type
var someItm = movieItem as! MediaItem //Movie
someItm.dynamicType //Movie.Type
I've shown the output from the playground in the comment. Here you can see the type in each line.
Now according the docs of apple, The conditional form, as?, returns an optional value of the type you are trying to downcast to. As per the docs, I am trying to downcast to MediaItem, and I am getting the MediaItem as optional type.
But when I use force unwrap(that is as!) the returned type is Movie. But I wanted it to be MediaItem.
Also, another thing to notice is that, the type is actually changed. Some data are actually truncated. Because when I tried to access the director property which is present in the Movie, I cannot access it. As I've downcast it.
So, if the type is downcast, why the returned type is Movie? Shouldn't it be MediaType?
So, my question is this, when I type cast some derived class(Movie) to base class(MediaType), shouldn't the converted type be base class(MediaType)?
dynamicType tells you what the underlying type of the object is. It doesn't tell you what the type of var currently referencing that object is.
For instance:
let a: Any = 3
a.dynamicType // Int.Type
Swift, of course, keeps track of these underlying types which is what allows you to later downcast a MediaItem to a Movie (if that is what it really is).
The confusion for you came when you did:
(movieItem as? MediaItem).dynamicType //Optional<MediaItem>.Type
An Optional is it's own type. It is an enumeration with two values: .None and .Some(T). The .Some value has an associated value that has its own dynamic type. In your example, when you asked for the dynamicType, it returned the underlying type of the Optional which is Optional<MediaItem>.Type. It didn't tell you what the dynamic type of the value associated with that Optional is.
Consider this:
let x = (movieItem as? MediaItem)
x.dynamicType // Optional<MediaItem>.Type
x!.dynamicType // Movie.Type

'self' used before all stored properties are initialized

I'm working through a learn-swift playground and upgrading it to Swift 2.0 as I learn the language. The following code (which likely worked with prior versions of Swift) now generates two errors: "'self' used before all stored properties are initialized" and "Constant 'self.capitalCity' used before initialized"
class Country
{
let name: String
let 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
}
}
reading an answer to a similar question I see that I can change let capitalCity: City! to var capitalCity: City! and the syntax error is resolved.
I realize that in this contrived example a country's capital city can change, so that would be fine, but what if there were a case where the value really was a constant...
Is there any way to resolve the syntax error while keeping capitalCity a constant?
In this case I would suggest you to make the property a variable but hiding it (make it seem like a constant) through a computed property:
class Country {
let name: String
private var _capitalCity: City!
var capitalCity: City {
return _capitalCity
}
init(name: String, capitalName: String) {
self.name = name
self._capitalCity = City(name: capitalName, country: self)
}
}
Is there any way to resolve the syntax error while keeping capitalCity a constant?
Not the way you have things set up. The source of the problem is actually that in order to set capitalCity, you have to create a City whose country is self. That is the use of self to which the compiler is objecting:
self.capitalCity = City(name: capitalName, country: self)
^^^^
Since you have configured City's country as a constant, you must supply this value when you initialize your City. Thus you have no way out; you must make capitalCity an Optional var so that it has some other initial value that is legal, namely nil. Your proposed solution actually works like this:
class Country
{
let name: String
var capitalCity: City! = nil // implicit or explicit
init(name: String, capitalName: String)
{
self.name = name
// end of initialization!
// name is set (to name), and capitalCity is set (to nil)...
// ... and so the compiler is satisfied;
// now, we _change_ capitalCity from nil to an actual City,
// and in doing that, we _are_ allowed to mention `self`
self.capitalCity = City(name: capitalName, country: self)
}
}
Just do:
private(set) var capitalCity: City!
which gives you the read-only public interface you want.
I understand that you find var capitalCity: City! contrived. I'd say that the selected answer is truly contrived; it adds lines of code that have no purpose other than resolving a language-related issue. Lines like that are meaningless and meaning is what matters most in code.
Came across this recently while struggling with a similar problem and as of swift 5.5 (or possibly lower) there is another alternative that is interesting. If you convert Capital City to a lazy var you are actually able to use self in initialization.
class Country
{
let name: String
lazy var capitalCity: City = {
City(name: capitalName, country: self)
}()
private let capitalName: String
init(name: String, capitalName: String)
{
self.name = name
self.capitalName = capitalName
}
}
class City
{
let name: String
unowned let country: Country
init(name: String, country: Country)
{
self.name = name
self.country = country
}
}
Cheers!