Swift protocol default implementation for optional readonly variables - ios

I have the following piece of code, the protocol MyDisplayable has three optional Strings, and I have a default implementation of the protocol via extension. My question is, since I'm sure the extension returns the three strings, is there a way I can use them as non-optional and is there any risk if some other implementation overwrites it? (see the question points 1 and 2 in code below)
Thanks a lot!
protocol MyDisplayable {
var displayName: String? { get }
var shortDescription: String? { get }
var longDescription: String? { get }
}
protocol MyObject : MyDisplayable, CustomStringConvertible {
}
extension MyObject {
var displayName: String? {
return "noname"
}
var shortDescription: String? {
return "something can't be described"
}
var longDescription: String? {
return "no way to describe it further"
}
var description: String {
// **1. is there a way to use the strings as if they are non-optional?**
// **2. is it a problem if another class implements the protocol and returns `nil` for any of the strings, but here they are force unwrapped?**
return "\(displayName!): \(shortDescription!)\n\(longDescription!)"
}
}
class Something : MyObject {
}
let something = Something()
print("Something: \(something)")

Unfortunately, it's not possible to treat a declared optional as a non-optional.
You have declared those strings as optional in your protocol, thus when you implement that protocol they stay optional.
However, you can use getter-setter to ensure that your variables always store some value even when they are declared as optional.
I'll elaborate with some code :
protocol MyDisplayable {
var displayName: String? { get set }
var shortDescription: String? { get set }
var longDescription: String? { get set }
}
protocol MyObject : MyDisplayable, CustomStringConvertible {
}
extension MyObject {
var displayName: String? {
get {
return "noname"
}
set {
newValue ?? ""
}
}
var shortDescription: String? {
get {
return "something can't be described"
}
set {
newValue ?? ""
}
}
var longDescription: String? {
get {
return "no way to describe it further"
}
set {
newValue ?? ""
}
}
var description: String {
// **1. is there a way to use the strings as if they are non-optional?**
// **2. is it a problem if another class implements the protocol and returns `nil` for any of the strings, but here they are force unwrapped?**
return "\(displayName!): \(shortDescription!)\n\(longDescription!)"
}
}
class Something : MyObject {
}
let something = Something()
print("Something: \(something)")
Now even if some other class overwrites a nil value to those strings they will return empty string "".
They will still be optional as they are declared optional, but now they will always have a non-nil value.

How about conditional unwrapping in your default implementation?
return "\(displayName ?? "" ): \(shortDescription ?? "" )\n\(longDescription ?? "")"

Related

Value of optional type 'String?' must be unwrapped to refer to member 'lowercased' of wrapped base type 'String'

I had a function that had no errors. Then I changed some elements in my data model to be optional. I have unwrapped these values everywhere in the code besides one part. The part I am struggling with is my filterData function.
I get the error:
Value of optional type 'String?' must be unwrapped to refer to member 'lowercased' of wrapped base type 'String'
My Data Model is:
import SwiftUI
import Foundation
import Combine
struct Product: Identifiable, Codable, Hashable {
var id: String? = ""
var product_name: String? = ""
}
My filterData function is:
func filterData(){
withAnimation(.linear){
self.filteredProduct = self.products.filter{
return $0.product_name.lowercased().contains(self.search.lowercased())
}
}
}
How do I safely unwrap 'product_name' in this scenario?
By using optional chaining foo?.bar and a fallback value maybeValue ?? fallback:
func filterData() {
withAnimation(.linear) {
self.filteredProduct = self.products.filter {
return $0.product_name?.lowercased().contains(self.search.lowercased()) ?? false
}
}
}
And slightly optimized to avoid doing the same lowercasing over and over:
func filterData() {
withAnimation(.linear) {
let term = self.search.lowercased()
self.filteredProduct = self.products.filter {
return $0.product_name?.lowercased().contains(term) ?? false
}
}
}

Property wrapper: change empty to Optional

I've created the following extension:
import Foundation
extension Collection {
/// Returns `nil` if empty
var nonEmptyValue: Self? {
isEmpty ? nil : self
}
}
Now I'd like to make it a property wrapper so I could use it like this:
final class MyClass {
#NonEmpty
var string: String? = "test"
}
The idea is that whenever an empty string is assigned to the property, it gets replaced with nil.
Is it even possible to create such a property wrapper (since String? and String are of different type) and how would I go about it?
I'm using your extension:
import Foundation
#propertyWrapper
struct NonEmpty<T: Collection> {
var wrappedValue: T? {
didSet {
self.wrappedValue = wrappedValue?.nonEmptyValue
}
}
init(wrappedValue: T?) {
self.wrappedValue = wrappedValue?.nonEmptyValue
}
}
extension Collection {
/// Returns `nil` if empty
var nonEmptyValue: Self? {
isEmpty ? nil : self
}
}
and the result is just like image below:

How to make Xcode display a string representation for user class?

Consider the following class:
class CalculatorButton: CalculatorButtonProtocol, CustomStringConvertible {
var type: CalculatorButtonType
var description: String {
return label
}
let label: String
init(_ label: String, type: CalculatorButtonType) {
self.label = label
self.type = type
}
func action(n1: String, n2: String?) -> String {
fatalError("Not implemented")
}
func format(_ n: Float) -> String {
if n == floor(n) {
return String(Int(n))
}
return String(n)
}
}
While debugging using breakpoints, it's very useful to see a string representation of a class in the debugger window. For the above, I'd like to see the label. However, Xcode shows the reference of the button instance instead. People are saying that adopting CustomStringConvertible protocol gives a class human-readable representation, but that didn't help.
GitHub link: https://github.com/asarkar/ios-bootcamp/tree/master/Calculator-SwiftUI
Make your class implement CustomDebugStringConvertible, this will print your custom representation instead of the instance address.
class CalculatorButton: CustomDebugStringConvertible {
// ...
var debugDescription: String {
label
}
}

Why this struct shadows the type String?

I am trying to understand property wrappers.
I have another question of mine on SO, where I was trying to create a property wrapper like this:
extension String {
func findReplace(_ target: String, withString: String) -> String
{
return self.replacingOccurrences(of: target,
with: withString,
options: NSString.CompareOptions.literal,
range: nil)
}
}
#propertyWrapper
struct AdjustTextWithAppName<String> {
private var value: String?
init(wrappedValue: String?) {
self.value = wrappedValue
}
var wrappedValue: String? {
get { value }
set {
if let localizedAppName = Bundle.main.localizedInfoDictionary?["CFBundleName"] as? String {
let replaced = value.findReplace("$$$", withString: localizedAppName)
}
value = nil
}
}
}
That was not working because the line value.findReplace was showing an error
Value of type String? has no name findReplace
As soon as someone suggested me to change the struct line to
struct AdjustTextWithAppName {
the whole thing started working.
Why? I cannot understand why <String> term on the struct was shadowing the extension to the String type I have created.
Why is that?
Replace <String> with the common generic type <T> and you'll see the issue immediately
#propertyWrapper
struct AdjustTextWithAppName<T> {
private var value: T?
init(wrappedValue: T?) {
self.value = wrappedValue
}
var wrappedValue: T? {
get { value }
set {
if let localizedAppName = Bundle.main.localizedInfoDictionary?["CFBundleName"] as? String {
let replaced = value.findReplace("$$$", withString: localizedAppName) // Value of type 'T' has no member 'findReplace'
}
value = nil
}
}
}
Now the error is more understandable
Value of type 'T' has no member 'findReplace'
I cannot understand why <String> term on the struct was shadowing the extension to the String type I have created.
Why wouldn't it? You explicitly requested for there to be a generic type parameter to AdjustTextWithAppName called String. The compiler gave you precisely what you asked for.

Downcast protocol conformance in extension

Let's say I have two non-generic protocols (1)
protocol StringValue {
var asString: String {get}
}
protocol StringProvider {
var value: StringValue {get}
}
I want to have a generic version of the second one (2)
protocol TypedStringProvider: StringProvider { // inherits from StringProvider
associatedtype TypedStringValue: StringValue
var typedValue: TypedStringValue { get }
}
And extension with default implementation of non-generic version to have a code free conformance to StringProvider (doesn't work, pls read below) (3)
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
}
Now I want several classes to conform both generic TypedStringProvider and non-generic StringProvider protocols (4)
extension UIView: TypedStringProvider {
typealias TypedStringValue = String
var typedValue: String { return "Some String" }
}
extension Double: TypedStringProvider {
typealias TypedStringValue = String
var typedValue: String { return String(self) }
}
extension String: StringValue {
var asString: String { return self }
}
And get compiler error: Type 'UIView' does not conform to protocol 'StringProvider'.
Seemingly extension (3) doesn't work like I want because TypedStringValue is not a StringValue in despite of this constraint associatedtype TypedStringValue: StringValue from (2)
The question is how to conform to non-generic StringProvider while keeping value typed
Example:
0.5.value.lowercased()
Of course StringValue doesn't have lowercased method from String so it won't compile.
What I have tried:
First is to add untyped property to extension (3)
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
var value: StringValue { return typedValue }
}
Doesn't work because of Invalid redeclaration of 'value' error
Second is to extend my classes and add untyped property there (5)
extension UIView {
var value: StringValue { return typedValue }
}
extension Double {
var value: StringValue { return typedValue }
}
It works without compiler errors but
No autocompletion for lowercased in our example.
With extensions (5) I need to write a lot of code for every class conforming StringProvider and every property this protocol has
Any ideas?
value is defined as type StringValue, so this is the type you should specify in your extension of TypedStringProvider in order to complete the protocol conformance:
extension TypedStringProvider {
var value: StringValue {
return typedValue
}
}
Problem
On StringProvider you are defining value to a StringValue:
protocol StringProvider {
var value: StringValue { get }
}
but here you are defining the type to TypedStringValue which is not the same as StringValue. (underlying value can be TypedStringValue, however it needs to be typecasted from StringValue to TypedStringValue when using it)
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
}
Solution
There are two solutions that I can come up for this scenario right now:
1. Generic Approach
If you want to make value generic and change the type based on TypedStringProvider you can:
protocol StringProvider {
associatedtype StringProviderValue: StringValue
var value: StringProviderValue { get }
}
Conform to the protocol by defining StringProviderValue to TypedStringValue
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
}
2. StringValue approach
Keep the StringProvider as it is:
protocol StringProvider {
var value: StringValue { get }
}
Conform to the protocol by using the correct StringValue type, and inside it return typedValue: TypedStringValue which can be downcasted to StringValue
extension TypedStringProvider {
var value: StringValue { return typedValue }
}
Output
Both solutions give the same output:
Found solution
extension StringProvider where Self: TypedStringProvider {
var value: StringValue { return typedValue }
}
With this extension there's no need to write similar extensions to every class and autocompletion works too.
Full code:
protocol StringValue {
var asString: String {get}
}
protocol StringProvider {
var value: StringValue {get}
}
protocol TypedStringProvider: StringProvider {
associatedtype TypedStringValue: StringValue
var typedValue: TypedStringValue { get }
}
extension StringProvider where Self: TypedStringProvider {
var value: StringValue { return typedValue }
}
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
}
extension UIView: TypedStringProvider {
typealias TypedStringValue = String
var typedValue: String { return "some string" }
}
extension Double: TypedStringProvider {
typealias TypedStringValue = String
var typedValue: String { return String(self) }
}
extension String: StringValue {
var asString: String { return self }
}
let doubleValue = 0.5.value.lowercased()

Resources