안녕하세요
포카마켓 iOS 팀에서 개발을하고 있는 WooKoo 입니다.
많은 서비스들에서 사용하는 실시간 채팅 기능이 저희 서비스의 여정에 대한 이야기를 조금 얘기해보고자합니다. 오늘은 기술적인 이야기와 코드들이 조금 있을 것 같아요!
앱이 첫 출시까지 대략 반년이라는 시간이 걸렸는데요. 그 중에서 가장 시간을 많이 들였었던 피처였던 것 같습니다.
왜냐하면 다른 피처들은 간단하게 제작할 수 있지만 채팅은 카카오톡처럼 이미 유저들에게 친숙한 기능들은 대부분 지원을 해야해서 참으로 어려웠습니다.
실시간 채팅이라는 피처가 어려운 이유는 크게는 소켓 핸들링, 채팅 타입별 다이나믹한 UI 이기 때문일 것 같아요 !
소켓이랑 UI 핸들링과 관련 된 내용을 다뤄보고자합니다.

- 소켓
우선 서버 개발자분과 당시소켓 서버 구축을 이용하자고 하였는데, 노드로 채팅 서버를 구축한다고 하셨다가 돌고 돌아 파이썬으로 채팅 서버를 개발했습니다.
파이썬은 SocketIO 지원이 미흡하다하여 웹소켓으로 진행하였습니다. 당연스레 저희도 socketIO 가 아닌 starscream 라이브러리를 이용하였습니다.
https://github.com/daltoniam/Starscream
GitHub - daltoniam/Starscream: Websockets in swift for iOS and OSX
Websockets in swift for iOS and OSX. Contribute to daltoniam/Starscream development by creating an account on GitHub.
github.com
그래도 당시에 소켓서버 로컬 노드서버에다가 소켓 서버 올려서 테스트 진행했었습니다.
아까워서 요거는 공유 드리겠습니다. (별거 없지만요)
https://github.com/2jae6/SocketNodeServer
GitHub - 2jae6/SocketNodeServer: 노드로 소켓서버 구축 Sample
노드로 소켓서버 구축 Sample. Contribute to 2jae6/SocketNodeServer development by creating an account on GitHub.
github.com
당시에 가장 기술적인 고민이 되었던 지점은 클라 - 소켓 - DB 간 순서였습니다.
클라이언트에서 채팅 API 를 통해서 요청을 보내면
1. DB 에 저장하고 서버에서 소켓을 브로드 캐스팅 해줍니다.
2. 소켓 서버로 직접 쏴서 뿌리고, 소켓 서버에서 DB 로 저장합니다.
두 가지는 아래와 같은 기능적인 차이가 있었던 것 같습니다.
방법 1의 경우에는
- DB 에 저장한 뒤 소켓으로 받기 때문에 속도면에서 사용자에게 보여지는 것이 느리다.
- 욕설과 같은 필터링을 저장 시에 가능하다.
- 채팅이 유실로부터 안전
방법 2의 경우에는
- 유저가 확인하는 속도 면에서 빠르다.
욕설과 같은 핸들링이 불가능 (물론 클라에서 처리 가능은 함)
채팅이 소켓서버에서 유실 될 경우 내가 보냈던 나중에 들어와보니 채팅이 사라질 경우가 있음
고민을 하다가 방법 1로 선택을 했습니다.
무엇보다도 채팅이 유실되는 문제를 중요 시 했기에 말이죠.
또한 속도적인 측면의 문제는 내가 보낸 메시지가 들어오면 버리고, 클라이언트에서 내가 보낸 메시지가 즉시 그려서 처리하여 해결하였습니다.
소켓 연결을 잘 하셨다면 잘 끊어주는 것도 중요합니다. 클라이언트에서는 크게 상관없을지 몰라도 서버에서는 해당 소켓이 계속 살아 있어 오류를 내뿜는다고 하더라구요.
추가로 소켓이 살아있으면 읽음처리 해주는 로직들도 상대방이 채팅방을 나갔는데 보고있는 것처럼 로직이 돌기에 잘 끊어주어야합니다.
그래서 화면 이탈 할 때(inActive, viewWillDisappear 시점 등) 로 넘어가질 때 disConnect 해줍니다.
그 후에는 적절하게 소켓으로 들어온 데이터를 가공해서 채팅들을 핸들링하면 큰 산 하나는 넘은 것 같습니다.
- 채팅 타입
채팅에는 일반 메시지, 사진, 파일, 첫 채팅 공지, 채팅 종료 등 다양한 타입의 메시지들이 있습니다.
우선은 다양한 타입들을 정의해두고, 나의 채팅인지, 상대방의 채팅인지, 두 유저가 아닌 공지 채팅인지 등의 소유 구분이 필요합니다.
이를 타입별로 enum 들을 만들어주고, 파싱 할 때 raw value 를 json 과 비교하여 처리해주는 작업을 진행합니다.
모델은 대략 이런 형태로 구현하였습니다.
// 채팅이 내 채팅인지 상대방 채팅인지 구분
public enum ChatOwnerType: Int, Equatable {
case myChat = 0
case yourChat = -1
case notice = 1
}
public enum ChatMessageType: String, Encodable, Decodable {
case common = "일반" // 일반
접속유저알림
public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let raw = try container.decode(RawValue.self)
switch raw {
default:
if let value = ChatMessageType(rawValue: raw) {
self = value
} else {
throw DecodingError.dataCorrupted(
.init(
codingPath: [],
debugDescription: "Invalid raw value for ChatMessageType"
)
)
}
}
}
}
// 내 자신이 판매자인지 구매자인지 구분
public enum TradeType: Equatable {
case seller
case buyer
}
이러한 메시지들을 메시지 큐 (배열) 에 앞뒤로 추가해주면서 채팅들을 관리하게 됩니다.
최근에 입력한 채팅들을 불러서 큐에 넣어주고 이전 채팅들과 신규 채팅들을 앞뒤로 붙여서 만들어나아가는거죠!
배열의 경우에는 앞에 element 를 추가하면 전체 인덱싱이 밀려서 연산 속도가 안좋거든요. (On)
SwiftCollection Dequeue 이라고 있는데 이걸 이용하시면 (O1) 속도로 조금 더 최적화가 가능합니다.
https://github.com/apple/swift-collections/blob/main/Documentation/Deque.md
swift-collections/Documentation/Deque.md at main · apple/swift-collections
Commonly used data structures for Swift. Contribute to apple/swift-collections development by creating an account on GitHub.
github.com
초기에는 UITableView 를 이용해서 하나하나 핸들링 해주었습니다.
- 새로운 채팅이 왔을 때 하단으로 스크롤을 옴겨주거나
- 채팅이 화면 사이즈보다 적을 때는 위로 메시지를 붙여주어야하거나, 그 반대는 최하단
- 상대방 채팅과 동시에 들어왔을 때 테이블뷰를 리로드 시켜줘야하는데 동시성 문제로 메시지 큐의 갯수와 업데이트 개수가 다를 때
등등등
많은 처리들을 만날 수 있었습니다.
그렇게 한 2년 쯤 채팅을 서비스 하다가 ChatLayout 이라는 라이브러리를 발견하게되었는데, 위 문제들을 해결하여 지원해주고 있었습니다.
현재는 이 ChatLayout과 UICollectionView 로 전환하여 잘 쓰고 있답니다.
그 외에도 몇가지 케이스에 따른 키보드 핸들링을 처리해줘야해요!
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardWillShowNotification),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
NotificationCenter.default.addObserver(
self,
selector: #selector(keyboardDidShowNotification),
name: UIResponder.keyboardDidShowNotification,
object: nil
)
키보드는 노티피케이션을 이용해서 높이를 가져와 처리했습니다.
결론적으로
그 외에도 미디어 파일 업로드와, 뷰어 등 너무나도 많은 고민들과 피처들이 담긴 저에겐 좋은 경험의 프로젝트였습니다.
다양한 회사에서 실시간성 서비스 혹은 채팅들을 구현하려고 하시는데, 유저들에 대한 기준이 이미 카톡과 같은 대형 회사의 서비스로 높아져서
참으로 스타트업 개발자들은 빠른 시간 내에 비슷하게까지 구현하는 것이 험난하다는 것을.. 많이 느꼈습니다.
감사합니다!
'개발 > 개발' 카테고리의 다른 글
포카마켓 iOS 팀의 여정 (세상에 나온 앱) - EP03 (4) | 2024.10.17 |
---|---|
포카마켓 iOS 팀의 여정 (디버깅 및 검수) - EP02 (9) | 2024.10.16 |
포카마켓 iOS 팀의 여정(개발 전략 및 체계) - EP01 (2) | 2024.10.14 |
[iOS] - Quick/Nimble 문서로 Unit Test 배우기 - 2 (0) | 2023.02.14 |
[iOS] - Quick/Nimble 문서로 Unit Test 배우기 - 1 (0) | 2023.02.14 |