위치 기반 스터디 매칭 어플리케이션

  • SeSAC 2기에서 진행한 SLP 프로젝트
  • 기능: 위치를 기반으로 매칭을 원하는 사용자와 스터디 요청 및 수락 및 실시간 채팅 기능
  • 개발기간: 2022.11.08 ~ 2022.12.06
  • 목표
    •  RxSwift Input Output 패턴 Develop
    • Moya 사용해보기
    • RxDataSources 사용
    • SocketIO 사용
  • RxSwift, CodeBase UI, RxDataSources, MVVM(Rx Input Output) 패턴, SocketIO(실시간 소켓 통신), Realm, Moya, firebase 등의 기술들을 사용


Git 링크


SLP 화면

map, 스터디 검색 및 추가, 스터디 수락 및 요청, 채팅, 새싹샵, 내정보, 정보관리


사용 외부 라이브러리

  • Toast
  • SnapKit
  • Moya
  • firebase-ios-sdk
  • RxSwfit
  • RxDataSources
  • RxMkMapView
  • RxCoreLocation
  • socket-io-client-swift
  • Realm
  • IQkeyboardManager

주요 기술 스택

  • SnapKit
    • 이전 출시 프로젝트에서는 SnapKit을 사용하지 않고 바닐라로 뷰를 작업했었기에 SnapKit을 사용하여 layout을 잡아주었습니다.
    • 확실히 코드양이 주는 장점이 느껴졌습니다.
  • Moya
    • 이전까지 네트워크 통신은 Alamofire만 사용해왔었는데 새로운 라이브러리를 사용해보고 싶어서 사용해보고 싶어 채택한 라이브러리입니다.
  • firebase-ios-sdk
    • firebase를 사용하여 fcm Notification을 보냈습니다.
  • RxSwfit
    • 이전 출시 프로젝트에서 한번 사용해봤지만 겉핥기식으로 사용했다고 생각해 규모가 큰 프로젝트에 녹여보면서 조금 더 Rx를 갈고 닦았습니다.
  • RxDataSources
    • RxSwift 용 UITableView와 UICollectionView의 DataSources 관련 라이브러리입니다.
    • 기존 collectionView, tableView를 사용할 때보다 편리하게 Rx에 녹일 수 있었습니다.
  • socket-io-client-swift
    • 실시간 채팅 기능을 구현하기 위해 사용했습니다.
  • Realm
    • 이전 출시 프로젝트에서 CRUD를 익히기 위해서 적용해봤고 이번에도 사용했습니다.
    • 채팅 이전 내역들을 매번 서버에서 모두 받아오기에는 통신하는 시간도 고려해야하므로 일정 부분까지는 Realm데이터에 저장, 그 이전 내역들을 서버에서 받아오는 방식을 사용했습니다.

화면 구성

SLP 화면 구성


회고

  • Rx Input Output pattern
    • 예전 출시 프로젝트에서 조금 사용해본 Rx Input Output을 좀 더 Develop하고 싶어서 적용해봤습니다.
    • 규모가 큰 프로젝트라 ViewControllerViewModel 사이의 의존성을 최대한 줄이고 로직을 세분화 시키고 싶었습니다.
    • InterAction과 행해지는 action을 확실히 분리해주니 나중에 에러가 생겼을 때 코드를 수정하거나 추가할 때 확실히 편하다고 느꼈습니다.
    • Rx Input Output 패턴은 볼륨이 커서 조금 더 상세하게 정리해뒀습니다
    • https://s2ung.tistory.com/35

SLP Rx Input, Output code

 

  • RxDataSources
    • 개인적으로 제일 생소해서 적용하면서 가장 많은 시간을 투자했던 부분 같습니다.
    • RxDataSource 모델을 생성해준 후 collection, table View에 bind시켜주는 방법입니다.
    • 개인적으로 느낀 장점으론 Rx Input Output 패턴을 사용하며 tableViewDelegate안에 로직들을 넣어주고 분리해주는 것보다 훨씬 로직을 깔끔하게 분리해줄 수 있다는걸 느꼈습니다.
    • cell이 tap 됐을 때나 그 인덱스를 가지고 액션을 처리해줘야 할때는 input 타입을 IndexPath로 넣어줘 반응형으로 처리해줘서 편리했습니다.(원래는 cellForRowAt을 사용했었습니다)
      • cellTapped: collectionView.rx.itemSelected.asSignal() 같은 방식
    • 단점으론 Section을 BehaviorRelay로 정의해줘 bind시켜줬기 때문에 값을 추가하거나 삭제해줄 때는 BehaviorRelay에 직접 접근이 불가해(get type) 아래와 같이 array를 생성해 값을 편집해줘야 했습니다.
      • 물론 처음 써봐서 가장 적합한 방법이 아닐 수도 있습니다. 좀 더 develop해야 겠다고 느꼈습니다!
    • 자동으로 데이터가 bind 되어 추가 삭제 내장 애니메이션도 깔끔하고 index error가 확실히 덜 난다고 느꼈습니다.
func setSectionValue(model: SearchCollecionModel, section: BehaviorRelay<[SearchCollecionSectionModel]>) {
        var array = section.value
        array[1].items.append(model)
        section.accept(array)
    }
    
    func deleteSectionValue(indexPath: IndexPath, section: BehaviorRelay<[SearchCollecionSectionModel]>) {
        var array = section.value
        array[1].items.remove(at: indexPath.item)
        section.accept(array)
    }

 

SLP DataSources code

 

  • Moya
    • 이번에 새로 사용해본 네트워크 라이브러리입니다.
    • URLSession을 추상화한 Alamofire를 한번 더 추상화 해서 만들어진게 Moya입니다.
    • Alamofire를 사용하면, URL과 같은 것을 사용할 때마다 request에 넣어주어야 해(Network Layer에 접근함)템플릿이 갖추어 지지 않아서 재사용에 유리하지 않은 구조입니다
    • 그러나 Moya에서는 Network layer를 템플릿화 해놓고 app에서는 request, response만 처리하면 돼서 재사용에 좀 더 유리합니다.
    • 또한 enum 타입으로 정의되어 있어서 안전하고 정돈된 방식으로 캡슐화가 되어 있습니다.
      • 추상화와 마찬가지로 캡슐화도 객체 지향 프로그래밍 중 하나입니다.
    • 추상화
      • 객체 지향 프로그래밍의 특징 중 하나입니다.
      • 객체들의 공통된 부분들을 따로 뽑아내서 구현해놓은 것입니다.
      • 공통된 정보들을 따로 뽑아냈기 때문에 구체적이지 않고 추상적이라 추상화라고 합니다.
        • generic도 마찬가지로 추상화적 타입입니다.!
      • 추상화 된 정도로는 Moya > Alamofire > URLSessions 순입니다.
    • 사용 방법
      • enum 타입으로 제공할 네트워크 서비스들을 선언
      • baseURL, path, method, Task, validationType, headers를 미리 Target 파일에 정의합니다.
        • BaseURL = 말그대로 기본 주소
        • Method: 통신 방법(put, push, delete, get, psot)
        • Task: 인코딩 할 방법( .requestPlain, . requestParameters 등등)
        • ValidationType: 성공하는 네트워크 응답값
        • Headers: 통신 시 헤더에 들어갈 값
      • 그리고 저는 바로 사용하지 않고 로직을 조금 더 분리해주고 싶어서 APIService라는 파일을 만들어줘서 escaping clousre를 사용해 응답값을 사용할 수 있게 만들어줬습니다.

SLP Moya Target code
SLP Moya APIService code + 통신 예제

  • 러닝 커브가 엄청 높지는 않아 금방 금방 적용했었던 것 같습니다.
    • 확실히 딱 선언할 때 선언하고 사용할 때 정의해 둔 APIService에서 받아와서 작성하니까 편했습니다. 
    • 그래도 가끔 response body의 타입형이 다른 부분들의 encoding 방식을 찾는데 종종 애를 먹었던 것 같습니다 😭
      • 시간상 해보진 못했지만 더 로직을 세분화 시키고 싶어서 다음 프로젝트에선 DTO를 도입해보려고합니다.

 

  • SocketIO
    • 소켓통신이란 무엇일까?
      • 네트워크를 통해 서버, 클라이언트 양쪽에 링크를 생성 후 그 링크를 통해 데이터를 주고 받는 양방향 통신입니다.
      • 소켓통신은 볼륨이 커서 따로 정리해봤습니다.
      • 소켓 통신 링크 -> https://s2ung.tistory.com/40

 

  • 채팅구현
    • 채팅 화면이 보여질 때 서버 호출, Realm, socket을 모두 사용했습니다.
    • 채팅의 모든 데이터를 서버에서 받아올 수는 없으니 일정 시점의 데이터는 자체 데이터베이스인 Realm에 저장했습니다.
    • 그 이후의 채팅 데이터를 서버에 호출해서 받아왔습니다.
    • 실시간으로 상대방이 보내는 메세지는 socket에서 받아와 화면에 녹여주었습니다.
    • 이렇게 해준 이유는
      • Realm에 저장하지 않고 http 통신을 통해서만 데이터를 받아오게 되면 데이터 유실, delay, timeout 등 많은 문제가 발생 할 수 있습니다.
      • 또한 네트워크가 끊겼을 때도 이전 채팅 내역이 보여야하는데 로컬에서 저장해주지 않았으면 서버 호출이 불가하니 채팅 내역을 확인할 수 없습니다.
      • 또한 로컬에서 저장해주는 방식 중 Realm을 사용해준건 Realm은 객체 중심의 데이터베이스(ORM)이므로 사용하면 가져온 데이터를 별도의 가공 없이 바로 사용할 수 있고 속도도 빨라서 사용했습니다.
      • 서버 호출을 사용한 이유는 소켓 연결이 끊긴 시점에 채팅이 오면 Realm에 추가를 시킬 수 없고 socket으로부터 채팅을 받아올 수도 없으므로 사용해줬습니다.
        • 카카오톡 같은 경우 화면이 꺼져 있어도 메세지 알림이 오는건 데이터가 추가되고 있는 것이 아닌 Firebase에서 알림을 보내주고 있는 것이지 실시간 Socket 통신이 이뤄지고 있는 것이 아닙니다.
      • 이러한 이유로 일정 시점 까지는 자체 데이터베이스인 Realm에 저장해준 후 그 마지막 채팅의 날짜로부터 서버에서 받아왔습니다.

Trouble Shooting

  • cell 강한순환참조 문제
    • tableView 내의 cell을 눌렀을 때 cell UI를 변경해줄 때 생겼던 문제였다.
    • cell.button.rx.tap을 했을 때 dispose 해줄때 cell.disposeBag가 아닌 self.disposeBag로 작성 후 실행했을 때 원하는 액션이 제대로 실행되지 않아 deinit을 찍어봤을 때 강한 순환 참조가 발생하는걸 알 수 있었다.
    • 찾아보니 self.disposeBag가 아닌 cell내에 disposeBag를 선언해주고 그것을 cell내의 prepareforReuse 함수내에서 기존의 disposeBag을 초기화 시켜 deinit해줘서 그 disposeBag과 연결된 Stream들을 해제해 주고 다시 연결해 사용해주니 해결되었다.
    • rx를 사용하며 클로저를 자주 사용하게 되어 강한 순환 참조를 늘 조심한다고 생각했는데 막상 deinit을 매번 찍어주지는 못했던 것 같다.
    • 습관적으로 deinit을 사용해 제대로 해제가 되고 있는지 체크해봐야겠다고 생각했다.
    • 막상 오류에 부딪히니 강한순환참조의 문제라고는 생각 못하고 로직 체크하기에 급급했다.
    • 늘 명심하자 강순참 조심!

SLP 강한순환참조 문제 해결 code


+ Recent posts