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
Related
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.
for example we have simple enum
public enum CXActionSheetToolBarButtonItem {
case cancel
case done
case now
private static var titles: [CXActionSheetToolBarButtonItem: String] = [
.cancel: "Cancel",
.done: "Done",
.now: "Now",
]
public var title: String {
get { return CXActionSheetToolBarButtonItem.titles[self] ?? String(describing: self) }
// what am I want to do
set(value) { CXActionSheetToolBarButtonItem.titles[self] = value }
}
// what am I forced to do
public static func setTitle(_ title: String, for item: CXActionSheetToolBarButtonItem) {
CXActionSheetToolBarButtonItem.titles[item] = title
}
}
why I don't can set new title like this
CXActionSheetToolBarButtonItem.cancel.title = "asd"
compiler responded error - Cannot assign to property: 'cancel' is not settable, but I can set title with function
CXActionSheetToolBarButtonItem.setTitle("asd", for: .cancel)
what should I change for worked my var as settable? .cancel.title = "asd"
Using an enum for this seems inappropriate, but I'll address the porblem at face value. You need to mark your setter as nonmutating, so that it can be called on non-var instances of your enum:
public enum CXActionSheetToolBarButtonItem {
// ...
public var title: String {
get { return CXActionSheetToolBarButtonItem.titles[self] ?? String(describing: self) }
nonmutating set(value) { CXActionSheetToolBarButtonItem.titles[self] = value }
}
}
CXActionSheetToolBarButtonItem.cancel.title = "foo" // Works... but why would you want this?!
I think the main reason why you can't do that is because the Swift compiler sees enum cases as immutable by default (similar to an immutable struct declared with let), and here you are trying to mutate it.
A good way to see this is if you try to add a mutating function to this enum
mutating public func setTitle2(_ newValue: String) {
CXActionSheetToolBarButtonItem.titles[self] = newValue
}
You will then receive the error message
error: cannot use mutating member on immutable value: 'cancel' returns immutable value
How to work around this
One way to have a similar behavior is by changing this enum into a set of static variables (which is more consistent with what you are trying to achieve).
public struct CXActionSheetToolBarButtonItem {
var title: String
static var cancel = CXActionSheetToolBarButtonItem(title: "Cancel")
static var done = CXActionSheetToolBarButtonItem(title: "Done")
static var now = CXActionSheetToolBarButtonItem(title: "Now")
}
Now you can use the following
CXActionSheetToolBarButtonItem.cancel.title = "asd"
Also, you still have the ability to use the dot-syntax
let buttonItem: CXActionSheetToolBarButtonItem = .cancel // it works
Hope that helps!
TD;DR: Because enum is an immutable object.
First of all: Whatever you're trying to do here – it's certainly not a good approach and pretty dangerous. As Craig pointed out in his comment, you're messing around with instance properties and static properties. You can have multiple instances of an enum – but when you want to change the title of a particular instance, you also change the title for all other instances. That's unexpected behavior and you should really think of another solution.
That being said, your code actually does work – with a little modification: Instead of
CXActionSheetToolBarButtonItem.cancel.title = "asd"
you can write
var item = CXActionSheetToolBarButtonItem.cancel
item.title = "asd"
This will compile.
The reason behind it is that an enum is a value type. Everytime you create an instance of an enum type, e.g. var item = CXActionSheetToolBarButtonItem.cancel, the enum's value is copied into the new variable item. You can choose if you want that value to be mutable or not by using either var or let. That's how Swift enums are intended to be used.
A single enum case like CXActionSheetToolBarButtonItem.cancel is immutable by definition.
CXActionSheetToolBarButtonItem.cancel.title = "asd"
wouldn't have any meaning because there is no instance to which the title can be assigned. The enum case .cancel is not bound to a variable.
Is there anyway to use conversion using a variable? I am using object stacking using type of "AnyObject" and I've been able to take the class type and populate a variable. Now I need to populate an array using conversion.
var myString = "Hello World"
var objectStack = [AnyObject]()
objectStack.append(myString)
let currentObject = String(describing: objectStack.last!)
var objectType = String()
let range: Range<String.Index> = currentObject.range(of: ":")!
objectType = currentObject.substring(to: range.lowerBound)
let range2: Range<String.Index> = objectType.range(of: ".")!
objectType = objectType.substring(from: range2.upperBound)
The code above will evaluate the class and set the value of "objectType" to "String". Now I'm trying to go the other way. Something like this:
for obj in objectStack{
obj = newObject as! objectType //this doesn't work
}
Is something like this possible?
There is a simpler, safer way to get the type:
let type = type(of: objectStack.last!) // String.Type
let typeString = String(describing: type) // "String"
The other way around is not possible because the type of the object is not known at compile time. Do you have a number of known types you want to try to cast to? In that case, use optional binding to check if the cast is successful:
let object = objectStack.last!
if let string = object as? String {
// do String stuff
}
else if let i = object as? Int {
// do Int stuff
}
// and so on
If you have a large number of possible types that share some common functionality: Use Protocols. See Swift Documentation for a nice introduction.
You define a protocol for some common functionality that different types can implement:
protocol Stackable {
func doStuff()
// (more methods or properties if necessary)
}
The protocol provides a contract that all types conforming to this protocol have to fulfill by providing implementations for all declared methods and properties. Let's create a struct that conforms to Stackable:
struct Foo: Stackable {
func doStuff() {
print("Foo is doing stuff.")
}
}
You can also extend existing types to make them conform to a protocol. Let's make String Stackable:
extension String: Stackable {
func doStuff() {
print("'\(self)' is pretending to do stuff.")
}
}
Let's try it out:
let stack: [Stackable] = [Foo(), "Cat"]
for item in stack {
item.doStuff()
}
/*
prints the following:
Foo is doing stuff.
'Cat' is pretending to do stuff.
*/
This worked for me:
var instance: AnyObject! = nil
let classInst = NSClassFromString(objectType) as! NSObject.Type
instance = classInst.init()
My example consists of:
protocol with read-only properties
struct implementing that protocol
"+" operator function
I'd like the "+" operator to be able to work for all implementations of the protocol by creating a new instance of the specific type that implements that protocol.
As you can see in the source code below, the operator accepts parameters of type "Aable" (the protocol). Using the generic placeholder T to construct a new instance fails with the error "'Aable' cannot be constructed because it has no accessible initializers".
Is that even possible?
Could it be achieved using reflections or some sort of introspection?
protocol Aable {
var name: String { get }
}
func +(left: Aable, right: Aable) -> Aable {
let leftType = left.dynamicType
//error: 'Aable' cannot be constructed because
// it has no accessible initializers
return leftType(name: left.name + right.name)
}
struct A: Aable {
let name: String
}
let a1 = A(name: "A #1")
let a2 = A(name: "A #2")
a1 + a2
You don't need reflection and AableError.
First of all lets add an init to your protocol
protocol Aable {
var name: String { get }
init(name:String)
}
Now you want a + operator where:
both params conform to the protocol Aable
the params have the same type
the return type is the same of the params
You can define this constraints with generics
func +<T:Aable>(left: T, right: T) -> T {
return T(name:left.name + right.name)
}
Now the check is performed at compile time so there's no need of the throws. The compiler will make sure the used values do have the proper type.
Test #1
struct Box:Aable {
let name:String
}
let amazon = Box(name: "Amazon")
let apple = Box(name: "Apple")
let res = amazon + apple
res.name // "AmazonApple"
Test #2
struct Box:Aable {
let name:String
}
struct Box:Aable {
let name:String
}
struct Bottle:Aable {
let name:String
}
let box = Box(name: "Amazon")
let bottle = Bottle(name: "Water")
box + bottle
// ^ compile error
How can I pass a protocol as a parameter in Swift?
In Objective-C I could do this:
id <CurrentUserContex> userContex = [ServiceLocator locate:#protocol(CurrentUserContex)];
Service locator:
+ (id)locate:(id)objectType
Edit
After Qbyte answer tried using:
ServiceLocator.locate(CurrentUserContex.self)
But I'm getting 'CurrentUserContex.Protocol' does not confirm to protocol 'AnyObject'
So I tried:
ServiceLocator.locate(CurrentUserContex.self as! AnyObject)
But then I get:
Could not cast value of type 'ApplicationName.CurrentUserContex.Protocol' (0x7fec15e52348) to 'Swift.AnyObject' (0x7fec15d3d4f8).
I would suggest to use this if CurrentUserContex is the name of the protocol itself:
ServiceLocator.locate(CurrentUserContex.self)
If CurrentUserContex is a variable which type is a protocol use:
ServiceLocator.locate(CurrentUserContex)
I hope this solves your problem
In Swift you have to pass a class (all classes conform to the AnyObject protocol). So CurrentUserContex has to be a class too and you can try it with .self or without (Unfortunately, I don't have enough reference to tell you exactly which to use).
Try
ServiceLocator.locate(CurrentUserContex.self as Protocol)
or (in some versions with a bug Protocol isn't recognized as being AnyObject for some reason):
ServiceLocator.locate((CurrentUserContex.self as Protocol) as! AnyObject)
Protocol as a method argument:
protocol Describable: class{ var text: String { get } }
class A: Describable { var text: String; init(_ text: String) { self.text = text } }
class B: A {}
class C {}
let a = A("I am a")
let b = B("I am b")
let c = C()
func ofType<T>(instance: Any?,_ type: T.Type) -> T? { /*<--we use the ? char so that it can also return a nil*/
if (instance as? T != nil) { return instance as? T }
return nil
}
Swift.print(ofType(a, A.self)!.text) // I am a
Swift.print(ofType(a, Describable.self)!.text) // I am a
Swift.print(ofType(b, B.self)!.text) // I am b
Swift.print(ofType(c, C.self)) // instance of c