1

I found this obscure problem when creating generic function inside Array extension. Let's say I have User class that I'll use to create array:

class User : NSObject {
    let name : String
    let surname : String

    init(name : String, surname : String) {
        self.name = name
        self.surname = surname
    }

    override var description : String {
        return self.name + " " + self.surname
    }
}

Note that I need it to be subclass of NSObject. Now let's create two users and add them to array of users.

var user1 = User(name: "Jimmy", surname: "Page")
var user2 = User(name: "David", surname: "Guilmour")

var users = Array<User>()
users.append(user1)
users.append(user2)

Now I need an Array extension with function specific to Array, or any other subclass of User, that will add some test users to any array of users. Reason I write it inside extension and not my own type is because i want to check it's type using if let something = something as [User]. If I build my own type I would not be able to do that.

extension Array {
    mutating func addTestUsers<T : User>() {
        var testUser1 = User(name: "King", surname: "Brown")
        var testUser2 = User(name: "Carlos", surname: "Santana")
        self.append(testUser1)
        self.append(testUser2)
    }
}

The problem comes when using append function or any other function that can somehow mutate the initial Array.

Error : Cannot invoke append with argument list of type (User)

I know it's because extension is never specified as extension of Array of User, but as I can see there is no way I can do this.

Said Sikira
  • 4,482
  • 29
  • 42
  • 2
    You *cannot* define an Array extension method which applies to arrays of User only, see for example http://stackoverflow.com/questions/24938948/array-extension-to-remove-object-by-value or http://stackoverflow.com/questions/27350941/is-it-possible-to-make-an-array-extension-in-swift-that-is-restricted-to-one-cla. – Martin R May 26 '15 at 09:24

1 Answers1

0

As suggested in @Martin R's comment, if you have a look at Array extension to remove object by value, the highest voted answer suggests writing a function that takes an array as an argument, in your case you could do something like:

func addTestUsers(inout array: [User]) {
    let user1 = User(name: "King", surname: "Brown")
    let user2 = User(name: "Carlos", surname: "Santana")
    array += [user1, user2]
} 

You could then use this like so:

var array = [User(name: "Jimmy", surname: "Page")]
addTestUsers(&array) // [Jimmy Page, King Brown, Carlos Santana]

If you are intending to create subclasses of User, however, the previous method is unsuitable since you won't be able to pass an array of subclasses as the argument. In this situation you could override addTestUsers to accept your subclass, but a more elegant solution could be:

func addTestUsers<T: User>(inout array: [T]) {
    let user1 = T(name: "King", surname: "Brown")
    let user2 = T(name: "Carlos", surname: "Santana")
    array += [user1, user2]
}

For the previous function to work you need to mark init(name : String, surname : String) in User as required:

class User : NSObject {
    // ... 
    required init(name : String, surname : String) {
        self.name = name
        self.surname = surname
    } 
    // ... 
}

Marking it as required means any subclasses of User must implement init(name : String, surname : String), therefore it's safe to use T(name: "...", surname: "...") in addTestUsers.

Community
  • 1
  • 1
ABakerSmith
  • 22,759
  • 9
  • 68
  • 78