miku1958
(Miku1958)
1
I am trying to write a class base on class clusters like NSArray:
class C {
init() { }
mutating init(use1: Bool) { // 'mutating' may only be used on 'func' declarations
self = use1 ? C1() : C2() // Cannot assign to value: 'self' is immutable
or
return use1 ? C1() : C2() // 'nil' is the only return value permitted in an initializer
}
}
class C1: C { }
class C2: C { }
is there any problems to achieve init with subclass in swift? I mean it could use static func to return subclass, but that is too weird that you can't use the init() right?
3 Likes
One scenario this could be tricky is when you're trying to subclass it.
class C3: C {
init() {
super.init(use1: true) // !??
}
}
Jens
4
You wouldn't even need to subclass to get into a weird situation, because if what you asked for would be allowed, then the initializer would also be available on C1, C2 and any other subclass of C, letting you write eg:
let myC2: C2 = C2(use1: true)
print(myC2 is C1) // true ... I guess.
which seems a bit nonsensical (since C1 is not a subclass of C2).
replacing self is finicky to begin with. It breaks Liskov principle at almost every turn.
This isn‘t a question about mutability inside init, this is the factory pattern which isn‘t yet supported for classes through the init.
4 Likes
miku1958
(Miku1958)
7
I have another idea
Actually the question is: I want to use something behave like init() to return subclass
We can do this now:
class C {
init() { }
static func `init`(use1: Bool) -> C { // Keyword 'init' cannot be used as an identifier here If this name is unavoidable, use backticks to escape it
return use1 ? C1() : C2()
}
}
class C1: C { }
class C2: C { }
then
let c = C.`init`(use1: <#T##Bool#>)
But we can't:
let c = C(use1: )
If I want to use C(use1: ), there is a actually way is callAsFunction, even It can't be use in static function and It will look very very confused because it's not named init():
class C {
init() { }
static func callAsFunction(use1: Bool) -> C {
return use1 ? C1() : C2()
}
}
class C1: C { }
class C2: C { }
let c = C(use1: true) // It is impossible now
So there is my simple solution, add a new attribute like @callAsFunction or other name (actually, I think callAsFunction should be done by using attribute from the beginning and make this feature be supported in Type, now just making all function named callAsFunction confusing everyone)
then:
class C {
init() { }
@callAsFunction static func `init`(use1: Bool) -> C {
return use1 ? C1() : C2()
}
}
class C1: C { }
class C2: C { }
let c = C(use1: true)
or
let c = C.`init`(use1: true) // is not good enough but at least it‘s clear
and the question from @Jens also can be avoid:
class C {
init() { }
}
extension C {
private class C1: C { }
private class C2: C { }
@callAsFunction static func `init`(use1: Bool) -> C where Self == C { // I think Self == C should be infered successed in Swift 5.3
return use1 ? C1() : C2()
}
}
Jens
8
But having C1 and C2 be private C.C1 and C.C2 makes them inaccessible/unusable outside C, ie if you try to use them outside the scope of C, you'd get this error:
'C1' is inaccessible due to 'private' protection level
which I guess is not what you'd want.
miku1958
(Miku1958)
9
if where Self == C work in 5.3, you can move C1, C2 outside if you want, and change the function to:
@callAsFunction static func `init`<R>(use1: Bool) -> R where Self == C, R: C { ... }
I just do that incidentally
Jens
10
I'm not seeing why
let c: C1 = C(use1: true)
would be better than simply
let c = C1()
though.
Can you give a more specific / real world example that shows the benefits of this?
Also, with your generic solution, how would R be resolved for eg:
let c = C(use1: Bool.random())
?
I guess R would have to be C?
miku1958
(Miku1958)
11
there is a exmaple Generic type not equal in extension
because swift does't support generic type not equal in extension, so I think if I could use a subclass to distinguish when I don't need the userInfo
and I am sorry I make a mistake, init(use1: Bool) -> R where Self == C, R: C is never work, because swift can't infer R, even you use some C 
cukr
12
This topic was discussed in the past
You can search for "factory init"
miku1958
(Miku1958)
13
I know, the oldest proposal was created 5 years ago...
miku1958
(Miku1958)
15
wow, I didn't know that before! If Opaque result type can infer diffent type in init(), there is a more easier way to do that:
init(use1: Bool) -> some {
return use1 ? C1() : C2()
}
And I think it also can be used in Protocol extension, that looks cool
Palle
16
You can get close to what you want by adding a global function with the same name as your class and use that instead of an initializer. No need for language changes.
struct Foo {}
func Foo(bar: Int) {}
> Foo() // creates an instance of Foo
> Foo(bar: 42) // calls the function Foo
Or alternatively just add a static instantiation method to your class:
extension Foo {
static func createInstance(foo: Int) -> Foo {}
}
1 Like
dabrahams
(Dave Abrahams)
17
Please see this gist for a technique that allows you to write factory initializers.
5 Likes
miku1958
(Miku1958)
18
It does't work
func Notificatable(name: String) -> Notificatable { // Invalid redeclaration of 'Notificatable(name:)'
...
}
miku1958
(Miku1958)
19
Surprisingly, it works! I am confused as to why the self can be mutated in the protocol.
dabrahams
(Dave Abrahams)
20
The protocol doesn’t know it’s going to be a class, so can’t prevent it.