Pass type parameter through protocols?

  1. 5 years ago
    Edited 5 years ago by ischuetz

    Hi,

    I'm trying to implement a solution for following use case, using generics:

    There's a dialog class, dialog is used to select an item from a list. Dialog has to be generic, so I created a protocol for the items, which delivers e.g. the label used in the list, and a protocol for the "delegate", to handle a clicked item.

    The dialog passes the clicked item to the delegate. Since I'm using generics, the idea is that I have not to use casting in order to get the item with the correct type. I prepared a self-contained example in a playground file, to illustrate what I mean.

    This doesn't compile, since I'm not casting the item down. The problematic places are indicated with <--- and --->

    I can solve this with Java - just implemented it, there DialogListener and DialogItem have type parameter <T>, so I can pass <Student> int the declaration / instantiation and in the listener I receive parameter DialogItem<Student> and thus don't need to downcast.

    I suspect it might not be possible to do this in in Swift since I don't see anything usable in the doc, but I hope I'm wrong...

    Thanks.

    ///////////////////////////////////////////////////////
    // Model class
    class Student {
        let name:String
        
        init(name:String) {
            self.name = name
        }
    }
    
    ///////////////////////////////////////////////////////
    // Dialog item, this wraps the model class
    
    protocol DialogItem {
        
        typealias T
        
        func getLabel() -> String
        
        func getModel() -> T
    }
    
    ///////////////////////////////////////////////////////
    // Implementation of dialog item "wrapper" for student
    
    class StudentDialogItem : DialogItem {
        
        let student:Student
        
        init(student:Student) {
            self.student = student
        }
        
        func getLabel() -> String {
            return student.name
        }
        
        func getModel() -> Student {
            return student
        }
    }
    
    
    ///////////////////////////////////////////////////////
    // Listener
    // ---> how can I pass T to DialogListener and then to DialogItem, to get items with correct type?
    
    protocol DialogListener {
        
        func onItemSelected(item:DialogItem)
    }
    
    
    ///////////////////////////////////////////////////////
    // Dialog, with "dummy" functionality
    
    class ItemSelectionDialog {
        let items:DialogItem[]
        let listener:DialogListener
        
        init (items:DialogItem[], listener:DialogListener) {
            self.items = items
            self.listener = listener
        }
        
        func show() {
            for item in items {
                println(item.getLabel())
            }
        }
        
        func simulateClick(index:Int) {
            listener.onItemSelected(items[index])
        }
    }
    
    ///////////////////////////////////////////////////////
    // Implementation of dialog listener
    
    class DialogListenerImpl : DialogListener {
        
        func onItemSelected(item: DialogItem)  {
           let student:Student = item.model; //<---- DialogItem doesn't have type information, don't want to cast
           //do something with student
            println("Selected a student!, name: " + student.name)
        }
    }
    
    
    
    ///////////////////////////////////////////////////////
    
    let items:Student[] = [Student(name: "Student1-name"), Student(name: "Student2-name"), Student(name: "Student3-name")]
    
    let listener = DialogListenerImpl()
    
    let studentDialogItems:StudentDialogItem[] = items.map({StudentDialogItem(student: $0)})
    
    let dialog = ItemSelectionDialog(items: studentDialogItems, listener: listener)
    
    dialog.show()
    
    dialog.simulateClick(1)
    

    This is the Java code that does what I want:

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    
    
    ///////////////////////////////////////////////////////
    //Model class
    
    class Student {
    
    	private String name;
    
    	public Student(String name) {
    		this.name = name;
    	}	
    	
    	public String getName() {
    		return name;
    	}
    }
    
    ///////////////////////////////////////////////////////
    //Dialog item, this wraps the model class
    
    class StudentDialogItem implements DialogItem<Student> {
    
    	private Student student;
    	
    	public StudentDialogItem(Student student) {
    		this.student = student;
    	}
    
    	@Override
    	public String getLabel() {
    		return student.getName();
    	}
    
    	@Override
    	public Student getModel() {
    		return student;
    	}
    }
    
    ///////////////////////////////////////////////////////
    //Implementation of dialog item "wrapper" for student
    
    interface DialogItem<T> {
    
    	String getLabel();
    	
    	T getModel();	
    }
    
    
    ///////////////////////////////////////////////////////
    //Listener
    
    interface DialogListener<T> {
    
    	void onItemSelected(DialogItem<T> item);	
    }
    
    ///////////////////////////////////////////////////////
    //Dialog, with "dummy" functionality
    
    class ItemSelectionDialog<T> {
    
    	private List<DialogItem<T>> items;
    	private DialogListener<T> listener;
    	
    	public ItemSelectionDialog(List<DialogItem<T>> items, DialogListener<T> listener) {
    		this.items = items;
    		this.listener = listener; 
    	}
    
    	public void show() {
    		for (DialogItem<T> item : items) {
    			System.out.println(item.getLabel());
    		}
    	}
    	
    	//Simulate click
    	public void simulateClick(int index) {
    		
    		listener.onItemSelected(items.get(index));
    	}
    }
    
    ///////////////////////////////////////////////////////
    //Implementation of dialog listener
    
    class DialogListenerImpl implements DialogListener<Student> {
    
    	@Override
    	public void onItemSelected(DialogItem<Student> item) {
    		Student student = item.getModel();				
    		//do something with student
    		System.out.println("Selected a student!, name: " + student.getName());
    	}
    }
    
    ///////////////////////////////////////////////////////
    
    public class Main {
    
    	public static void main(String[] args) {
    		List<Student> students = Arrays.asList(new Student("Student1-name"), new Student("Student2-name"), new Student("Student3-name"));
    		
    		DialogListener<Student> listener = new DialogListenerImpl();
    		
    		//Wrap the model objects in dialog items
    		List<DialogItem<Student>> studentDialogItems = new ArrayList<DialogItem<Student>>();
    		for (Student student : students) {
    			studentDialogItems.add(new StudentDialogItem(student));
    		}
    		
    		ItemSelectionDialog<Student> dialog = new ItemSelectionDialog<Student>(studentDialogItems, listener);
    		
    		dialog.show();
    		
    		dialog.simulateClick(1);
    	}
    }

  2. niutech

    2 Jul 2014 Administrator
    Edited 5 years ago by niutech

    Tom Lieber replied:

    I think you want to use type constraints along these lines:

     class ItemSelectionDialog<DL: DialogListener where DL.T: DialogItem {
          let listener:DL
          let items:DL.T[]
          
          init (items:DL.T[], listener:DL) {
              self.items = items
              self.listener = listener
          }
          
          func show() {
              for item in items {
                  println(item.getLabel())
              }
          }
          
          func simulateClick(index:Int) {
              listener.onItemSelected(items[index])
          }
      }
    
      class DialogListenerImpl : DialogListener {
          
          func onItemSelected(item: StudentDialogItem)  {
              let student:Student = item.getModel(); //<---- DialogItem doesn't have type information, don't want to cast
              //do something with student
              println("Selected a student!, name: " + student.name)
          }
      }

    I’m pretty sure that’s close, but trying to finish it has already caused Xcode to crash half a dozen times and I don’t want to start my day that way. :)

or Sign Up to reply!