Property not initialized at super.init call using MVVM with convenience init - ios

I have a View Model that subclasses NSObject as it is my UICollectionViewDataSource
I'd like to pass a service into this via dependancy injection.
I'm getting an error though
Property 'self.chatService' not initialised at super.init call
class ChatQuestionsViewModel: NSObject {
fileprivate var chatService: ChatService
convenience init(chatService: ChatService = ChatService()) {
self.init()
self.chatService = chatService
}
private override init() {
super.init()
}
}
And it appears to be focused on the super.init() line.
How can I initialise this class? I am unsure what I am doing wrong.

You cannot have a convenience initializer in this case. The rules of initializers state that you have to guarantee that all stored properties get initialized.
Convenience initializers are just that: for convenience only. This means it must not be necessary to use them to create an instance of the object. However if code uses your only non-convenience init, there's no initialization of chatService. (Never mind that your non-convenience init is private; that doesn't help).
Fixed result:
class ChatQuestionsViewModel: NSObject {
fileprivate var chatService: ChatService
init(chatService: ChatService = ChatService()) {
self.chatService = chatService
super.init()
}
}

Related

Confusion between self and super in a subclass

Let's say you have a class named Vehicle:
class Vehicle {
var name: String
var size: Int
init(name: String, size: Int) {
self.name = name
self.size = size
}
convenience init(name: String) {
self.init(name: name, size: 100)
}
func operate() {
print("operate")
}
}
If I have a subclass named Car and create a convenience initializer:
class Car: Vehicle {
convenience init(size: Int) {
self.init(name: "Vehicle", size: size)
}
}
the super class' initializer is made available using self.
But, if I want to override the super class' method, it's made available by using super:
class Car: Vehicle {
// redacted
override func operate() {
super.operate()
print("operate a car")
}
}
If the subclass wants to use the inherited method, it's made available by using self:
class Car: Vehicle {
// redacted
func drive() {
self.operate()
print("drive")
}
}
I understand that class methods are instance methods, meaning self refers to the instance of the class. I'm guessing when the subclass inherits the super class' method, it becomes its own instance method. Why does it not apply to overriding? I'm getting confused between the usage of self and super.
Why does it not apply to overriding?
Well, in this code:
class Car: Vehicle {
// redacted
override func operate() {
super.operate()
print("operate a car")
}
}
self.operate would refer to the overridden method that is in Car. In other words, if you called self.operate(), it would create infinite recursion. On the other hand, super.operate refers to:
func operate() {
print("operate")
}
Think of it like this: when you override a method, you kind of "lose" the version of the method that you inherit, and you can only use super to access the original in the super class.
Also, no one stops you from doing:
class Car: Vehicle {
func drive() {
super.operate()
print("drive")
}
}
It's just that in this case, super.operate() and this.operate() do exactly the same thing, since operate is not overridden in the subclass.
On the other hand, it is illegal to call super.init in a convenience initialiser. You must delegate to a self.init in a convenience initialiser. And in this case, the super class initialisers are inherited because you don't have any designated initialisers (in general, they aren't inherited!). Read more about initialisers here.

passing data to already implemented delegate methods inside extensions inside swift

I am trying to pass data to a delegate method implemented inside an extension but i am unable to do it since extensions cannot have stored properties. How to get it done?
You can make the stored property a requirement of the delegate protocol.
protocol MyProtocol {
var aProperty: String { get set }
func aProtocolMethod()
}
For the corresponding extension of MyProtocol, the property can be accessed directly.
extension MyProtocol {
func aProtocolMethod() {
print("property:" + aProperty)
}
}
In the class which conforms to MyProtocol, it should implement the variable to store data.
class MyClass: MyProtocol {
var aProperty: String
init() {
self.aProperty = "some value"
}
}
let myClass = MyClass()
myClass.aProtocolMethod()

Convenience initialization of UINavigationController subclass makes subclass constant init twice

I have subclasses of UINavigationController and UITableViewController.
To initialize subclasses I decided to use some convenience init methods that call some designated initializer of the superclass. Also, each subclass has some let constant:
let someValue: SomeClass = SomeClass()
Each class is successfully initialized by calling its newly created convenience init method.
The problem is that the let constant is initialized TWICE in UINavigationController subclass.
import UIKit
import PlaygroundSupport
final class Navigation: UINavigationController {
convenience init(anyObject: Any) {
self.init(rootViewController: UIViewController())
}
let service = Constant("Constant Initialization -> Navigation")
}
final class Table: UITableViewController {
convenience init(anyObject: Any) {
self.init(style: .plain)
}
let service = Constant("Constant Initialization -> Table")
}
class Constant: NSObject {
init(_ string: String) {
super.init()
debugPrint(string)
}
}
Navigation(anyObject: NSNull())
Table(anyObject: NSNull())
Are we allowed to use convenience init like above? Why?
Why is the convenience init behavior is different in these two cases?
Checked with: Version 8.2 beta (8C30a), Version 8.2 (8C38), Version 8.2.1 (8C1002)
P.S. Playground log of the code above:
"Constant Initialization -> Navigation"
"Constant Initialization -> Navigation"
"Constant Initialization -> Table"
So this seems to be weirdly "expected behavior" with Swift. The reason it's happening is due to the constant initialized property service. Namely, this post summarizes the issue your seeing pretty well: blog post
Essentially, the Obj-C underlying super classes are leaking memory and your service property is initialized twice because of this pattern of Obj-C initialization:
- (id)init {
self = [super init];
if (self) {
self = [[self.class alloc] initWithNibName:nil bundle:nil];
}
return self;
}
A simple solution to avoid this is the following pattern:
import UIKit
final class NavigationController: UINavigationController {
var service: ObjectTest?
convenience init() {
self.init(rootViewController: UIViewController())
self.service = ObjectTest("init nav")
}
}
class ObjectTest: NSObject{
init(_ string: String) {
super.init()
print(string)
}
}
All that's changing from your implementation is initializing the service only after your class itself is initialized.
Another solution, though, is to use the designated initializer for the superclass your initializing. Meaning that you don't use a convenience initializer which would employ the above Obj-C initialization pattern.

Swift protocol & weak references with class

If I have a protocol:
protocol SomeProtocol {
func doSomething()
}
and in a helper class, I have a reference to a protocol variable:
class someClass {
var delegate: SomeProtocol?
}
because SomeProtocol isn't marked with : class, it's assumed that delegate can be anything and in the case of value types (structs and enums) there's no need for weak var because value types can't create strong references. In fact, the compiler doesn't allow weak var on anything but class types.
However, nothing stops you from setting a class as the delegate and if the protocol isn't marked with : class (as SomeProtocol is),weak var` can't be used and that creates a retain cycle.
class MyClass: NSObject, SomeProtocol {
func doSomething() { }
}
struct MyStruct: SomeProtocol {
func doSomething() { }
}
let someClass = SomeClass()
let myStruct = MyStruct()
someClass.delegate = myStruct
// After myStruct gets assigned to the delegate, do the delegate and the struct refer to the same instance or does the struct get copied?D
let myClass = MyClass()
someClass.delegate = myClass // can't use weak var so myClass is retained
Given the above example, in the case of using delegates and datasource, shouldn't : class always be used? Basically any protocol that is used to maintain a reference should always be restricted to class objects right?
Right. If you a are trying to break retain cycle with weak reference, you have to use classes because weak modifier works only with reference types (classes).
: class is the preferred approach most of the time. As an alternative answer, though, you can set delegate = nil in the deinit method of the parent object.
For example, say the parent object is an NSOperation subclass that has a SomeClass property, and this property has a delegate that implements SomeProtocol:
protocol SomeProtocol {
func doSomething()
}
class SomeClass {
var delegate: SomeProtocol?
}
class CustomOperation: NSOperation {
let foo: SomeClass
}
We'll make a class that implements the protocol too:
class SomeProtocolImplementation: SomeProtocol {
func doSomething() {
print("Hi!")
}
}
Now we can assign foo in init():
class CustomOperation: NSOperation {
let foo: SomeClass
override init() {
foo = SomeClass()
foo.delegate = SomeProtocolImplementation()
super.init()
}
}
This creates a retain cycle. However, consider this deinit method:
deinit {
foo.delegate = nil
}
Now, whenever CustomOperation will be deallocated, foo.delegate will be set to nil, breaking the retain cycle. Then when the autorelease pool drains at the end of the run loop, both SomeClass and SomeProtocolImplementation will be deallocated.

Swift Class Pointer as? Class Protocol?

I have a class, lets call it SomeClass. Instances of SomeClass have an optional pointer to SomeOtherClass. In this way, instances of SomeClass can be instantiated, given a pointer to SomeOtherClass (or a subclass of SomeOtherClass), and then this pointer can be used to dynamically create instances of this SomeOtherClass belonging to SomeClass. Eg;
class SomeClass {
var classPointer: SomeOtherClass.Type?
}
class SomeOtherClass {
}
So far so good. Now, I have a protocol - lets call it SomeProtocol - that I want SomeOtherClass to conform to. This protocol has class functions in it:
protocol SomeProtocol {
static func someClassFunction()
}
extension SomeOtherClass : SomeProtocol {
class func someClassFunction() {
print("I am a class function being executed on SomeOtherClass")
}
}
As expected, I can then call this protocol class function on SomeOtherClass like so:
SomeOtherClass.someClassFunction() // Prints "I am a class function being executed on SomeOtherClass"
Here is the troublesome part. I want to dynamically determine if an instance of SomeClass' classPointer conforms to SomeProtocol, and if so execute the class function on it. So, I try to cast the pointer using as?:
// Create an instance of SomeClass and set it's classPointer to the SomeOtherClass class
let someInstance = SomeClass()
someInstance.classPointer = SomeOtherClass.self
// Check if the instance's classPointer class conforms to the SomeProtocol protocol
if let conformingClass = someInstance.classPointer as? SomeProtocol {
// If so, execute the class function in SomeProtocol on the instance's classPointer
conformingClass.someClassFunction() // Build fails "Static member someClassFunction cannot be used on instance of type SomeProtocol"
}
And the build fails with the error "Static member of someClassFunction cannot be used on instance of type SomeProtocol".
Is there a way to accomplish what I'm attempting? Currently if this doesn't work I can only think of these alternatives (none are preferable and they're all rather hacky):
Switch to objective c.
Switch the protocol to use instance functions instead, then instantiate a temporary instance of SomeClass' classPointer and message it with any necessary functions, then release the instance.
For completeness, here is all of the code together that can be pasted into a Playground (it won't build due to the error I mentioned though):
class SomeClass {
var classPointer: SomeOtherClass.Type?
}
class SomeOtherClass {
}
protocol SomeProtocol {
static func someClassFunction()
}
extension SomeOtherClass : SomeProtocol {
class func someClassFunction() {
print("I am a class function being executed on SomeOtherClass")
}
}
// Create an instance of SomeClass and set it's classPointer to the SomeOtherClass class
let someInstance = SomeClass()
someInstance.classPointer = SomeOtherClass.self
// Check if the instance's classPointer class conforms to the SomeProtocol protocol
if let conformingClass = someInstance.classPointer as? SomeProtocol {
// If so, execute the class function in SomeProtocol on the instance's classPointer
conformingClass.someClassFunction() // Build fails "Static member someClassFunction cannot be used on instance of type SomeProtocol"
}
Thanks for any help you can provide,
- Adam
Ahah! As usual, as soon as I make the SO post, I figure out the answer.
For those wondering, you must cast the classPointer as the protocol's Type, not as the protocol itself. The line:
if let conformingClass = someInstance.classPointer as? SomeProtocol {
Needs to be changed to:
if let conformingClass = someInstance.classPointer as? SomeProtocol.Type {
And you'll then be able to message conformingClass with the class functions declared in SomeProtocol. The complete working code is:
class SomeClass {
var classPointer: SomeOtherClass.Type?
}
class SomeOtherClass {
}
protocol SomeProtocol {
static func someClassFunction()
}
extension SomeOtherClass : SomeProtocol {
class func someClassFunction() {
print("I am a class function being executed on SomeOtherClass")
}
}
// Create an instance of SomeClass and set it's classPointer to the SomeOtherClass class
let someInstance = SomeClass()
someInstance.classPointer = SomeOtherClass.self
// Check if the instance's classPointer class conforms to the SomeProtocol protocol
if let conformingClass = someInstance.classPointer as? SomeProtocol.Type {
// If so, execute the class function in SomeProtocol on the instance's classPointer
conformingClass.someClassFunction()
}
And it works :).

Resources