0

If the caller passes in an object of type MyDelegate and it is in the array, I want to remove it from the array.

protocol MyDelegate {
}

private var delegates = [MyDelegate]()
...
...
func removeDelegate(_ delegate: MyDelegate) {
    if let index = self.delegates.index(where: { $0 == delegate }) {
        log.trace("Removing delegate \(delegate)");
        self.delegates.remove(at: index)
    }
}
  1. Is there a less complicated way to do this?

  2. This conditional, "{ $0 == delegate }", is 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.

I am using Xcode 8.2.1 and Swift 3(?).

noctonura
  • 12,763
  • 10
  • 52
  • 85
  • Are the delegates supposed to be instances of *classes?* Then you can define a "class protocol" `protocol MyDelegate: class { }` and use the identity operator for comparison: `index(where: { $0 === delegate })`. – Martin R Apr 08 '17 at 15:11

1 Answers1

2
  1. 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 Object​Identifier 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
  }
}
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • Is there no Swift concept of default object equality like in java and c#? – noctonura Apr 08 '17 at 15:10
  • @RichAmberale generally, no. There is the [`Object​Identifier`](https://developer.apple.com/reference/swift/objectidentifier) available to class instances, so if you constraint `MyDelegate` (after your edit you showed it to be a protocol) to only classes, you could test the equality of the `ObjectIdentifier`:s of `$0` and `delegate` above. This is in essence testing if the both refer (object reference) to the same object, and not if they are equal in the sense of some pre-defined `==` operator. – dfrib Apr 08 '17 at 15:16
  • Comparing the ObjectIdentifier is the same as checking identity with `===`: http://stackoverflow.com/questions/39587027/difference-between-using-objectidentifier-and-operator. – Martin R Apr 08 '17 at 15:23
  • 1
    @MartinR thanks for the link! I saw the same comment section in the source of `ObjectIdentifier`, but was in the progress of looking around for the actual implementation of `===` (to verify that it most likely calls the same `Builtin` method). – dfrib Apr 08 '17 at 15:32
  • Thanks! In this new enlightened state of understanding I changed MyDelegate to extend NSObjectProtocol and updated the func, which so far at least compiles: `func removeDelegate(_ delegate: MyDelegate) { delegates.index(where: { $0.isEqual(delegate) }) .map { delegates.remove(at: $0) log.trace("Removed delegate \(delegate)"); } }` – noctonura Apr 08 '17 at 15:33
  • @MartinR found it in [`Equatable`](https://github.com/apple/swift/blob/master/stdlib/public/core/Equatable.swift#L235)! Possibly worth adding to your linked answer. – dfrib Apr 08 '17 at 15:35
  • 1
    @RichAmberale happy to help. It might be a personal preference, but I usually avoid, myself, resorting to Obj-C features when using Swift, so I would probably prefer `===` / `ObjectIdentifier` over `NSObjectProtocol`:s `isEqual(...)` method myself. – dfrib Apr 08 '17 at 15:37
  • @MartinR btw, any thoughts on why the concrete `l` and `r` `AnyObject` instances in the `===` impl. above would be processed through `Builtin.castToUnknownObject(...)` prior to being passed to `Buildin.bridgeToRawPointer(...)`, when this is not the case for the initializer (by concrete `AnyObject`) of [`ObjectIdentifier`](https://github.com/apple/swift/blob/master/stdlib/public/core/ObjectIdentifier.swift#L61)? Does this look like an inconsistency, or am I missing something obvious? – dfrib Apr 08 '17 at 15:48
  • 1
    Thanks for the link, I have updated the other answer. – I have no idea. From https://github.com/apple/swift/blob/master/include/swift/AST/Builtins.def it seems unnecessary: "CastToUnknownObject has type (T) -> Builtin.UnknownObject." and "BridgeToRawPointer has type (T) -> Builtin.RawPointer" – But I have no deeper knowledge of "built-in SIL operations" :) – Martin R Apr 08 '17 at 17:33