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
}
}
Related
I'm trying to instantiate my viewmodel for testing, in this case I don't need its parameters, but as it asks me to add them, when I try I get an error "Constant 'data' used before being initialized"
This is my code:
struct Account: Codable {
let details: [Details]?
}
struct Details: Codable {
let id: String?
let currency: String?
let interest: Float64?
let date: String?
}
class DetailViewModel {
private let data: Details?
init(data: Details) {
self.data = data
}
}
Tests:
class DetailViewModelTest: QuickSpec {
override func spec() {
var viewModel : DetailViewModel!
let data: Details!
viewModel = DetailViewModel(data: data) // I have error in this line
}
}
Is there a way to instantiate my viewmodel without parameters? Or a better way to handle this?
To use Details in a test with hardcoded values you either need to create it from some json or add another init to initialise all values, here I am using the latter. I am adding it in an extension and created a static method that uses the init to create an object with hard coded values.
extension Details {
private init(id: String, currency: String, interest: Float64, date: String) {
self.id = id
self.currency = currency
self.interest = interest
self.date = date
}
static func createStub() -> Details {
Details(id: "1", currency: "EUR", interest: 1.23, date: "2022-02-12")
}
}
This is one way of doing it, the init could be designed in many ways but this is to show you how to move forward.
This can then be used in the test class
class DetailViewModelTest: QuickSpec {
override func spec() {
let viewModel = DetailViewModel(data: Details.createStub())
//...
}
}
you should:
let data: Details = Details() // create your data
I have parent class called "Item", and child class called "YTListItem". I want to have a functions called instantiate() which will be implemented in each child class, and return a view controller. The type of the view controller will be different depending on which child class is calling the instantiate() method. My issue is that swift does not seem to recognize me overriding the function if it has different parameters or return types.
The error occurs when I override the function, I get the error "Method does not override any method from its superclass".
class Item {
var name: String
var description: String
init(name: String = "Test text", description: String = "Test description of the item") {
self.name = name
self.description = description
}
func instantiate() {}
}
class YTListItem: Item {
var address: String
init(name: String = "Test text", description: String = "Test text", address: String = "SivTGfXxYz0") {
self.address = address
super.init(name: name, description: description)
}
override func instantiate(storyboard: UIStoryboard) -> YTDescViewController? {
let vc = storyboard.instantiateViewController(identifier: "desc") as? YTDescViewController
return vc
}
}
Is there a way to do this? I've seen mentions of protocols when searching how to make it work but I'm new to swift so I'm hoping to be able to get through it using methods I've already learned.
First of all I don't think you should be instantiating a ViewController from you model class. Instead you should be injecting your model to the ViewController. But for your particular scenario you can just return a UIViewController from instantiate function rather than it's subclass (YTDescViewController). And I think you should use protocols for your scenario something like this:
protocol Item {
var name: String {get set}
var description: String {get set}
var storyboard : UIStoryboard {get set}
func instatntiate() -> UIViewController
}
extension Item {
var viewControllerID : String {
return "desc"
}
}
struct YTListItem: Item {
var name: String
var description: String
var storyboard: UIStoryboard
func instatntiate() -> UIViewController {
return storyboard.instantiateViewController(identifier: viewControllerID)
}
}
You can also use associatedType to customize the return type of instantiate (or the parameters) in Item protocol. But in that case you would need to implement type erasures to hold generic reference to class/struct objects that implement Item protocol.
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 ?? "")"
I have a protocol that has a function that can return a String or a [String: String]. This is my declaration:
protocol Test {
associatedtype T: Hashable
func returnSomething() -> T
}
Then I want a default implementation for returnSomething, so I made a protocol extension:
extension Test {
func returnSomething() -> T {
let valueToReturn = readValueFromPLISTthatCanReturnAStringOrDictionary() as T
return valueToReturn
}
}
So finally I have 2 clases, TestString and TestDictionary that both implements Test protocol and I want to indicate the T parameter and I want to use the default implementation. How I do this?
class TestString: Test {}
class TestDictionary: Test { }
class TestString: Test where Test.T = String or similar?
I have a protocol that has a function that can return a String or a [String: String]. This is my declaration:
No problem. Let's write that down.
enum StringOrDictionary {
case string(String)
case dictionary([String: String])
}
protocol Test {
func returnSomething() -> StringOrDictionary
}
Then I want a default implementation for returnSomething, so I made a protocol extension:
Sounds good. I'll assume that readValueFromPLISTthatCanReturnAStringOrDictionary() actually returns Any, since that's what is returned by propertyList(from:).
extension Test {
func returnSomething() -> StringOrDictionary {
let value = readValueFromPLISTthatCanReturnAStringOrDictionary()
switch value {
case let string as String: return .string(string)
case let dictionary as [String: String]: return .dictionary(dictionary)
default: fatalError() // Or perhaps you'd like to do something else
}
}
}
It'd probably be nice to name your type something more meaningful than StringOrDictionary, but other than that, it should be pretty straightforward. Just make a type that means what you say. You want a type that means "OR" and that is an enum. (If you want a type that means "AND" that's a struct BTW.)
Regarding your answer, this isn't legal:
class RandomClass: Test where Test.T == String {
func getValue() {
let bah = doSomething() // I don't need here to specify bah's type.
}
}
The way to define your T is to implement the required method.
class RandomClass: Test {
func returnSomething() -> String {
return ""
}
}
If you wanted to share some common code, then you can attach that as an extension rather than a default implementation. You could write a returnString() method and call it from the RandomClass.returnSomething(). This is all very useful in some cases, but I definitely wouldn't use it in this case. You don't mean "returns any possible type (T)." You mean "returns one of two possible types" and that's an enum, not a generic.
Update: Apparently they've added a new feature that they've talked about but I thought wasn't in yet. You could now implement RandomClass this way:
class RandomClass: Test {
typealias T = String
}
(Which is a very nice new feature, even if it's not a good answer for this problem.)
Here's a solution to your immediate problem:
Create 2 subtypes of your protocol, each with a different definition of the associated type, and a different default implementation. You select which default implementation you'd like your classes to use by picking between the 2 sub types.
The next issue here is that [String: String] isn't Hashable. This is due to a lack of support for conditional conformances (e.g. the ability to express that a Dictionary is Hashable iff the keys and values are both Hashable), one of Swift's largest downfalls, IMO. You'll probably want to use the type erasing wrapper AnyHashable.
protocol ResultProvider {
associatedtype Result: Hashable
func getResult() -> Result
}
protocol StringResultProvider: ResultProvider {
typealias Result = String
}
extension StringResultProvider {
func getResult() -> String {
return "A string result"
}
}
protocol IntResultProvider: ResultProvider {
typealias Result = Int
}
extension IntResultProvider {
func getResult() -> Int {
return 123
}
}
class TestIntResult: IntResultProvider {}
class TestString: StringResultProvider {}
print(TestString().getResult())
print(TestIntResult().getResult())
// protocol DictionaryResultProvider: ResultProvider {
// typealias Result = [String: String]
// }
// extension DictionaryResultProvider {
// func getResult() -> [String: String] {
// return ["A dictionary": "result"]
// }
// }
// class TestDictionaryProvider: DictionaryResultProvider {}
You need to specify the typealias when you extend the class, like so:
protocol Test {
associatedtype T: Hashable
func returnSomething() -> T
}
extension String: Test {
typealias T = Int
}
func def() -> Int {
return 6
}
extension Test {
func returnSomething() -> T {
return def() as! Self.T
}
}
"".returnSomething()
6
However, I couldn't find a way to do it without force casting.
The only working solution is made the generic in the function and specify the variable type when calling the function. I was wondering if i could specify the T type when i implement the protocol in the class, similar like this:
class RandomClass: Test where Test.T == String {
func getValue() {
let bah = doSomething() // I don't need here to specify bah's type.
}
}
But previous example just don't work, so an alternative could be this:
protocol Test {
func doSomething<T>() -> T
}
extension Test {
func doSomething<T>(key: String) -> T {
return returnDictOrStringFromPLIST(key: key) as! T
}
}
class TestString: Test {
func getValue() {
let bah: String = doSomething()
}
}
class TestDict: Test {
func getValue() {
let bah: [String: String] = doSomething()
}
}
I have a class MyClass implementing a generic function of a delegate Collection_Delegate.
My classes Collection and Item are superclasses for some specific classes
protocol Collection_Delegate {
func onFetchAllCompleted<T, U where T: Collection<U>, U: Item>(collection: T, error: String?)
}
class Collection<T>: Item {
private var items: [T]
override init (communicator: CG_API_Communicator) {
items = [T]()
super.init(communicator: communicator)
}
internal func fetchAll() {
fatalError(notImplemented)
}
internal func onFetchAllCompleted(error: String?, json: JSON?) {
fatalError(notImplemented)
}
internal func appendItem(item: T) {
self.items.append(item)
}
internal func getItems() -> [T] {
return self.items
}
}
class Item {
var itemDataRaw: JSON?
func toString() -> String? {
var retval: String?
if let value: String = itemDataRaw?.rawString(encoding: NSUTF8StringEncoding) {
retval = value
} else {
retval = "Something went badly wrong"
}
return retval
}
}
Now in some subclasses of Collection I want to call the generic onFetAllCompleted function of the delegate avery subclass has. But the class implementing the Collection_Delegate protocol causing compiler errors
class MyClass: Collection_Delegate { // Error
func onFetchAllCompleted<T, U where T: Collection<U>, U: Item>(collection: T, error: String?){
println("MyClass.onFetchAllCompleted:\(_stdlib_getTypeName(collection))") // This displays the right class name of the subclasses
let item: Item = collection.getItems()[0] //Error
let string = item.toString()
}
}
Here we go. The class **MyClass* gets the error
Type "MyClass" does not conform to protocol "Collection_Delegate"
Within the generic function I get the error
'U' is not convertible to 'Item'
So what am I doing wrong? Why doesn't the generic stuff work?
I think you over complicated things a bit with your generic function declaration. If I understood you correctly your onFetchAllCompleted function takes parameter T which is Collection of U's and U is an Item. If that is correct the above expression can be simplified like this : onFetchAllCompleted function takes parameter T which is Collection of Items. So your protocol and class should look like this
protocol Collection_Delegate {
func onFetchAllCompleted<T: Collection<Item>>(collection: T, error: String?)
}
class MyClass: Collection_Delegate {
func onFetchAllCompleted<T: Collection<Item>>(collection: T, error: String?){
println("MyClass.onFetchAllCompleted:\(_stdlib_getTypeName(collection))") // This displays the right class name of the subclasses
let item: Item = collection.getItems()[0] //Error
let string = item.toString()
}
}
Let me know if this helped you