Understanding EnvironmentValues syntax - swift

I was spending some time trying to understand the syntax used in EnvironmentValues and I was hoping someone could point out if I have made any errors.
extension EnvironmentValues {
var isSensitive: Bool {
get { self[SensitiveKey.self] }
set { self[SensitiveKey.self] = newValue }
}
}
My understanding is that the the body of the getter and setter is using subscript syntax where the first self is referring to the EnvironmentValues dictionary as an object. The square brackets allows for a key to be passed into this object. The key itself is a metatype instance of SensitiveKey which is a previously defined EnvironmentKey. It allows access to any type properties and methods, which in this case would be the required defaultValue for the EnvironmentKey.
Have I understood this correctly? I'd really appreciate if you could clarify any misunderstandings I have here.
Cheers.

You are close. Per the Swift Documentation:
private struct SensitiveKey: EnvironmentKey {
static let defaultValue: String = "SensitiveKey" // the default could be anything unique
}
extension EnvironmentValues {
var isSensitive: String {
get { self[SensitiveKey.self] }
set { self[SensitiveKey.self] = newValue }
}
}
extension View {
func isSensitive(_ isSensitive: String) -> some View {
environment(\.isSensitive, isSensitive)
}
}
The key is just an EnvironmentKey, not a meta type for isSensitive. It is literally the key to the dictionary value in EnvironmentValues. defaultValue is a poor choice of name that implies mutability as the key itself is immutable.
Also, when you see .self, it is the current instance of the type. .Self would refer to the type.

Related

Redeclaring members in an extension hides the original member *sometimes*. Why?

By chance, I discovered that you can do this without the compiler complaining:
extension Date {
var timeIntervalSinceNow: TimeInterval {
return 1000
}
}
What's weirder, is that this actually evaluates to 1000:
Date().timeIntervalSinceNow
The extension seems to hide the original member.
So I tried to do this with my own class:
class A {
var a: String {
return "A"
}
}
extension A {
var a: String {
return "a"
}
}
and it fails to compile: "invalid redeclaration of 'a'".
I observed that this does not affect the usage of the original member through a protocol, which is expected behaviour of hiding:
extension Date {
var description: String {
return "XXXX"
}
}
let date: CustomStringConvertible = Date()
date.description // normal date
Date().description // "XXXX"
Can you explain why does the bullet pointed phenomena occur?
This works because you are declaring this extension in a separate module from the original variable declaration.
Across modules a variable name can be overloaded but in my mind this has been a shortcoming of Swift as there is currently no way to explicitly state which module declaration it is that you want.

Error: Trying to put the stack in unreadable memory at:

I am trying to add additional properties to UIViewController.
Code:
protocol AdditionalStoredProperties
{
associatedtype Title
func getAssociatedObject<Title>(key: UnsafePointer<Title> ,
defValue : Title)->Title
}
extension AdditionalStoredProperties
{
func getAssociatedObject<Title>( key: UnsafePointer<Title> , defValue : Title)->Title
{
guard let actual_value = objc_getAssociatedObject(self as! AnyObject, key) as? Title else
{
return defValue
}
return actual_value
}
}
extension UIViewController:AdditionalStoredProperties
{
typealias Title = String
var previousPage : String
{
get { return getAssociatedObject(&self.previousPage, defValue: self.previousPage) }
set { objc_setAssociatedObject(self, &self.previousPage, newValue, .OBJC_ASSOCIATION_RETAIN)}
}
}
But I am getting the following error:
Error: Trying to put the stack in unreadable memory at:
I know that we cannot directly add stored properties to extensions so I am trying it add using objc_setAssociatedObject()
If someone has the below scenario
If your method is getting called recursively, you may get this error.
There are a number of things wrong with what you're doing:
Attempting to access self.previousPage within its own getter will call itself recursively.
You cannot use &self.previousPage as a stable or unique pointer value, as it'll be a pointer to a temporary variable (because you're dealing a computed property). You cannot therefore use it as the key for an associated object. Swift only guarantees stable and unique pointer values for static and global stored variables (see this Q&A for more info).
You should make AdditionalStoredProperties a class-bound protocol (with : class), as you can only add associated objects to Objective-C classes (which, on Apple platforms, Swift classes are built on top of). While you can bridge, for example, a struct to AnyObject (it'll get boxed in an opaque Obj-C compatible wrapper), it is merely that; a bridge. There's no guarantee you'll get the same instance back, therefore no guarantee the associated objects will persist.
You probably didn't mean for Title to be an associated type of your protocol; you're not using it for anything (the generic placeholder Title defined by getAssociatedObject(key:defValue:) is completely unrelated).
Bearing those points in mind, here's a fixed version of your code:
protocol AdditionalStoredProperties : class {
func getAssociatedObject<T>(ofType: T.Type, key: UnsafeRawPointer,
defaultValue: #autoclosure () -> T) -> T
}
extension AdditionalStoredProperties {
func getAssociatedObject<T>(ofType: T.Type, key: UnsafeRawPointer,
defaultValue: #autoclosure () -> T) -> T {
// or: return objc_getAssociatedObject(self, key) as? T ?? defaultValue()
guard let actualValue = objc_getAssociatedObject(self, key) as? T else {
return defaultValue()
}
return actualValue
}
}
extension UIViewController : AdditionalStoredProperties {
private enum AssociatedObjectKeys {
static var previousPage: Never?
}
var previousPage: String {
get {
// return the associated object with a default of "" (feel free to change)
return getAssociatedObject(ofType: String.self,
key: &AssociatedObjectKeys.previousPage,
defaultValue: "")
}
set {
objc_setAssociatedObject(self, &AssociatedObjectKeys.previousPage,
newValue, .OBJC_ASSOCIATION_RETAIN)
}
}
}
Note that we're:
Using a static stored property in order to get a pointer value to use as the key for our associated object. Again, this works because Swift guarantees stable and unique pointer values for static and global stored variables.
Using #autoclosure for the defaultValue: parameter, as it may not need to be evaluated if an associated object is already present.
Having the key: parameter take an UnsafeRawPointer, as the type of the pointee is irrelevant; it's merely the location in memory that's used as the key.
Explicitly satisfying the generic placeholder with an ofType: parameter. This is mainly a matter of preference, but I prefer to spell these things out explicitly rather than relying on type inference.
Using camelCase instead of snake_case, as is Swift convention.

How to reference an attribute as a method default in a Swift class? [duplicate]

In a Swift class, I want to use a property as a default parameter value for a method of the same class.
Here is my code :
class animal {
var niceAnimal:Bool
var numberOfLegs:Int
init(numberOfLegs:Int,animalIsNice:Bool) {
self.numberOfLegs = numberOfLegs
self.niceAnimal = animalIsNice
}
func description(animalIsNice:Bool = niceAnimal,numberOfLegs:Int) {
// I'll write my code here
}
}
The problem is that I can't use my niceAnimal property as a default function value, because it triggers me a compile-time error :
'animal.Type' does not have a member named 'niceAnimal'
Am I doing something wrong ? Or is it impossible in Swift ? If that's impossible, do you know why ?
I don't think you're doing anything wrong.
The language specification only says that a default parameter should come before non-default parameters (p169), and that the default value is defined by an expression (p637).
It does not say what that expression is allowed to reference. It seems like it is not allowed to reference the instance on which you are calling the method, i.e., self, which seems like it would be necessary to reference self.niceAnimal.
As a workaround, you could define the default parameter as an optional with a default value of nil, and then set the actual value with an "if let" that references the member variable in the default case, like so:
class animal {
var niceAnimal: Bool
var numberOfLegs: Int
init(numberOfLegs: Int, animalIsNice: Bool) {
self.numberOfLegs = numberOfLegs
self.niceAnimal = animalIsNice
}
func description(numberOfLegs: Int, animalIsNice: Bool? = nil) {
if let animalIsNice = animalIsNice ?? self.niceAnimal {
// print
}
}
}
I think for now you can only use literals and type properties as default arguments.
The best option would be to overload the method, and you can implement the shorter version by calling the full one. I only used a struct here to omit the initializer.
struct Animal {
var niceAnimal: Bool
var numberOfLegs: Int
func description(#numberOfLegs: Int) {
description(niceAnimal, numberOfLegs: numberOfLegs)
}
func description(animalIsNice: Bool, numberOfLegs: Int) {
// do something
}
}

Swift 3 Generic Extension Arguments

In Swift 2.x, I had a nice little setup that allowed me to store and retrieve dictionary values using enum members:
public enum UserDefaultsKey : String {
case mainWindowFrame
case selectedTabIndex
case recentSearches
}
extension Dictionary where Key : String {
public subscript(key: UserDefaultsKey) -> Value? {
get { return self[key.rawValue] }
set { self[key.rawValue] = newValue }
}
}
This allowed me to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[.recentSearches] as? [String] {
// Populate "Recent" menu items
}
… instead of having to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[UserDefaultsKey.recentSearches.rawValue] as? [String] {
// Populate "Recent" menu items
}
Note: The use of a string literal to access the dictionary from NSUserDefaults is for example purposes only. I wouldn't actually go out of my way to use an enum for dictionary keys, only to use a string literal to access the dictionary itself. :-)
Anyway, this has worked great for my needs, and it made reading and maintaining code involving NSUserDefaults a lot more pleasant.
Since migrating my project to Swift 3, however, I'm getting the following error:
extension Dictionary where Key: String {
public subscript(key: UserDefaultsKey) -> Value? { <---- Use of undeclared type 'Value'
~~~~~~
get {
return self[key.rawValue]
}
set {
self[key.rawValue] = newValue
}
}
}
I looked at the generated headers for Dictionary, and the generic Key and Value arguments are still present in the Generic Argument Clause of the Dictionary struct, so I'm not too sure what the issue is.
Do I need to rewrite the where clause to conform to some new Swift 3 grammar I'm unaware of? Or … can one no longer access generic placeholder types in extensions?
I just don't know what to do!
My project has only 28 migration errors left to resolve. I'm so close to actually getting to use Swift 3, so I'd love any pointers (as long as they're not Unsafe and/or Raw).
Thanks!
A generic parameter of a concrete type cannot be constrained to a concrete type, currently. This means that something like
extension Dictionary where Key == String
won't compile. It's a limitation of the generics system, and it hopefully won't be a problem in Swift 4.
There is a workaround though, but it's a bit hacky:
protocol StringConvertible {
init(_ string: String)
}
extension String: StringConvertible {}
extension Dictionary where Key: StringConvertible {
subscript(key: UserDefaultsKey) -> Value? {
get { return self[Key(key.rawValue)] }
set { self[Key(key.rawValue)] = newValue }
}
}

Swift protocol extensions for Value(Structures) types

public struct KZErrorInfo: Unboxable {
var statusCode = -1
var status: String?
var errorMessage: String?
public init() {
}
public init(unboxer: Unboxer) {
self.statusCode = unboxer.unbox("StatusCode")
self.status = unboxer.unbox("Status")
self.errorMessage = unboxer.unbox("Message")
}
}
protocol KZClientResponse: ETClientResponse {
var errorInfo: KZErrorInfo? { get set }
}
var errorInfo: KZErrorInfo? {
get {
if let value = objc_getAssociatedObject(self, &xoAssociationKeyErrorInfo) as? KZErrorInfo {
return value
}
return nil
}
set(newValue) {
if let error = newValue {
objc_setAssociatedObject(self, &xoAssociationKeyErrorInfo, error, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
}
My objective is to have a default implantation for the protocol KZClientResponse and Xcode is giving me a compile error as below. In the case of value types, how to overcome this issue? Appreciate you suggestions.
As the error message is indicating, objc_getAssociatedObject(_:_:) and objc_setAssociatedObject(_:_:_:_:) require AnyClass as the first argument. You cannot use Swift structs as AnyClass.
Think another way to store errorInfo which works with structs.
Why don't you have it as the struct's property?
... giving me a compile error as below. In the case of value types, how to overcome this issue?
You can't overcome the compiler error. You're trying to mix apples with oranges. objc_getAssociatedObject is, by definition, Objective-C. But Objective-C knows nothing of Swift structs; it cannot possibly see them. The only thing it knows about are what it calls objects — that is, classes and their instances. To work with a Swift struct, you cannot use the Objective-C runtime at all: you must operate entirely within Swift itself.