Create a weak container in Swift that accepts a native Swift protocol - swift

Here's the standard boilerplate weak container in Swift.
struct Weak<T: AnyObject> {
weak var value: T?
init(value: T) {
self.value = value
}
}
It works well unless you want T to be a protocol, e.g.,
protocol ImplementationHiding: class {}
class Implementation: ImplementationHiding {}
let w = Weak(value: Implementation() as ImplementationHiding)
This does not compile, sadly. The only way I've found to get it work is to introduce #objc on the protocol:
#objc protocol ImplementationHiding {}
The only way I've found around this is pretty ugly, since it throws out compile-time safety.
struct Weak<T> {
private let get: () -> T?
init(value: AnyObject, type: T.Type = T.self) {
get = { [weak value] in value as! T? }
}
var value: T? {
return get()
}
}
How can I create Weak with a native Swift protocol as T?

If you are fine with losing some compile time safety, you can change your Weak to accept Any as T and then store value as AnyObject:
struct Weak<T: Any>{
weak var value: AnyObject?
public var ref: T? {
get {
return value as? T
}
}
init(value: T) {
self.value = value as AnyObject
}
}

Related

Swift error: cannot convert from 'A' to 'A?' when using generics

The problem is this:
I want to create a class using generics. That class has a delegate that contains functions that take values of the generic type as arguments. The delegate is declared using a protocol.
For some reason, I cannot get this to compile in Swift 5. This is the code:
protocol SelectItemViewControllerDelegate: AnyObject {
func selectItemViewController<T>(_ vc: SelectItemViewController<T>, selectedItem: T) where T: CustomStringConvertible
}
class SelectItemViewController<T> where T: CustomStringConvertible {
weak var delegate: SelectItemViewControllerDelegate?
}
class MyClass: SelectItemViewControllerDelegate {
private var selectItemCompletionBlock: ((String?) -> Void)?
func selectItemViewController<String>(_ vc: SelectItemViewController<String>, selectedItem: String) {
selectItemCompletionBlock?(selectedItem)
}
}
The error I get is:
error: cannot convert value of type 'String' to expected argument type 'String?'
selectItemCompletionBlock?(selectedItem)
^
as! String
When I do this:
selectItemCompletionBlock?(selectedItem as? String)
I get
error: cannot convert value of type 'String?' to expected argument type 'Swift.String?'
selectItemCompletionBlock?(selectedItem as? String)
^
as! String
What is going on here?
The String here:
func selectItemViewController<String>
declares a generic parameter called String. It does not refer to Swift.String, which selectItemCompletionBlock can accept.
Since SelectItemViewController is generic, you want its delegate to specify for which T it is implementing the delegate. In the case of MyClass, it is String. To do this, you need an associated type, not a generic method:
protocol SelectItemViewControllerDelegate {
associatedtype ItemType: CustomStringConvertible
func selectItemViewController(_ vc: SelectItemViewController<ItemType>, selectedItem: ItemType)
}
Now MyClass can implement selectItemViewController(_:selectedItem:) just for ItemType == String:
class MyClass: SelectItemViewControllerDelegate {
private var selectItemCompletionBlock: ((String?) -> Void)?
func selectItemViewController(_ vc: SelectItemViewController<String>, selectedItem: String) {
selectItemCompletionBlock?(selectedItem)
}
}
The problem now becomes, this is now invalid
weak var delegate: SelectItemViewControllerDelegate?
You can't use a protocol with associated types as the type of a property. To solve this, you need to create a type eraser:
struct AnySelectItemViewControllerDelegate<T: CustomStringConvertible>: SelectItemViewControllerDelegate {
let selectedItemFunc: (SelectItemViewController<T>, T) -> Void
init<DelegateType: SelectItemViewControllerDelegate>(_ del: DelegateType) where DelegateType.ItemType == T {
// keep a weak reference to del here, so as not to cause a retain cycle
selectedItemFunc = { [weak del] x, y in del?.selectItemViewController(x, selectedItem: y) }
}
func selectItemViewController(_ vc: SelectItemViewController<T>, selectedItem: T) {
selectedItemFunc(vc, selectedItem)
}
}
Then, you can declare the delegate as:
var delegate: AnySelectItemViewControllerDelegate<T>?
And to assign the delegate, create an AnySelectItemViewControllerDelegate. Suppose self is the delegate...
selectItemVC.delegate = AnySelectItemViewControllerDelegate(self)
That's a lot of work isn't it? If you only have few delegate methods, I suggest not using a delegate. Just declare some closure properties in SelectItemVC:
var onItemSelected: ((T) -> Void)?

Protocol of reference type to generic constraint of type AnyObject

I have a generic struct declared as follows:
struct WeakReference<T: AnyObject> {
weak var value: T?
init(value: T?) {
self.value = value
}
}
And a protocol:
protocol SomeProtocol: class {
}
But I'm not able to declare a variable of type of WeakReference<SomeProtocol>, the compiler complains that
'WeakReference' requires that SomeProtocol be a class type
Interestingly, in Swift, the class is a typealias of AnyObject.
I actually want to hold an array of WeakReference<SomeProtocol> because the array holds strong references.
Class-only generic constraints in Swift is a similar question but doesn't really solve this problem.
How can we pass the SomeProtocol to WeakReference?
EDIT:
The following scenario compiles fine, but we lose the ability to hold weak reference:
struct Reference<T> {
var value: T?
init(value: T?) {
self.value = value
}
}
var array: [Reference<SomeProtocol>] = []
Thats simple. You are passing SomeProtocol which is a protocol. You need to pass there specific class type.
Eample:
class SomeImplementation: SomeProtocol {
}
var weakSome: WeakReference<SomeImplementation> = ...
Or you can bypass it by marking the protocol with #objc annotation, but I am not a fan of this approach.
#objc protocol SomeProtocol: class {
}
var weakSome: WeakReference<SomeProtocol> = ...
Try checking this answer, it might provide you more context on the issue.
What do you think about this approach?
class WeakReference<T> {
weak var value: AnyObject?
init(value: T?) {
self.value = value as? AnyObject
}
}
protocol SomeProtocol: class {
}
class A: SomeProtocol { }
let araayOfSomeProtocolObjects: [SomeProtocol] = (0...5).map {_ in A() }
let arrayOfWeakReferences: [WeakReference<SomeProtocol>] = araayOfSomeProtocolObjects.map { WeakReference(value: $0) }
for item in arrayOfWeakReferences {
print(item.value is A) // true
}
I think this should solve your problem.
struct WeakReference<T> {
private weak var privateRef: AnyObject?
var ref: T? {
get { return privateRef as? T }
set { privateRef = newValue as AnyObject }
}
init(_ ref: T? = nil) {
self.ref = ref
}
}
// usage
protocol MyProto: class { }
extension UIViewController: MyProto { }
let vc = UIViewController()
var weakRef = WeakReference<MyProto>(vc)
print(weakRef.ref)
You obviously can use WeakReference with non class protocol or non bridged value types.
If you try that, you'll get always nil.
P.S. : Try this code on a real Xcode project because in the Playground doesn't work as expected.

type A requires that type B be a class type swift 4

the following code gives me a compile error
'WeakReference' requires that 'ServiceDelegate' be a class type
protocol ServiceDelegate: AnyObject {
func doIt()
}
class SomeClass() {
// compile error occurs in this line
private var observers = [WeakReference<ServiceDelegate>]()
}
WeakReference code:
final class WeakReference<T: AnyObject> {
private(set) weak var value: T?
init(value: T?) {
self.value = value
}
}
How can I fix this error? Delegate should be declared correctly as per this site.
What I have tried so far:
Changing the delegate protocol conformance from AnyObject to
class does not solve the problem.
Try the above code in a clean playground.
You can't have a WeakReference<ServiceDelegate>. ServiceDelegate itself is not an AnyObject, it just requires that anything that conforms to it be an AnyObject.
You would need to make SomeClass generic and use the generic type as the type for the WeakReference:
class SomeClass<T: ServiceDelegate> {
private var observers = [WeakReference<T>]()
}
If the generic on SomeClass is too constricting and you want to be able to have instances of multiple unrelated classes as observers then I would do it by abandoning the generic parameter on WeakReference:
final class WeakServiceDelegate {
private(set) weak var value: ServiceDelegate?
init(value: ServiceDelegate?) {
self.value = value
}
}
class SomeClass {
private var observers = [WeakServiceDelegate]()
}
Alternatively you could make WeakReference conditionally conform to ServiceDelegate:
extension WeakReference: ServiceDelegate where T: ServiceDelegate {
func doIt() {
value?.doIt()
}
}
And then use an array of ServiceDelegate in SomeClass:
class SomeClass {
private var observers = [ServiceDelegate]()
func addObserver<T: ServiceDelegate>(_ observer: T) {
observers.append(WeakReference(value: observer))
}
}
As you see, ServiceDelegate is a protocol, not a class type.
Even if all types which can conform to ServiceDelegate are class types, ServiceDelegate itself is not a class type. It is the fact of the pure Swift protocols currently.
Try #obc, Objective-C protocols are a bit different:
#objc protocol ServiceDelegate {
func doIt()
}
You may want to exclude Objective-C something and to make some pure Swift classes conform to ServiceDelegate, but I cannot find other ways around.
The problem is that WeakReference<ServiceDelegate> is wrong at line
private var observers = [WeakReference<ServiceDelegate>]()
You have to use a concrete class instead of protocol inside <>
You have two possible solutions:
Create a concrete class and use it:
class ServiceClass: ServiceDelegate {
//...
}
private var observers = [WeakReference<ServiceClass>]()
Or use a protocol. I mean this:
final class WeakReference<T: AnyObject> {
private(set) weak var value: T?
init(value: T?) {
self.value = value
}
}
protocol SomeContainer: AnyObject { }
extension WeakReference: SomeContainer { }
and use this way:
private var observers = [SomeContainer]()
Note
Using this way:
class SomeClass<T: ServiceDelegate> {
private var observers = [WeakReference<T>]()
}
You just move the problem to another part of the code.
I had similar problem and ended up keeping generic WeakReference, but removing type constraint:
struct WeakReference<T> {
private weak var storage: AnyObject?
var value: T? {
get { return storage.map { $0 as! T } }
set {
storage = newValue.map { $0 as AnyObject }
}
}
init(value: T?) {
self.value = value
}
}
This works for classes, Objective-C protocols and Swift protocols:
protocol P: class {}
#objc protocol Q {}
class Z: P, Q {}
var z = Z()
var rz = WeakReference<Z>(value: z)
var rp = WeakReference<P>(value: z)
var rq = WeakReference<Q>(value: z)
assert(rz.value === z)
assert(rp.value === z)
assert(rq.value === z)
z = Z()
assert(rz.value === nil)
assert(rp.value === nil)
assert(rq.value === nil)
Unfortunately it compiles for other things too:
protocol R {}
struct S: R {}
var rr = WeakReference<R>(value: S())
print("rr =", rr.value as Any) // nil
var rs = WeakReference<S>(value: S())
print("rs =", rs.value as Any) // nil
In Swift anything can be casted to AnyObject, but for value types that means boxing - new instance is allocated and immediately lost, so it always produces nil.
This can be used to implement an assertion that casting to AnyObject preserves identity:
struct WeakReference<T> {
private weak var storage: AnyObject?
var value: T? {
get { return storage.map { $0 as! T } }
set {
storage = newValue.map {
let asObject = $0 as AnyObject
assert(asObject === $0 as AnyObject)
return asObject
}
}
}
init(value: T?) {
self.value = value
}
}
Alternative approach would be to use https://github.com/wickwirew/Runtime to validate kind of T.self.
create plain protocol
public protocol AnyWeakValue {
var anyValue: Any? { get }
}
inherit associatedtype protocol from AnyWeakValue
public protocol WeakValue: AnyWeakValue {
associatedtype ValueType
var value: ValueType? { get }
}
extension WeakValue {
public var anyValue: Any? { return value }
}
create class Weak inherit WeakValue
open class Weak<Value: AnyObject>: WeakValue {
public init(value: Value?) { self.value = value }
open private(set) weak var value: Value?
}
using example
private var _delegates: [AnyWeakValue] = []
public var delegates: [SomeProtocol] {
return _delegates.compactMap({$0.anyValue as? SomeProtocol})
}
public func register<Delegate>(_ delegate: Delegate) where Delegate: SomeProtocol {
let weak: Weak<Delegate> = Weak.init(value: delegate)
_delegates.append(weak)
}

Using swift Generic Types with protocols

I've tried to use generic type with protocol:
class Weak<T: AnyObject> {
weak var value: AnyObject?
init (value: AnyObject) {
self.value = value
}
}
protocol SomeProtocol: AnyObject {
func doSomething()
}
func createWeak(object: SomeProtocol) -> Weak<SomeProtocol> {
return Weak<SomeProtocol>(value: object)
}
class SomeClass: SomeProtocol {
func doSomething() {
print("Some 2")
}
}
let temp = SomeClass()
let weakObject = createWeak(object: temp)
weakObject.value?.doSomething()
And got the compiler error:
error: 'SomeProtocol' is not convertible to 'AnyObject'
return Weak(value: object)
But without AnyObject constraint it works fine
class Weak<T> {
var value: T?
init (value: T) {
self.value = value
}
}
protocol Protocol {
func doSomething()
}
class Class: Protocol {
func doSomething() {
print("This is class")
}
}
func createWeak(object: Protocol) -> Weak<Protocol> {
return Weak(value: object)
}
let temp = Class()
let weakObject = createWeak(object: temp)
weakObject.value?.doSomething()
Why I can't use protocols inherited form AnyObject in generic classes?
Swift protocols are incomplete types, which means that you can't use them in places like generic arguments, as the compiler needs to know the whole type details so it can allocate the proper memory layout.
Your createWeak function can still be used if you make it generic:
func createWeak<T: SomeProtocol>(object: T) -> Weak<T> {
return Weak<T>(value: object)
}
The above code works because the compiler will generate at compile time a function mapped to the concrete type you pass.
Even better, you can make the initializer generic, and convert Weak to a struct (value types are preferred Swift over reference ones):
struct Weak<T: AnyObject> {
weak var value: T?
init(_ value: T) {
self.value = value
}
}
which you can use it instead of the free function:
let weakRef = Weak(temp)

Using as a concrete type conforming to protocol AnyObject is not supported

I'm using Swift 2 and using WeakContainer as a way to store a set of weak objects, much like NSHashTable.weakObjectsHashTable()
struct WeakContainer<T: AnyObject> {
weak var value: T?
}
public protocol MyDelegate : AnyObject {
}
Then in my ViewController, I declare
public var delegates = [WeakContainer<MyDelegate>]
But it is error
Using MyDelegate as a concrete type conforming to protocol AnyObject is not supported
I see that the error is that WeakContainer has value member declared as weak, so T is expected to be object. But I also declare MyDelegate as AnyObject, too. How to get around this?
I ran into the same problem when I tried to implement weak containers. As #plivesey points out in a comment above, this seems to be a bug in Swift 2.2 / Xcode 7.3, but it is expected to work.
However, the problem does not occur for some Foundation protocols. For example, this compiles:
let container = WeakContainer<NSCacheDelegate>()
I found out that this works for protocols marked with the #objc attribute. You can use this as a workaround:
Workaround 1
#objc
public protocol MyDelegate : AnyObject { }
let container = WeakContainer<MyDelegate>() // No compiler error
As this can lead to other problems (some types cannot be represented in Objective-C), here is an alternative approach:
Workaround 2
Drop the AnyObject requirement from the container, and cast the value to AnyObject internally.
struct WeakContainer<T> {
private weak var _value:AnyObject?
var value: T? {
get {
return _value as? T
}
set {
_value = newValue as? AnyObject
}
}
}
protocol MyDelegate : AnyObject { }
var container = WeakContainer<MyDelegate>() // No compiler error
Caveat: Setting a value that conforms to T, but is not an AnyObject, fails.
I had the same idea to create weak container with generics.
As result I created wrapper for NSHashTable and did some workaround for your compiler error.
class WeakSet<ObjectType>: SequenceType {
var count: Int {
return weakStorage.count
}
private let weakStorage = NSHashTable.weakObjectsHashTable()
func addObject(object: ObjectType) {
guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
weakStorage.addObject(object as? AnyObject)
}
func removeObject(object: ObjectType) {
guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
weakStorage.removeObject(object as? AnyObject)
}
func removeAllObjects() {
weakStorage.removeAllObjects()
}
func containsObject(object: ObjectType) -> Bool {
guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
return weakStorage.containsObject(object as? AnyObject)
}
func generate() -> AnyGenerator<ObjectType> {
let enumerator = weakStorage.objectEnumerator()
return anyGenerator {
return enumerator.nextObject() as! ObjectType?
}
}
}
Usage:
protocol MyDelegate : AnyObject {
func doWork()
}
class MyClass: AnyObject, MyDelegate {
fun doWork() {
// Do delegated work.
}
}
var delegates = WeakSet<MyDelegate>()
delegates.addObject(MyClass())
for delegate in delegates {
delegate.doWork()
}
It's not the best solution, because WeakSet can be initialized with any type, and if this type doesn't conform to AnyObject protocol then app will crash. But I don't see any better solution right now.
Why are you trying to use generics? I would suggest doing the following:
import Foundation
import UIKit
protocol MyDelegate : AnyObject {
}
class WeakContainer : AnyObject {
weak var value: MyDelegate?
}
class ViewController: UIViewController {
var delegates = [WeakContainer]()
}
There is also NSValue's nonretainedObject
If your Protocol can be marked as #obj then you can use code below
protocol Observerable {
associatedtype P : AnyObject
var delegates: NSHashTable<P> { get }
}
#objc protocol MyProtocol {
func someFunc()
}
class SomeClass : Observerable {
var delegates = NSHashTable<MyProtocol>.weakObjects()
}
Your issue is that WeakContainer requires its generic type T to be a subtype of AnyObject - a protocol declaration is not a subtype of AnyObject. You have four options:
Instead of declaring WeakContainer<MyDelegate> replace it with something that actually implements MyDelegate. The Swift-y approach for this is to use the AnyX pattern: struct AnyMyDelegate : MyDelegate { ... }
Define MyDelegate to be 'class bound' as protocol MyDelegate : class { ... }
Annotate MyDelegate with #obj which, essentially, makes it 'class bound'
Reformulate WeakContainer to not require its generic type to inherit from AnyObject. You'll be hard pressed to make this work because you need a property declared as weak var and there are limitation as to what types are accepted by weak var - which are AnyObject essentially.
Here is my implementation of WeakSet in pure Swift (without NSHashTable).
internal struct WeakBox<T: AnyObject> {
internal private(set) weak var value: T?
private var pointer: UnsafePointer<Void>
internal init(_ value: T) {
self.value = value
self.pointer = unsafeAddressOf(value)
}
}
extension WeakBox: Hashable {
var hashValue: Int {
return self.pointer.hashValue
}
}
extension WeakBox: Equatable {}
func ==<T>(lhs: WeakBox<T>, rhs: WeakBox<T>) -> Bool {
return lhs.pointer == rhs.pointer
}
public struct WeakSet<Element>: SequenceType {
private var boxes = Set<WeakBox<AnyObject>>()
public mutating func insert(member: Element) {
guard let object = member as? AnyObject else {
fatalError("WeakSet's member (\(member)) must conform to AnyObject protocol.")
}
self.boxes.insert(WeakBox(object))
}
public mutating func remove(member: Element) {
guard let object = member as? AnyObject else {
fatalError("WeakSet's member (\(member)) must conform to AnyObject protocol.")
}
self.boxes.remove(WeakBox(object))
}
public mutating func removeAll() {
self.boxes.removeAll()
}
public func contains(member: Element) -> Bool {
guard let object = member as? AnyObject else {
fatalError("WeakSet's member (\(member)) must conform to AnyObject protocol.")
}
return self.boxes.contains(WeakBox(object))
}
public func generate() -> AnyGenerator<Element> {
var generator = self.boxes.generate()
return AnyGenerator {
while(true) {
guard let box = generator.next() else {
return nil
}
guard let element = box.value else {
continue
}
return element as? Element
}
}
}
}