Cell 안의 button을 처리하는 여러가지 방법들

1 답변 글타래를 보이고 있습니다
  • 글쓴이
    • 뚭니
      참가자
      • 글작성 : 9
      • 답글작성 : 17
      1,590 포인트

      Cell 내부의 버튼 이벤트를 처리하기 위해 방법을 검색하던 중에
      @쥬트 님께서 남겨주신 Delegation 패턴을 이용하여 Cell내부의 버튼 이벤트 처리하기 글에 야곰님과 멍단비님 답글을 보고, 
      추천해주신 notification center로 전달하는 방법과 closure 로 처리하는 방법을 공부해보고자 이 글을 정리하게 되었습니다.
      살펴본 결과 Cell 안의 버튼을 처리하는 방법에는 여러가지의 패턴이 있었습니다.
       
       

      Contents

      Handling Button

      TableView 또는 CollectionView 안에 있는 버튼을 처리하기 위한 레퍼런스를 조사하다보니 개발자마다, 상황마다 선호하는 여러가지 방법들이 있었습니다.
       
      1. tag 로 처리
      * 장점: 버튼마다 고유한 식별이 가능하고 IBOutlet 선언 대신으로 사용하는 경우 편리학 사용이 가능하다.
      * 단점: 애초에 셀의 인덱스를 저장하기 위해 사용되는 것이 아니기 때문에 100개 버튼이면 100개의 태그.. multiple selection하면 악몽 수준

      2. delegate 로 처리
      * 장점: cellForRowAt메서드에 할당해서 row 삽입 / 삭제 가 있는 경우에 indexPath 핸들링에 유리하다.
      * 단점: 버튼이나 화면이 많아지면 딜리게이트 프로토콜을 선언하고 뷰 컨트롤러를 상속해야해서 과정이나 네이밍이 귀찮다. 추적하기도 어렵다.

      3. Observer & Notification 로 처리
      * 장점: 느슨한 결합으로 인하여 시스템이 유연해지고, 객체간의 의존성을 제거할 수 있다. pull 방식이 아닌 push 방식을 사용함으로써 직관적으로 이해하기 쉽습니다.
      스크린샷 2020-05-30 오후 11 45 07
      출처: https://coloredrabbit.tistory.com/86
      * 단점: 너무 많이 사용하게 되면, 콜백헬의 늪에 빠져 상태 관리가 힘들 수 있다. 비동기 방식이기 때문에 이벤트 구독을 원하는 순서대로 받지 못할 수 있다.

      4. call back closure 로 처리
      * 장점: 관련 메서드들이 하나의 클로저안에 모두 있기에 그 안에서만 처리할 수 있다.
      * 단점: 각각의 셀이 클로저 변수(버튼이 탭됐을 때 실행할 행동)를 저장하기 위해 메모리에 할당되어야 한다. 이 접근 방법은 함수가 커지면 꽤 많은 메모리를 차지할 수 있다.

       

      내가 처리해야하는 요구사항

      제가 처리해야하는 부분은 아래 영상과 같은 과업으로,
      요청 탭에서 채팅을 하고 싶어하는 상대로부터 요청이 들어왔을 때
      ‘거절하기’ 버튼을 누르면 Firebase의 요청 db에서 삭제되면서 동시에 테이블 뷰의 리스트에서도 삭제되어 reload 하고,
      ‘채팅 시작하기’ 버튼을 누르면 Firebase의 채팅 db에 추가되면서 동시에 요청 db에서는 삭제되고 테이블 뷰 리스트에서도 역시 삭제된 후 reload 되는 것이었습니다.

       

      Closure Source code

      1. UICollectionViewCell.swift

      // input이 없고 void를 반환하는 클로저 타입 변수 선언
      var accept : (() -> ()) = {}
      var refuse : (() -> ()) = {}
      
      @IBAction func acceptChat(_ sender: UIButton) {
         //Call your closure here
         accept()
      }
      @IBAction func refuseChat(_ sender: UIButton) {
         refuse()
      }
      

       

      2. UIViewController.swift

      func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RequestCell", for: indexPath) as! RequestCVCell
         // 시작하기 버튼 눌렀을 때 실행할 함수 선언
         cell.accept = { [unowned self] in
         // 1. 새로운 채팅방 개설하기 위해 DB에 채팅 데이터 추가하는 함수 호출
         // 2. DB 에서 요청 데이터 삭제하기
         self.destinationUid = request.userID
         self.createRoom(uid: self.myUid!, destinationUid: request.userID)
         Database.database().reference().child("chatrooms").child(request.chatIdx).removeValue()
         self.requestList.remove(at: indexPath.row)
         self.requestCV.reloadData()
      }
         // 거절하기 버튼 눌렀을 때 실행할 함수 선언
         cell.refuse = { [unowned self] in
         // 1. DB 에서 요청 데이터 삭제하기
         Database.database().reference().child("chatrooms").child(request.chatIdx).removeValue()
         self.requestList.remove(at: indexPath.row)
         self.requestCV.reloadData()
      }
         return cell
      }
      

       
      이때, unowned self 를 사용한 이유는 다음과 같습니다.
       

      unowned self

      Notification Observer

      이번에는 Notification + 옵저버 패턴을 이용하여 셀 안의 버튼을 처리하는 방법을 정리해보려고 합니다.

      1. UICollectionViewCell.swift

      // Notification 패턴을 사용하는 셀이 많다면
      // Notification Name을 관리하는 Extensions 를 따로 만들어도 좋을 것 같습니다.
      let name = Notification.Name(rawValue: "notificationKey")
      NotificationCenter.default.post(name: name, object: nil)
      

       

      2. UIViewController.swift

      let notificationKey = "acceptButtonPressed"
      
      // class 안에 선언하는 부분
      let accept = Notification.Name(rawValue: "notificationKey")
      // observer 를 제때 제거해주지 않으면 메모리 누수가 일어날 수 있습니다.
      deinit{
          NotificationCenter.default.removeObserver(self)
      }
      func createObservers() {
         NotificationCenter.default.addObserver(self, selector: #selector(createChatrooms), name: accept, object: nil)
      }
      @objc func createChatrooms(notification: NSNotification) {
         // 실행하고 싶은 함수 구현
      }
      
      3+
      avataravatar
    • 야곰
      키 마스터
      • 글작성 : 37
      • 답글작성 : 435
      12,180 포인트

      깔끔한 정리 멋지네요! 역시 정리 장인!
      해결 방법들이 상황마다 다르고 정답이 없다보니 참 고르기 쉽지 않죠 ㅎㅎ 역시 경험이 많이 좌지우지 하는 것 같습니다.

      노티피케이션 이름 등의 상수 등은 묶어서 표현하고 싶을 때 아래처럼 사용하면 훨씬 보기도 좋고 실수도 줄일 수 있습니다.


      struct MyNotification { static let accept = Notification.Name(rawValue: "DidAcceptNotification") static let reject = Notification.Name(rawValue: "DidRejectNotification") } NotificationCenter.default.post(name: MyNotification.accept, object: nil)

      관련된 제 블로그 글도 읽어보시면 더 도움이 될까 싶습니다.

      클로저도 typealias 해서 사용하면 좀 더 보기 쉽습니다.
      typealias EmptyClosure = (() -> ()) 처럼 정의해주면 좀 더 보기도 좋아요 ㅎㅎ

      2+
      avataravatar
      • 뚭니
        참가자
        • 글작성 : 9
        • 답글작성 : 17
        1,590 포인트

        매번 역시 감사하게도 공부할 방향을 짚어주시는 야곰님!

        정말 감사드립니다 ㅎㅎ

        코드리뷰까지, 감사해요 😆

        1+
        avatar
1 답변 글타래를 보이고 있습니다
  • 답변은 로그인 후 가능합니다.

logo landscape small

사업자번호 : 260-27-00477
통신판매업 신고번호 : 제 2020-충북청주-0663 호
고객센터 : 카카오톡채널 @yagom