Find delegate in a swift Array of delegates
Update for Swift 4.2:
Assuming that the delegates are actually instances of a class, you could require that in the protocol by "inheriting" from "class":
protocol LocationManagerDelegate: class {
// ...
}
and then use the firstIndex(where:)
method, using the "identity operator===
:
class LocationManager: NSObject {
private var _delegates = [LocationManagerDelegate]()
func removeDelegate(delegate:LocationManagerDelegate) {
if let index = _delegates.firstIndex(where: { $0 === delegate }) {
_delegates.remove(at: index)
}
}
}
Old answer (Swift 1):
There are two slightly different contains()
functions:
func contains<S : SequenceType where S.Generator.Element : Equatable>(seq: S, x: S.Generator.Element) -> Bool
func contains<S : SequenceType, L : BooleanType>(seq: S, predicate: (S.Generator.Element) -> L) -> Bool
You are using the first one, which requires that the sequence elements conform to
the Equatable
protocol, i.e. they can be compared with ==
.
Assuming that the delegates are actually instances of a class, you could require
that in the protocol by "inheriting" from "class":
protocol LocationManagerDelegate : class {
// ...
}
and then use the second, predicate-based version of contains()
with the
identity operator ===
:
func removeDelegate(delegate:LocationManagerDelegate) {
if contains(_delegates, { $0 === delegate }) {
// Remove delegate
}
}
To remove the object from the array you'll have to get its index, so you might use
the findIdenticalObject()
function from https://stackoverflow.com/a/25543084/1187415:
func findIdenticalObject<T : AnyObject>(array: [T], value: T) -> Int? {
for (index, elem) in enumerate(array) {
if elem === value {
return index
}
}
return nil
}
and then find and remove from the array with
func removeDelegate(delegate:LocationManagerDelegate) {
if let index = findIdenticalObject(_delegates, delegate) {
_delegates.removeAtIndex(index)
}
}
How to avoid a retain cycle when using an array of delegates in Swift
The problem is that weakDelegates
is a strong reference and its reference to its elements of type WeakDelegateContainer is a strong reference.
Your situation is why the class NSHashTable exists. Initialize using weakObjects()
. This will give you a set of ARC-weak references, each of which will be nilified and removed when the referenced object goes out of existence (with no need for any extra bookkeeping on your part, and no need for your WeakDelegateContainer type).
Your set will have to be typed as holding AnyObject, but you can easily mediate to ensure that you are supplying and retrieving SomeDelegate-conformant objects:
let list = NSHashTable<AnyObject>.weakObjects()
func addToList(_ obj:SomeDelegate) {
list.add(obj)
}
func retrieveFromList(_ obj:SomeDelegate) -> SomeDelegate? {
if let result = list.member(obj) as? SomeDelegate {
return result
}
return nil
}
func retrieveAllFromList() -> [SomeDelegate] {
return list.allObjects as! [SomeDelegate]
}
The function retrieveAllFromList()
lists only objects that still exist. Any object that has gone out existence has been changed to nil
in the NSHashTable and is not included in allObjects
. That is what I mean by "no extra bookkeeping"; the NSHashTable has already done the bookkeeping.
Here is code that tests it:
func test() {
let c = SomeClass() // adopter of SomeDelegate
self.addToList(c)
if let cc = self.retrieveFromList(c) {
cc.someFunction()
}
print(self.retrieveAllFromList()) // one SomeClass object
delay(1) {
print(self.retrieveAllFromList()) // empty
}
}
Alternatively, you can use NSPointerArray. Its elements are pointer-to-void, which can be a little verbose to use in Swift, but you only have to write your accessor functions once (credit to https://stackoverflow.com/a/33310021/341994):
let parr = NSPointerArray.weakObjects()
func addToArray(_ obj:SomeDelegate) {
let ptr = Unmanaged<AnyObject>.passUnretained(obj).toOpaque()
self.parr.addPointer(ptr)
}
func fetchFromArray(at ix:Int) -> SomeDelegate? {
if let ptr = self.parr.pointer(at:ix) {
let obj = Unmanaged<AnyObject>.fromOpaque(ptr).takeUnretainedValue()
if let del = obj as? SomeDelegate {
return del
}
}
return nil
}
Here is code to test it:
let c = SomeClass()
self.addToArray(c)
for ix in 0..<self.parr.count {
if let del = self.fetchFromArray(at:ix) {
del.someFunction() // called
}
}
delay(1) {
print(self.parr.count) // 1
for ix in 0..<self.parr.count {
if let del = self.fetchFromArray(at:ix) {
del.someFunction() // not called
}
}
}
Interestingly, after our SomeClass goes out of existence, our array's count
remains at 1 — but cycling through it to call someFunction
, there is no call to someFunction
. That is because the SomeClass pointer in the array has been replaced by nil
. Unlike NSHashTable, the array is not automatically purged of its nil
elements. They do no harm, because our accessor code has guarded against error, but if you would like to compact the array, here's a trick for doing it (https://stackoverflow.com/a/40274426/341994):
self.parr.addPointer(nil)
self.parr.compact()
Swift: using delegates to send data to another view controller
You don't need delegates in this case. You are sending data forwards, so just do it like this:
class ViewController: UIViewController {
private var model: Users = ViewController.createAccount()
var exampleDelegate: ExampleDelegate?
@IBAction func showUsers(_ sender: UIButton) {
let showUsersVC = storyboard?.instantiateViewController(identifier: "ThirdViewController") as! ThirdViewController
var userList: Array<User> = model.listOfUsers
showUsersVC.usersList = userList /// pass the data!
present(showUsersVC, animated: true)
}
}
Also in Swift you should lowercase objects like userList
, as well as functions like showUsers
.
How to remove specific object from array?
- Is there a less complicated way to do this?
You could omit self
when accessing the delegates
member, and bake the resulting index of the index(where:)
call into a call to the map
method of Optional
:
func removeDelegate(_ delegate: MyDelegate) {
delegates.index(where: { $0 == delegate})
.map { delegates.remove(at: $0) }
}
In case no such delegate
object is found, the expression above simply results nil
(non-captured result).
This conditional, "
{ $0 == delegate }
", is giving causing the error,
"Cannot convert value of type '(OptionalNilComparisonType) -> Bool
' to
expected argument type '() -> Bool
'". How can I fix this? I've tried
adding?
and!
but still not fully understanding Swift's optional
concept.
This is yet another example of a kind of obscure error message in Swift. The core error is that MyDelegate
does not have an ==
operator defined (does not conform to Equatable
).
After your edit you showed, however, that MyDelegate
is a protocol, so if you let this conform to Equatable
, you wont be able to (as it will be contain a Self
type requirement) use MyDelegate
as a concrete type (only as e.g. type constraint on a generic).
If your concrete delegate objects are reference ones (class
), and you want to test object equality in the sense of testing if both refer to the same object (object reference), you could make use of the the ObjectIdentifier
available to class instances. Constraining MyDelegate
(after your edit you showed it to be a protocol) to only classes,
protocol MyDelegate: class { /* ... */ }
and testing the equality of the ObjectIdentifier
in the index(where:)
call above:
func removeDelegate(_ delegate: MyDelegate) {
delegates.index(where: { ObjectIdentifier($0) == ObjectIdentifier(delegate) })
.map { delegates.remove(at: $0) }
}
Looking at the source code for ObjectIdentifier
, we see that this will compare the underlying raw ptr values of two delegate
instances; from swift/stdlib/public/core/ObjectIdentifier.swift:
public static func == (x: ObjectIdentifier, y: ObjectIdentifier) -> Bool {
return Bool(Builtin.cmp_eq_RawPointer(x._value, y._value))
}
As mentioned by @MartinR in the comments to the question above, rather than going via ObjectIdentifier
, you could also use the ===
identity operator for class instances directly.
func removeDelegate(_ delegate: MyDelegate) {
delegates.index(where: { $0 === delegate })
.map { delegates.remove(at: $0) }
}
For completeness, we may verify that ===
make use of the same Builtin
method as the ==
operator for ObjectIdentifier
, by having a look at swift/stdlib/public/core/Equatable.swift:
public func === (lhs: AnyObject?, rhs: AnyObject?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return Bool(Builtin.cmp_eq_RawPointer(
Builtin.bridgeToRawPointer(Builtin.castToUnknownObject(l)),
Builtin.bridgeToRawPointer(Builtin.castToUnknownObject(r))
))
case (nil, nil):
return true
default:
return false
}
}
Passing an array using protocols/delegates
You seem to have confusion around the role of a delegate and where it should be implemented. You don't need to use a delegate to pass data from SendingVC
to RecevingVC
, you can simply set the property on the destination view controller in prepareForSegue
;
class SendingVC: UIViewController {
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destinationViewController as? ReceivingVC {
destVC.incomingCarDetails = self.carDetails
}
}
}
If you do want to use a delegate, then you will set the SendingVC
instance as the delegate of your ReceivingVC
in prepareForSegue
and change your protocol so that the delegate method returns data, rather than accepts data:
protocol SenderVCDelegate {
func passArrayData() -> [String]
}
Then, you can implement the delegate method and set the delegate in prepareForSegue
class SendingVC: UIViewController, SenderVCDelegate {
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destinationViewController as? ReceivingVC {
destVC.senderDelegate = self
}
}
func passArrayData() -> [String] {
return self.carDetails
}
}
In ReceivingVC
you can call the delegate method in viewWillAppear
class ReceivingVC: UIViewController {
var incomingCarDetails = [String]()
var senderDelegate: SenderVCDelegate?
override func viewWillAppear(animated: bool) {
super.viewWillAppear(animated)
if let incomingDetails = self.senderDelegate?.passArrayData() {
self.incomingCarDetails = incomingDetails
}
}
}
As you can see this is a whole lot more work and no benefit. A delegation pattern is typically used where you want to send data back and where the function call will happen at an unpredictable time, such as in response to a network operation completing or a user interaction.
Not able to get array using delegate
Here is how you can proceed:
Conform
VC1
toImageAssetsProtocol
and implement itssendImageAssets(myData:)
method.class VC1: UIViewController, ImageAssetsProtocol {
func openVC2() {
let vc2 = self.storyboard?.instantiateViewController(withIdentifier: "VC3") as! VC3
vc2.delegate = self
}
func sendImageAssets(myData: [MyImageAsset]) {
print(myData)
}
}In
VC2
create adelegate
of typeImageAssetsProtocol?
and setvc2.delegate = self
while presentingVC2
fromVC1
class VC2: UIViewController {
var delegate: ImageAssetsProtocol?
func openVC3() {
let vc3 = self.storyboard?.instantiateViewController(withIdentifier: "VC3") as! VC3
vc3.delegate = self.delegate
}
}In
VC3
create adelegate
of typeImageAssetsProtocol?
and setvc3.delegate = self
while presentingVC3
fromVC2
. Call the delegate methodsendImageAssets(myData:)
inviewWillDisappear(_:)
.class VC3: UIViewController {
weak var delegate: ImageAssetsProtocol?
override func viewWillDisappear(_ animated: Bool) {
self.delegate?.sendImageAssets(myData: selectedAssets)
}
}
Swift : Create a multi-function multicast delegate
You need to change the signature of invokeDelegates
to take a closure of type (MyProtocol) -> ()
, and then you need to pass each delegate to the closure.
protocol MyProtocol {
func method1()
func method2()
func method3()
}
class TestClass {
var delegates = [MyProtocol]()
func invokeDelegates(delegateMethod: (MyProtocol) -> ()) {
for delegate in delegates {
delegateMethod(delegate)
}
}
}
The closure should just invoke the appropriate delegate method on its argument. Swift can infer the argument and return types of the closure, and you can use the shorthand $0
to refer to the argument, so the closure can be quite short:
let tester = TestClass()
tester.invokeDelegates(delegateMethod: { $0.method1() })
On the other hand, you could just use Collection.forEach
directly on the delegates
array (if it's accessible) and skip the invokeDelegates
method:
tester.delegates.forEach { $0.method1() }
Related Topics
Why Swift Use a Struct as Dictionary Key Instead of a String Here
Extending a Delegate from a Base Class
Ios-Charts How to Put Uiimage Beside a Point
Get "No Keychain Available" Error When Try to Access Keychain from App Extension
Uicollectionviewcontroller Error in Swift 3.0: Must Be Initialized with a Non-Nil Layout Parameter
Array Element Checking in Swift
Module Compiled with Swift 5.1.2 Cannot Be Imported by the Swift 5.2.4 Compiler
Nsclassfromstring Returning Nil for Nested Class
Swift Difference Between Var Arr:[String] = [] and Var Arr = [String]()
Animated View Floats into Place Weirdly Swiftui
Uitraitcollection Clarification
How to Update a Swiftui View State from Outside (Uiviewcontroller for Example)
Issue with Encoding "&" in Urls
Swift and Nsuserdefaults - Exc_Bad_Instruction When User Defaults Empty
How Should Secure Transport Tls Be Used with Bsd Sockets in Swift