First, I'm new to Swift language and have worked using python which is 'dynamic typed' language, so don't know exact compiler's perspective.
Below code is a simple code snippet to practice what Apple said POP(protocol oriented programming).
Snippet contains various ReturnClasses, RunnerClasses(each has different return type) and actual runner which just run RunnerClasses. Also has Selector enum to pretend dynamic dispatch.
protocol Return {
var value: Int { get set }
init(_ value: Int)
}
final class Return0: Return {
var value: Int
init(_ value: Int) {
print("return 0")
self.value = value
}
}
final class Return1: Return {
var value: Int
init(_ value: Int) {
print("return 1")
self.value = value + 50
}
}
final class Return2: Return {
var value: Int
init(_ value: Int) {
print("return 2")
self.value = value + 500
}
}
protocol FirstProtocol {
init()
func a() -> Return
func b() -> Return
}
extension FirstProtocol {
func a() -> Return {
print("protocol a hello")
return self.b()
}
func b() -> Return {
print("protocol b hello")
return Return0(5)
}
}
enum Selector {
case first, second
func cls() -> FirstProtocol{
switch self {
case .first:
return First1Class()
case .second:
return First2Class()
}
}
}
final class First1Class: FirstProtocol {
init() {
}
func b() -> Return1 {
print("first 1 class hello")
return Return1(3)
}
}
final class First2Class: FirstProtocol {
init() {
}
func b() -> Return2 {
print("first 2 class hello")
return Return2(5)
}
}
final class Runner {
var cls: FirstProtocol
var selector: Selector
init(_ selector: Selector) {
self.cls = selector.cls()
self.selector = selector
}
func createCls(cls: FirstProtocol.Type) -> FirstProtocol {
return cls.init()
}
func run(cls: FirstProtocol.Type) {
print("--- run 1 ---")
(self.createCls(cls: cls) as! First2Class).a()
}
func run2(t: Selector) -> Return {
print("--- run 2 ---")
print(t.cls())
return t.cls().b()
}
func run3() -> Return {
print("--- run 3 ---")
print(self.cls)
return self.cls.a()
}
}
final class Runner2 {
var runner: Runner
init(runner: Runner) {
self.runner = runner
}
func run() -> Return {
print("--- 2run 1 ---")
return self.runner.run2(t: .second)
}
}
var a = Runner2(runner: Runner(.second)).run()
print(type(of: a))
print(a.value)
And returns below result.
--- 2run 1 --- line 1
--- run 2 --- line 2
__lldb_expr_80.First2Class line 3
protocol b hello line 4
return 0 line 5
Return0 line 6
5 line 7
And here I have a question.
In line 3 which is result of Runner.run2, I think __lldb_expr_80.First2Class string means compiler exactly knows which class should be initiated. Therefore line 4 should be first 2 class hello not protocol b hello. Why protocol b hello is printed and how to make first 2 class hello is printed?
This is because the b in First2Class is not actually a witness for the b requirement in the FirstProtocol (same goes for First1Class). In other words, the reason why First2Class conforms to FirstProtocol is not the b method declared in it. The only witness for the protocol requirement is only the b in the protocol extension.
This is because the return types of these bs don't match the protocol requirement's return type:
// protocol requirement
func b() -> Return {
...
}
// First1Class
func b() -> Return1 {
...
}
// First2Class
func b() -> Return2 {
...
}
Return types must match exactly in order for a type to implement a protocol. See more discussions here.
When you call b on a protocol type, such as here:
return t.cls().b()
(t.cls() is of compile-time type FirstProtocol) because the bs are not the witness, your bs do not get considered when resolving the call. The call will only resolve to the only witness, which is the b in the protocol extension.
...means compiler exactly knows which class should be initiated.
That's not true. Remember that you only see the message printed at runtime. The compiler doesn't know which class is instantiated by cls.
Here is a shorter example that demonstrates this:
class A {}
class B : A {}
protocol Foo {
func foo() -> A
}
extension Foo {
func foo() -> A {
print("Foo")
return A()
}
}
class Bar : Foo {
func foo() -> B {
print("Bar")
return B()
}
}
let foo: Foo = Bar()
let bar = Bar()
foo.foo() // prints Foo
bar.foo() // prints Bar
Related
Suppose we have:
class BaseClass {
var id: String
}
class Child1: BaseClass {}
class Child2: BaseClass {}
struct Structure<T : BaseClass> {
var map = [String: T]()
}
Is it possible for an extension to return the specific type?
extension BaseClass {
static func <- <T : BaseClass>(left: T, right: T) -> Structure<T> where T == Self {
return Structure(map: [left.id, right])
}
}
The compiler doesn't like T == Self, or the left/right operator being T, but you can see what I'm trying to do. I'm trying to return the specific type of BaseClass so that I only have to implement these operators once.
So then you could use it like this:
var child1 = Child1()
var child11 = Child1()
// the structure returned matches the type of the operator inputs
var structure: Structure<Child1> = child1 + child11
If I try to put T in the operator left, right params, the compiler doesn't like that either.
This also doesn't work (for obvious reasons), but if there a way I could rewrite it to make it work?
extension BaseClass {
func combine<T : BaseClass>(with: T) -> Structure<T> {
// this doesn't work because 'self' can be assumed to be T
return Structure<T>(map: [self.id : with])
}
}
You can define the operator. You just can't put it inside of a type.
func + <T>(left: T, right: T) -> Structure<T> {
.init(map: [left.id: right])
}
var structure = Child1() + Child1()
If you want it to be a method, you'll need a protocol.
protocol BaseClassProtocol: BaseClass { }
extension BaseClassProtocol {
func combine(with instance: Self) -> Structure<Self> {
.init(map: [id: instance])
}
}
extension BaseClass: BaseClassProtocol { }
…but if you're going to have a protocol, you can throw the operator in there too if you want.
extension BaseClassProtocol {
static func + (left: Self, right: Self) -> Structure<Self> {
.init(map: [left.id: right])
}
func combine(with instance: Self) -> Structure<Self> {
self + instance
}
}
Simplified example
Take a look at this simple protocol
protocol FooOwner {
static var foo: Self { get }
}
I would like an enum to conform to said protocol, and in my opinion this ought to work, since the static syntax SomeTypeConformingToFooOwner.foo should result in an instance of SomeTypeConformingToFooOwner and in the case where SomeTypeConformingToFooOwner is a enum.
enum Foo: FooOwner { // Type 'Foo' does not conform to protocol FooOwner
case foo
}
The workaround is this ugly thing:
protocol FooOwner {
static var fooOwner: Self { get }
}
enum Foo: FooOwner {
case foo
static var fooOwner: Foo {
return Foo.foo
}
}
Do you have a nicer workaround for enum conforming to protocols with static vars?
Real use case
protocol StringConvertibleError: Swift.Error {
static var invalidCharactersError: Self { get }
}
protocol StringConvertibleErrorOwner {
associatedtype Error: StringConvertibleError
}
protocol StringConvertible {
var value: String { get }
/// Calling this with an invalid String will result in runtime crash.
init(validated: String)
init(string value: String) throws
static func validate(_ string: String) throws -> String
}
// MARK: - Default Implementation Constrained
extension StringConvertible where Self: CharacterSetSpecifying, Self: StringConvertibleErrorOwner {
static func validate(_ string: String) throws -> String {
guard Self.allowedCharacters.isSuperset(of: CharacterSet(charactersIn: string)) else {
throw Error.invalidCharactersError
}
// Valid
return string
}
}
struct HexString: StringConvertible, CharacterSetSpecifying, StringConvertibleErrorOwner {
static var allowedCharacters = CharacterSet.hexadecimal
let value: String
init(validated unvalidated: String) {
do {
self.value = try HexString.validate(unvalidated)
} catch {
fatalError("Passed unvalid string, error: \(error)")
}
}
}
extension HexString {
enum Error: StringConvertibleError {
static var invalidCharactersError: Error {
return Error.invalidCharacters
}
case invalidCharacters
}
}
So it's the last part that I would like to change to:
extension HexString {
enum Error: StringConvertibleError {
case invalidCharacters
}
}
Since I have many similar types to HexString.
Yes of course obviously I can use one shared Error enum, but I would like to have one specific enum per type.
SE-0280 solves this - if accepted. It is currently in review.
Edit 1
Great news, SE-0280 has been accepted.
This might not fully match OP's issue, but as it seems somewhat related, I'm throwing it out there in case it helps.
In my case, I cared about some cases, but other cases could be ignored (or handled in a common way).
I admit my approach is not elegant and requires a lot of boiler plate, but it got me past a similar problem.
// Given these enums, we want a protocol that matches enums with
// 'foo' and 'bar' cases.
enum FooFriends {
case foo // conforms
case bar // conforms
case baz // don't really care
}
enum FooBar {
case foo // conforms
case bar // conforms
}
// We wish we could do this:
protocol FooBarWish {
case foo // or static var foo: Self { get }
case bar // or static var bar: Self { get }
}
// Workaround
// the boiler plate
enum FooBarProxy {
case foo
case bar
case other
}
protocol FooBarProtocol {
func getProxy() -> FooBarProxy
}
extension FooFriends: FooBarProtocol {
func getProxy() -> FooBarProxy {
switch self {
case .foo:
return .foo
case .bar:
return .bar
default:
return .other
}
}
}
extension FooBar: FooBarProtocol {
func getProxy() -> FooBarProxy {
switch self {
case .foo:
return .foo
case .bar:
return .bar
}
}
}
// Usage
// Instead of the ideal case (which won't work)
let fooBarOrFooFriend1: FooBarWish = FooFriends.foo
let fooBarOrFooFriend2: FooBarWish = FooBar.bar
// We can get by with
let fooBarProxy1 = FooFriends.foo.getProxy()
let fooBarProxy2 = FooBar.bar.getProxy()
// Verification
func checkIt(_ foobar: FooBarProxy) {
switch foobar {
case .foo:
print("it was foo")
case .bar:
print("it was bar")
case .other:
print("it was neither")
}
}
checkIt(fooBarProxy1)
checkIt(fooBarProxy2)
// =>
// it was foo
// it was bar
I have a sample example:
struct Test<T: SignedNumeric & Comparable> {
let n: T
init(n: T) {
self.n = n
}
}
extension Test where T: BinaryInteger {
func half() -> T {
return self.n / 2
}
func printHalf() {
print(self.half())
}
}
extension Test where T: FloatingPoint {
func half() -> T {
return self.n / 2
}
func printHalf() {
print(self.half())
}
}
Test(n: 2).printHalf() // print "1"
Test(n: 2.2).printHalf() // print "1.1
This code compiles and works. But I do not like:
1) I must additionally declare 2 extensions.
2) I have to duplicate printHalf method.
How correctly to implement this example? (just is inside the method?)
I would like to have a protocol that defines some methods and properties. However, property types and method return types may vary between the different classes that conform to said protocol. For example: A.getContent() may return a value of type String, but B.getContent() may return a value of type Int. In my example below, I used the type Any. Is this possible in Swift or is this a totally wrong approach? Maybe with generics?
protocol Content {
func getContent() -> any
}
class A: Content {
func getContent() -> String {
return "Im a String"
}
}
class B: Content {
func getContent() -> Int {
return 1234
}
}
I think you are looking about Generics in Protocol.
You can associate a type dynamically with associatedtype, for example
protocol Content{
associatedtype T
func getContent()-> T
}
class A: Content {
func getContent() -> String {
return "Hello World"
}
}
class B: Content {
func getContent() -> Int {
return 42
}
}
A().getContent() //"Hello World"
B().getContent() //42
If you look at this example when you put the Type after the function in the class sun of Content, the protocol Content will be this one type
Update
I am putting another example using "swiftly" syntax instead of traditional getContent.
protocol Content{
associatedtype T
var content:T { get }
}
class A: Content {
var content:String{
return "Hello World"
}
}
class B: Content {
var content:Int{
return 42
}
}
A().content //"Hello World"
B().content //42
You can use generics and meta-types:
protocol Content {
func getContent<T>(ofType: T.Type) -> T?
}
class A: Content {
func getContent<T>(ofType: T.Type) -> T? {
return "Im a String" as? T ?? nil
}
}
class B: Content {
func getContent<T>(ofType: T.Type) -> T? {
return 1234 as? T ?? nil
}
}
let aClass = A()
let aValue = aClass.getContent(ofType: String.self) // "Im a String"
let bClass = B()
let bValue = bClass.getContent(ofType: Int.self) // 1234
Say I have a generic class
class Foo<T> { … }
Can I somehow specify that instances of this class can be converted to T in assignments? Example:
let foo = Foo<Int>()
func useAnInt(a: Int) {}
let getTheInt: Int = foo
useAnInt(foo)
Why not just use the underlying type? (Similar to #MrBeardsley's answer)
class Foo<T> {
var t : T
init(t: T) {
self.t = t
}
}
let foo = Foo(t: 3)
func useAnInt(a: Int) {}
let getTheInt: Int = foo.t
useAnInt(foo.t)
You are not able to do what you are asking. Even though Foo defines a generic type T, instances of Foo are still Foo and cannot be converted to the generic type. The reason you would declare a generic type at the class level is to use it multiple places throughout the class.
class Foo<T> {
var value: T
init(withValue: T) {
self.value = withValue
}
func getSomeValue() -> T {
return self.value
}
}
Generics don't mean the class itself is generic and can be converted.
One way of achieving what you want is to use a dedicated protocol for each of the target types that your class shall be convertible to. Here is very basic example:
protocol BoolAdaptable {
func adaptToBool() -> Bool
}
protocol IntAdaptable {
func adaptToInt() -> Int
}
protocol FloatAdaptable {
func adaptToFloat() -> Float
}
class Foo: BoolAdaptable, IntAdaptable, FloatAdaptable {
var v: NSNumber
init(_ v: NSNumber) {
self.v = v
}
func adaptToBool() -> Bool {
return v.boolValue
}
func adaptToInt() -> Int {
return v.integerValue
}
func adaptToFloat() -> Float {
return v.floatValue
}
}
let foo = Foo(NSNumber(double: 1.23))
let getTheBool = foo.adaptToBool() // true
let getTheInt = foo.adaptToInt() // 1
let getTheFloat = foo.adaptToFloat() // 1.23
This could easily be extended to support more complex conversions.