This question already has answers here:
How can I change the textual representation displayed for a type in Swift?
(7 answers)
Closed 12 months ago.
I've made an class - call it Blah. It contains a string "value". So anytime someone makes a string out of it - I want it to appear as the value. So I try it:
let x = Blah( "hello world")
XCTAassertEqual("hello world", "\(x)")
out of the box - of course it doesn't work - I haven't told it how to do the rendering. So I do some googling and find that thing is "description" - so add this to Blah:
public var description = {get{return _value}}
and then the test becomes this:
let x = Blah( "hello world")
XCTAassertEqual("hello world", "\(x.description)")
XCTAassertEqual("hello world", "\(x)")
the first assert works, but the second is still giving me the whole object
so - more googling - I find that you can add custom string interpolations.. so I add this:
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Blah) {
appendLiteral( value.description)
}
}
and that achieves... well... absolutely nothing.
How do I change the way my objects are represented by string interpolation?
The property description you have seen belongs to the protocol CustomStringConvertible and is used when you want to convert an object to a string representation. So all you need to do is to conform to the protocol and description will be used in your tests
struct Blah: CustomStringConvertible {
let value: String
init(_ value: String) {
self.value = value
}
var description: String {
value
}
}
You were right about var description. However, you forgot to add the protocol CustomStringConvertible:
struct Blah {
let value: String
}
extension Blah: CustomStringConvertible {
var description: String { value }
}
let x = Blah(value: "Hello world")
print("Hello world: ", "\(x)")
Without the protocol, description is just a custom property.
I manage to get it to work.
class Blah {
let value: String
init(_ value: String) {
self.value = value
}
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Blah) {
appendLiteral(value.value)
}
}
func testBlah() {
let blah = Blah("Hello world")
XCTAssertEqual("Hello world", "\(blah)")
}
The test passes.
Related
I have a lot of DTOs in my app which log some field. That field should not be logged because the data is kind of sensitive.
The model looks like this:
typealias HiddenFieldType = String
struct DTO1 {
var field1_1: String
var fieldToHide: HiddenFieldType
var field1_2: String
var field1_3: String
}
struct DTO2 {
var field2_1: String
var field2_2: String
var fieldToHide: HiddenFieldType
var field2_3: String
}
the code which outputs the data is like this (actually it's os_log in a real app):
func test() {
let dto1 = DTO1(field1_1: "1_1", fieldToHide: "super-secret-1", field1_2: "1_2", field1_3: "1_3")
let dto2 = DTO2(field2_1: "2_1", field2_2: "2_2", fieldToHide: "super-secret-2", field2_3: "2_3")
print("Test1: dto1=\(dto1) dto2=\(dto2)")
}
Approach #1
It seems the field can be hidden in DTO1 with such code:
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: DTO1) {
appendInterpolation("field1_1: \(value.field1_1), fieldToHide: 🤷♀️, field1_2: \(value.field1_2), field1_3: \(value.field1_3)")
}
}
However, the solution is neither scalable nor maintainable:
the same extension should be added for each DTO
each field should be included into appendInterpolation - a lot of boilerplate
if a new field is added to some DTO, we may forget to update appendInterpolation etc
Approach #2
I tried to add interpolation for HiddenFieldType (assuming it's a type, just like DTO1...):
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: HiddenFieldType) {
appendInterpolation("🤷♀️")
}
}
But this solution doesn't work at all:
the compiler says "Function call causes an infinite recursion"
and it actually causes an infinite recursion
when changing appendInterpolation to appendLiteral, there's no recursion, but "super-secret-1" is not hidden
Approach #3
I tried overriding DefaultStringInterpolation, conforming to ExpressibleByStringLiteral/ExpressibleByStringInterpolation, but it doesn't work: the compiler says that HiddenFieldType is String, and Conformance of 'String' to protocol 'ExpressibleByStringLiteral' was already stated in the type's module 'Swift'
The only approach I can imagine is changing typealias HiddenFieldType = String to struct HiddenFieldType { let value: String }, so the HiddenFieldType becomes a "real" type.
Approach #4
Then such code doesn't cause an infinite recursion anymore, but doesn't works either (the value is unhidden)
struct HiddenFieldType {
let value: String
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: HiddenFieldType) {
appendInterpolation("🤷♀️")
}
}
Approach #5
This code finally works:
struct HiddenFieldType {
let value: String
}
extension HiddenFieldType: CustomStringConvertible {
var description: String {
"🤷♀️"
}
}
As I can't imagine any better, for now I'd use this approach, but it also has some slight scalability issues, as I must update each DTO's initializing point:
from
let dto1 = DTO1(field1_1: "1_1", fieldToHide: "super-secret-1", field1_2: "1_2", field1_3: "1_3")
to
let dto1 = DTO1(field1_1: "1_1", fieldToHide: .init(value: "super-secret-1"), field1_2: "1_2", field1_3: "1_3")
and I hoped to only add some extension in the file which contains typealias HiddenFieldType = String, and not to update the entire code.
The questions
Is it possible to hide the value of HiddenFieldType without changing it from typealias to struct, and without updating each DTO?
Is there any better approach than 5?
Thanks in advance
Is it possible to hide the value of HiddenFieldType without changing it from typealias to struct
I think you're attempting to use the wrong tool for the job here. A typealias is just a name change, and it sounds like you want something that acts fundamentally different than a String (i.e. one gets printed when passed into an os_log call and one doesn't). You won't be able to write logic that treats a String different from its typealias; the compiler doesn't differentiate between them.
Is it possible to make your DTOs classes instead of structs? (EDIT: see below, you can keep them as structs and just use a protocol extension) If so, you could use reflection on a superclass to accomplish this without having to manually specify the description for every different DTO.
struct HiddenFieldType {
let value: String
}
open class DTO: CustomStringConvertible {
public var description: String {
Mirror(reflecting: self).children.compactMap { $0.value as? String }.joined(separator: "\n")
}
}
final class DTO1: DTO {
let field1_1: String
let field1_2: String
let fieldToHide: HiddenFieldType
init(field1_1: String, field1_2: String, fieldToHide: HiddenFieldType) {
self. field1_1 = field1_1
self. field1_2 = field1_2
self. fieldToHide = fieldToHide
}
}
Note that I'm including all Strings in the description but, if you have types other than String and HiddenFieldType that you want to log, you could always just filter out the HiddenFieldTypes specifically.
Personally, I'd be hesitant to rely on reflection for any critical code but other people have more tolerance for it so its a judgement call.
EDIT:
You don't need to use inheritance to accomplish this. Instead of a superclass, DTO should be a protocol that conforms to CustomStringConvertible:
protocol DTO: CustomStringConvertible {}
extension DTO {
public var description: String {
Mirror(reflecting: self).children.compactMap { $0.value as? String }.joined(separator: "\n")
}
}
I have the following code, which compiles & works fine:
import RealmSwift
struct Bucket: Codable, Identifiable {
var id: UUID
var title: String
init() {
self.id = UUID()
self.title = "new bucket"
}
init(title: String) {
self.id = UUID()
self.title = title
}
init(id: UUID, title: String) {
self.id = id
self.title = title
}
}
class RealmBucket : Object {
#Persisted var id : UUID
#Persisted var title : String
convenience init(_ id: UUID, _ title: String) {
self.init()
self.id = id
self.title = title
}
}
func loadBuckets() -> [Bucket] {
let realm = try! Realm()
let realmBuckets = realm.objects(RealmBucket.self)
return realmBuckets.map { Bucket(id: $0.id, title: $0.title) }
}
but if I change the loadBuckets() function to:
func loadBuckets() -> [Bucket] {
let realm = try! Realm()
let realmBuckets = realm.objects(RealmBucket.self)
let result = realmBuckets.map { Bucket(id: $0.id, title: $0.title) }
return result
}
(just the last line changed)
I get the error:
Cannot convert return expression of type 'LazyMapSequence<Results<RealmBucket>, Bucket>' to return type '[Bucket]'
If I change the let line to be:
let result : [Bucket] = realmBuckets.map { Bucket(id: $0.id, title: $0.title) }
then it works again.
I can think of a couple possible explanations for the behavior, but they all seem to point to a compiler bug, or language deficiency, and I'm guessing that perhaps there is some language feature I'm unaware of.
Does anyone know why the compiler is able to automatically convert the LazyMapSequence in the case of the return value being a variable, when it clearly knows the type of the variable given the error it is giving me. I'm relatively new to Swift, and hoping to learn something from this.
Based on the current answers, my assumption is that it is just a slightly different case in the compiler code to convert the variable versus a method call, so it's really just a compiler deficiency, and likely to not exist down the road. In any case, it's easy enough to work around, it just struck me as odd.
You have to explicitly define the return type of map function, when you use shorthand closure syntax, probably that is the reason.
A “return” statement knows the type it has to return, so if applies that knowledge when the right hand side is evaluated, and can often convert that result to the correct type.
A let statement with no type means the compiler doesn’t know which type is needed. So the right hand side can be evaluated differently.
So here's something that's been bugging me for a while and I've been trying to find a good pattern to use. The problem occurs when I need to create a list of items that conform to a protocol, that has an associated type. For example:
protocol Setting {
associatedtype Value
var value: Value
}
struct ProjectSetting<T>: Setting {
var value: T
}
let setting1 = ProjectSetting(value: 1)
let setting1 = ProjectSetting(value: "abc")
The problem occurs when then trying to store ProjectSetting instances in an array.
let settings: /* ??? */ = [ProjectSetting(value: 1), ProjectSetting(value: "abc")]
Swift won't let me do let settings: [Setting] = ... because of the associated type, and it won't let me do let settings: [ProjectSetting<Any>] = ... either.
So I need to do some sort of type erasure to hide the type of the setting, but everything I've tried ends up needing the generic type exposed. I've tried to type erase by wrapping closures but I end up either exposing an Any or the generic type again.
Does anyone have a technique for wrapping a generic protocol so that it can be stored in an array regardless of the type being used?
Hopefully the following approach fits your needs. I did it only for getter just to simplify the demo, but the idea should be clear.
Note: used Xcode 11.2 / Swift 5.1 / Catalina
So here is your original entities
protocol Setting {
associatedtype Value
var value: Value { get }
}
struct ProjectSetting<T>: Setting {
let value: T
}
Now we need some helper protocols to hide your type differences, aka type erasers
private protocol TypeErasing {
var value: Any { get }
}
private struct TypeEraser<V: Setting>: TypeErasing {
let orinal: V
var value: Any {
return self.orinal.value
}
}
Now the core entity that wraps your concrete implementors holding different type values, but still allows to use those values and be stored in standard containers
struct AnySetting : Setting {
typealias Value = Any
private let eraser: TypeErasing
init<V>(_ setting: V) where V:Setting {
eraser = TypeEraser(orinal: setting)
}
var value: Any {
return eraser.value
}
}
Now testing your expectation
let settings = [AnySetting(ProjectSetting(value: 1)), AnySetting(ProjectSetting(value: "abc"))]
if let value = settings[0].value as? Int {
print("Stored value: \(value)")
}
if let value = settings[1].value as? String {
print("Stored value: \(value)")
}
PlayGround output
Stored value: 1
Stored value: abc
I am a beginner Swift learner and I have a question about protocols. I have followed a tutorial that teaches you about linked lists, which is as follows:
Node:
class LinkedListNode<Key> {
let key: Key
var next: LinkedListNode?
weak var previous: LinkedListNode?
init (key: Key) {
self.key = key
}
}
And the linked list:
class LinkedList<Element>: CustomStringConvertible {
typealias Node = LinkedListNode<Element>
private var head: Node?
// irrelevant code removed here
var description: String {
var output = "["
var node = head
while node != nil {
output += "\(node!.key)"
node = node!.next
if node != nil { output += ", " }
}
return output + "]"
}
}
The var description: String implementation simply lets you to print each elements in the linked list.
So far, I understand the structure of the linked list, my problem isn't about the linked list actually. What I don't understand is the protocol CustomStringConvertible. Why would it be wrong if I have only the var description: String implementation without conforming to the protocol? I mean, this protocol just simply say "Hey, you need to implement var description: String because you are conformed to me, but why can't we just implement var description: String without conforming to the protocol?
Is it because in the background, there is a function or some sort that takes in a type CustomStringConvertible and run it through some code and voila! text appears.
Why can't we just implement var description: String without conforming to the protocol?
Compare:
class Foo {
var description: String { return "my awesome description" }
}
let foo = Foo()
print("\(foo)") // return "stackoverflow.Foo" (myBundleName.Foo)
and
class Foo: CustomStringConvertible {
var description: String { return "my awesome description" }
}
let foo = Foo()
print("\(foo)") // return "my awesome description"
When you use CustomStringConvertible, you warrant that this class have the variable description, then, you can call it, without knowing the others details of implementation.
Another example:
(someObject as? CustomStringConvertible).description
I don't know the type of someObject, but, if it subscriber the CustomStringConvertible, then, I can call description.
You must conform to CustomStringConvertible if you want string interpolation to use your description property.
You use string interpolation in Swift like this:
"Here's my linked list: \(linkedList)"
The compiler basically turns that into this:
String(stringInterpolation:
String(stringInterpolationSegment: "Here's my linked list: "),
String(stringInterpolationSegment: linkedList),
String(stringInterpolationSegment: ""))
There's a generic version of String(stringInterpolationSegment:) defined like this:
public init<T>(stringInterpolationSegment expr: T) {
self = String(describing: expr)
}
String(describing: ) is defined like this:
public init<Subject>(describing instance: Subject) {
self.init()
_print_unlocked(instance, &self)
}
_print_unlocked is defined like this:
internal func _print_unlocked<T, TargetStream : TextOutputStream>(
_ value: T, _ target: inout TargetStream
) {
// Optional has no representation suitable for display; therefore,
// values of optional type should be printed as a debug
// string. Check for Optional first, before checking protocol
// conformance below, because an Optional value is convertible to a
// protocol if its wrapped type conforms to that protocol.
if _isOptional(type(of: value)) {
let debugPrintable = value as! CustomDebugStringConvertible
debugPrintable.debugDescription.write(to: &target)
return
}
if case let streamableObject as TextOutputStreamable = value {
streamableObject.write(to: &target)
return
}
if case let printableObject as CustomStringConvertible = value {
printableObject.description.write(to: &target)
return
}
if case let debugPrintableObject as CustomDebugStringConvertible = value {
debugPrintableObject.debugDescription.write(to: &target)
return
}
let mirror = Mirror(reflecting: value)
_adHocPrint_unlocked(value, mirror, &target, isDebugPrint: false)
}
Notice that _print_unlocked only calls the object's description method if the object conforms to CustomStringConvertible.
If your object doesn't conform to CustomStringConvertible or one of the other protocols used in _print_unlocked, then _print_unlocked creates a Mirror for your object, which ends up just printing the object's type (e.g. MyProject.LinkedList) and nothing else.
CustomStringConvertible allows you to do a print(linkedListInstance) that will print to the console whatever is returned by the description setter.
You can find more information about this protocol here: https://developer.apple.com/reference/swift/customstringconvertible
I'm learning about extension in swift and I want to create a extension for String like the command .hasPrefix(), in that command we send a String, for test it I try this code:
extension String{
var teste:(String) { return "\(self) - \($1)" }
}
texto.teste("other String")
But not working, all I want to do is create a extension that we can send other values like .hasPrefix (that send a string inside) .hasSufix (send a string too), How can i do this?
var teste: String { ... } is a computed property, and computed
properties cannot take parameters.
You'll want to define an extension method:
extension String {
func teste(arg : String) -> String {
return "\(self) - \(arg)"
}
}
println("foo".teste("bar"))
// foo - bar