Swift protocol & weak references with class - ios

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.

Related

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

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()
}
}

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()

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 :).

Set delegate to class type not to instance in Swift

In Objective-C it was possible to set a class as a delegate (not an instance of a class but a pure class). Is it possible in Swift?
Yes it is
Declare a delegate variable to be a class type, not instance type.
I also make it optional, but we could also make it non-optional and pass it in the init method.
var delegate : Int.Type?
Code Example
class A {
static func sayHello() {
println("Hello")
}
}
class B {
var num = 10
var delegate : A.Type?
func hi() {
delegate?.sayHello()
}
}
var b = B()
b.delegate = A.self
b.hi()

Compiler error when assigning the Delegate for a Protocol in Swift iOS

I have a problem assigning the delegate for an object that is an instance of a class that defines a protocol in Swift as follows:
I simplified the code to the bare bones to exemplify the issue:
This is the class with the protocol
protocol TheProtocol {
func notifyDelegate()
}
class ClassWithProtocol: NSObject {
var delegate: TheProtocol?
fire() {
delegate?.notifyDelegate()
}
}
This is the class the conforms to the Protocol
class ClassConformingToProtocol: NSObject, TheProtocol {
var object: ClassWithProtocol?
func notifyDelegate() {
println("OK")
}
init() {
object = ClassWithProtocol()
object?.delegate = self // Compiler error - Cannot assign to the result of this expression
object?.fire()
}
}
I have tried all sort of alternatives to assign the delegate without success. Any idea what I am missing?
The Known Issues section of the Release Notes says:
You cannot conditionally assign to a property of an optional object.
(16922562)
For example, this is not supported:
let window: NSWindow? = NSApplication.sharedApplication.mainWindow
window?.title = "Currently experiencing problems"
So you should do something like if let realObject = object { ... }

Resources