0

I am learning swift. I would like to use a custom class to be loopable [able to a for...in loop] like Array. Below is the given sample code that so far, I have tried. The class in question is "GuestManager" which is holding a private collection of guests [objects of class Guest]

import Foundation

class Guest{
    var guestId: String
    var guestName: String

    init(gId: String, name: String){
        self.guestId = gId
        self.guestName = name
    }
}

class GuestManager: GeneratorType, SequenceType{
    private var guests = [Guest]?()
    private var nextIndex: Int

    init(guests: [Guest]){
        self.guests = guests
        self.nextIndex = 0
    }

    func next() -> Guest? {
        if  self.nextIndex > (self.guests?.count)! - 1 {
            self.nextIndex = 0
            return nil
        }
        let currentGuest = self.guests![self.nextIndex]
        self.nextIndex += 1
        return currentGuest
    }
    subscript(aGuestId gID: String) -> Guest{
        return (self.guests?.filter({$0.guestId == gID}).first)!
    }
}

I do not want to create separate classes that are conforming to GeneratorType & SequenceType protocols. Instead I have created a single class that is conforming to both protocols.

Below are some of my questions:

  1. I would like to know if this a correct way to have a custom collection type ?

  2. Can we use subscript as a way to perform a search based on a property for example "subscript(aGuestId gID: String)" in the sample code above ?

  3. It is clear from the code for next() function implementation in above sample code that is resetting the "nextIndex" when the iteration reached at the end. How one will handle the situation wherein we use a break statement inside the for...in loop as below:

    for aGuest in guestManager{//guestManager an instance of GuestManager class instantiated with several guest objects
        print(aGuest.guestName)
    }
    for aG in guestManager{
        print(aG.guestId)
        break
    }
    

    In the 2nd for loop the code break out after getting the first Element [Guest object in this case]. The subsequent for loop will start at index 1 in the collection and not at 0. Is there anyway to handle this break situation so that for each subsequent for looping the index is always set to 0?

Thanks

Edit: It seems the "nextIndex" reset issue can be fixed with below code [added inside GuestManager class] for generate() method implementation

func generate() -> Self {
        self.nextIndex = 0
        return self
    }
user2788672
  • 245
  • 4
  • 15

2 Answers2

2

You should not store the nextIndex inside the class. You can use a local variable in the generate method and then let that variable be captured by the closure you pass to the generator you create in that method. That’s all you need to adopt SequenceType:

class GuestManager: SequenceType{
    private var guests: [Guest]

    init(guests: [Guest]) {
        self.guests = guests
    }

    func generate() -> AnyGenerator<Guest> {
        var nextIndex = 0
        return AnyGenerator {
            guard nextIndex < self.guests.endIndex else {
                return nil
            }
            let next = self.guests[nextIndex]
            nextIndex += 1
            return next
        }
    }
}

For subscripting, you should adopt Indexable. Actually, the easiest way to fulfill all your requirements is to pass as much of the logic for SequenceType, Indexable, and eventually (if you want to support it) CollectionType, to your array, which already has these capabilities. I would write it like this:

class GuestManager {
    private var guests: [Guest]

    init(guests: [Guest]){
        self.guests = guests
    }
}

extension GuestManager: SequenceType {
    typealias Generator = IndexingGenerator<GuestManager>

    func generate() -> Generator {
        return IndexingGenerator(self)
    }
}

extension GuestManager: Indexable {
    var startIndex: Int {
        return guests.startIndex
    }

    var endIndex: Int {
        return guests.endIndex
    }

    subscript(position: Int) -> Guest {
        return guests[position]
    }
}

Some more observations:

  1. Your guests property should not be an optional. It makes the code more complicated, with no benefits. I changed it accordingly in my code.
  2. Your Guest class should probably be a value type (a struct). GuestManager is also a good candidate for a struct unless you require the reference semantics of a class (all collection types in the standard library are structs).
Ole Begemann
  • 135,006
  • 31
  • 278
  • 256
0

I think the subscripting approach you're trying here is kind of convoluted. Personally, I would use a function to do this for the sake of clarity.

guestManager[aGuestId: guestId]

guestManager.guestWithID(guestId) 

So stylistically I would probably land on something like this

import Foundation

class Guest{
  var guestId: String
  var guestName: String

  init(guestId: String, guestName: String){
    self.guestId = guestId
    self.guestName = guestName
  }
}

class GuestManager: GeneratorType, SequenceType{
  private var guests: [Guest]
  private var nextIndex = 0

  init(guests: [Guest]){
    self.guests = guests
  }

  func next() -> Guest? {
    guard guests.count < nextIndex else {
      nextIndex = 0
      return nil
    }

    let currentGuest = guests[nextIndex]
    nextIndex += 1
    return currentGuest
  }

  func guestWithID(id: String) -> Guest? {
    return guests.filter{$0.guestId == id}.first ?? nil
  }
}
Dare
  • 2,497
  • 1
  • 12
  • 20
  • By subscripting, I want to make the GuestManager searchable by certain key properties though it is true that we can create functions also but what I am looking for is a more cleaner way of performing search. – user2788672 May 06 '16 at 02:57