Search
Duplicate

2021년 3월 1주차 회고록

포스팅은 꾸준히 해왔지만, 회고록은 2월 4주차를 쓰지 않았기에 3월 1주차와 합쳐서 작성한다.
요즘 다시 자바를 많이 다루고 있으니 컨디션이 많이좋아지고 있다.
정신없는 2월이 지나고 안정적인 3월이 되었다. 물론, 이번달내로 1차 완성해야하는 프로젝트가 있지만, 사실상 내가 맡은부분에 대해서는 순항까진 아니여도 진행이 되고 있으니....

금주의 노래

책을 보면서 공부를 하는시점에서는 노래도 가사가 없고 단순한 리듬의 노래를 들으려 노력한다.
하지만 저번주, 이번주는 주로 실무 코딩 및 진행하는 과정 진행으로 코드작성 위주이기에 좀 더 흥겹게 들으면서 해도 집중이 깨지지 않기에 다른 플레이리스트들을 찾아봤다.
그러다 찾은 채널이 베이버스 스튜디오 그동안은 그냥 배경화면이나, 특정 캐릭터 사진에 노래가 나오는 것들만 봤었는데, 이렇게 가수들이 즐겨듣는 플레이리스트와 함께 해당 가수가 1시간동안 노래듣는걸 같이하니 뭔가 더 분위기도 있고 좋은 것 같다.
플레이리스트는 여럿있었는데 개인적으로는 황소윤의 플레이 리스트가 좋았다.
노래를 듣는 위치인 빨래방이라는 분위기나 원래 새소년의 노래를 좋아하는편이라 지금도 듣고있다.

Flutter

플러터인액션 책 학습 및 포스팅을 계속 진행하고 있다.
사실 2월 4주차는 플러터 공부만 하다 끝났다고 볼 수 있다. 근데 현재 프로젝트에서는 IOS네이티브 약간을 제외하면 Spring으로 API 생성과 관리자 페이지만 만들고 있기에 굳이 이렇게 해야했나 싶기도 하다.
4장과 5장의 내용은 플러터라는 프레임웍의 사용법을 알려준다고 볼 수 있다.
MaterialApp, Scaffold, AppBar와 같은 레이아웃 위젯을 통해 모바일 화면을 구성하고
크기정보를 얻어서 어떻게 조절하는지 스타일을 어떻게 변경하는지 등에 대해서 말이다. 그리고 그 와중 퍼포먼스를 위해 특정 위젯들은 어떤식으로 최적화를 했는지는 간략하게 알려주는 편이다. 그리고 이런 최적화 코드는 이미 다 구현이 되어있기에 개발자 입장에서 하나하나 고려하면서 튜닝할 필요는 없다. 말 그대로 비즈니스 로직에 더 집중해 생산성을 높히라는 의도로 보인다.
그리고 5장을 볼때면 이제 각종 이벤트(제스처)들에 대한 동작들을 설명하는데, 이를 자연스럽게 익히기 위해서는 callback이라는 개념에 대해서는 공부하고 있어야 한다. 그렇지 않으면 그냥 인수값(onPress, onTap, onTapUp등)에 할당한 메서드들이 어떻게 동작하는지 이해가 안되니까 말이다.
5장을 전부 포스팅하지는 못한 상황이지만, 현재까지 느낀점은 웹 개발에서 부트스트랩으로 개발을 하는 느낌과 비슷하다. 각각의 요소들이 애니메이션들까지 다 제작이 이미되어있고 그걸 레고로 조립하듯이 완성시키는것인데, 플러터도 레고를 쌓듯이 위젯을 조립해 어플을 만든다.
그럼 이런 걱정도 든다. 부트스트랩을 할 때도 그렇지만, 정말 사용자 요구사항의 디테일을 들어가다보면 제공해주는 기능들만으로는 부족한 경우들이 생기는데 이에 대한
플러터는 어떻게 해야하지? 생각을 했는데, 물론 난이도는 많이 올라가지만 위젯도 클래스이기에 그를 상속및 재정의해서 나만의 위젯을 만들수도 있는것 같기는 하다.
해당 책의 샘플 프로젝트만봐도 그렇게 만든 사용자 위젯들이 있는데 이러한 부분들을 분석하면서 나도 만들 수 있도록 해야할 것 같다.
하지만, 현재 ATDD 과정 진행, 회사에서는 서버개발 및 웹쪽으로 내 담당 업무 중요도가 달라져서 플러터를 할 시간이 부족한 상황이라 책으로 보고 포스팅한 내용을 체화할 시간이 부족해서 그런지 내가 머리로 아는 지식들과 실무코딩에 있어서 괴리감이 꽤 있는편이다.
회사 업무가 여유가 생기거나 ATDD과정이 끝나면 토이프로젝트로 어플을 만들어봐야겠다.

회사 - IOS Call & Hangup

2월은 사실상 기본적은 VOIP 기술검증시간이였고, 어느정도 검증이 끝나서 플러터로도 VOIP및 앱 개발이 가능하겠다는 판단하에 요구사항 개발의 구체화가 시작되었다.
다른 부분들은 앱 개발을 좀 더 중점적으로 하는 분이 맡고 나는 웹 개발 위주이기에 크게 문제가 없나 싶었지만, 이쯤해서 나온 나도 고민하고 해결해야하는 가장 큰 이슈는 Hangup이였다.
지금은 FCM(APNS)로 VOIP 요청을 하면 타겟 핸드폰은 안드로이드는 Push Notification에 의한 Custom Page노출, IOS는 CallKit을 이용한 전화화면 노출인데, IOS는 플러터 라이브러리중 flutter_ios_voip_kit 이라는 라이브러리를 사용해서 화면을 노출시키고 있었다.
그런데 IOS에서 현재 VOIP를 받을 경우 내가 1분이 지나던 10분이 지나던 내가 응답을 안하면, 부재중화면으로 넘어가질 못한다. Spring Server의 FCM(APN) API를 통해 VOIP를 보내는 상황인지라 소켓통신도 아니고 이걸 어떻게 해결해야하나 고민하기 시작했다.

첫 번째 시도: event callback 을 통한 핸들링

해당 IOS용 VOIP 라이브러리의 사용법으로는 전화 수락(거절)시 callback을 통해 로직을 제어할 수 있었고, FlutterIOSVoIPKit.instance 으로 싱글톤 인스턴스를 받아 onDidAcceptIncommingCall이나 onDidRejectIncommingCall 을 에 로직을 할당해서 수행하도록 했다. 그렇다면 수락(거절)뿐 아니라 전화가 오는 이벤트 역시 있지 않을까 해서 보니 onDidReceiveIncomingPush 으로 핸들링을 할 수 있었다. 그래서 생각보다 쉽게 해결이 되겠다고 생각을 했었다.. 하지만, 그것은 큰 착각이였다.
onDidReceiveIncomingPush 콜백을 정의해서 부재중 으로 변경하는 코드는 심지어 예제코드에 대놓고 _timeOut으로 존재하고있었다!. 그래서 냉큼 copy/paste로 코드를 붙혔고 테스트를 시작했다.
void _timeOut({ int seconds = 15, }) async { timeOutTimer = Timer(Duration(seconds: seconds), () async { print('🎈 example: timeOut'); final incomingCallerName = await voIPKit.getIncomingCallerName(); voIPKit.unansweredIncomingCall( skipLocalNotification: false, missedCallTitle: '📞 Missed call', missedCallBody: 'There was a call from $incomingCallerName', ); });
Dart
복사
15초이후 부재중전화로 변경해주는 예제코드
foreground상태에서 해당 로직은 정상적으로 동작을했다. 이쯤에서 나는 한 90%정도는 끝났다고 생각을 했고, background에서도 테스트를 했는데 정상적으로 동작을 했다.
그리고, 마지막으로 not running상태에서 테스트를 했는데... 동작을 안한다. 마치 내 카톡처럼 어떠한 알림도 없이 고요할 따름이였다.
그래서, 해당 라이브러리 git으로 이동해 이슈를 확인해 본 후, 라이브러리 코드를 뜯어본 결과 이런 내용을 발견했다.
/// [onDidReceiveIncomingPush] is not called when the app is not running, because app is not yet running when didReceiveIncomingPushWith is called. IncomingPush onDidReceiveIncomingPush;
Dart
복사
바로, 해당 콜백은 앱이 not running 상태일 때 호출되지 않는다는 것인데 그 이유가 IOS에 didReceiveIncomingPushWith 으로 push가 도착했을때는 아직 앱이 동작을 하지 않은 상태이기 때문에 해당 콜백을 수행할 수 없다는 것이였다. 이쯤에서 멘탈이 슬슬 깨지기 시작한다...

두 번째 시도: apns 파라미터를 통한 유효시간이 있는지 검색

그래도 아직까진 희망이 있었다. 사람들도 다 IOS개발을 할테고 전화를 걸면 다 자연스럽게 부재중으로 넘어가니까 apns payload를 통해 유효시간을 주면 일정시간 이후에 유효하지 않는 VOIP는 부재중으로 자동으로 넘어가지 않을까? 생각을 했다.
그래서 희망을 가지고 apns parameter를 찾기 시작했는데...

없다.... 아무리 찾아봐도 없다...

사실 희망을 가졌던 이유중에는 내가 대충 예전에 봤었던 파라미터중 apns-expiration 을 보고 저거지 않을까? 했었던 것이였는데, 해당 파라미터에 대한 내용을 다시 읽어보니..
이는 voip apns가 부재중으로넘어가는 유효시간이 아닌 해당 apns를 반복 전달하는 일종의 스케줄러같은 파라미터였다.
이사님에게 여쭤보니 확인사살로 파라미터로 그러는건 없다는 말과 네이티브에서 직접해야지 라는 말이였다.

세 번째 시도: Native Code 수정

이 마지막 방법만큼은 정말 싫었다. 일단 여러가지 이유가 있지만 나는 IOS 개발을 해본적도 없고,
swift도 최근에 강의보고 겨우 기초만 한 정도인데 심지어 해당 프로젝트는 Object-C로 되있다.
이사님은 다 똑같다고, 다 쉽다고 말씀하시는데 차범근이 축구 다 똑같고 쉽다고 이걸 왜못하냐고 하는말이 생각나는건 왜일까?
열심히 구글링하고 AppDelegate.m 에서 push notification을 받는 pushRegistry를 작성해서 브레이크 포인트를 잡아보니 잘 잡히지도 않거나, 앱이 뻗기 시작한다.
기존에는 FlutterAppDelegate를 상속받아서 여기서 내가 설정한 라이브러리들이 등록되며 네이티브쪽에도 코드들이 연결되어 pushRegistry를 다 처리해주는 것이였는데 내가 억지로 재정의를 해주니까 되던것도 안되기 시작한다...

해결: Native Code 수정(Flutter Library Code)

그 어디서도 해결책을 찾지못하고 아무도 내게 도움을 주지않는 상황에서 마지막으로 시도한것은 이 플러터의 VOIP 라이브러리가 구현한 Native Code를 뜯어보겠다는 것이였다. 그래서 .symlink까지 다 찾아가면서 코드를 트레이싱하기시작했고, 해당 클래스들을 찾아냈다.
VOIPCenter, CallKitCenter등 여러 클래스들이 있었고, 나는 이부분에서 pushRegistry 메서드마다 브레이크 포인트를 잡아가면서 과연 이쪽으로 Push Notification이 오는지 확인한 결과
IOS11 MORE Support 에서 지원한다는 PushRegistry 메서드로 Push가 들어오는것을 확인했다.
그래서 이제 코드를 수정하기 시작했다.
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { print("🎈 VoIP didReceiveIncomingPushWith completion: \(payload.dictionaryPayload)") let info = self.parse(payload: payload) let voipType = info?["type"] as! String let callerName = info?["incoming_caller_name"] as! String if(voipType == "HANGUP"){ self.callKitCenter.unansweredIncomingCall() let content = UNMutableNotificationContent() content.title = "부재중 전화 알람" content.body = "\(callerName) 님에게 부재중 전화가 왔습니다." let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false) let request = UNNotificationRequest(identifier: "unansweredIncomingCall", content: content, trigger: trigger) self.notificationCenter.add(request) { (error) in if let error = error { print("❌ unansweredIncomingCall local notification error: \(error.localizedDescription)") } } return } if(voipType == "CALL"){ self.callKitCenter.incomingCall(uuidString: info?["uuid"] as! String, callerId: info?["incoming_caller_id"] as! String, callerName: callerName) { error in if let error = error { print("❌ reportNewIncomingCall error: \(error.localizedDescription)") return } } self.eventSink?(["event": EventChannel.onDidReceiveIncomingPush.rawValue, "payload": info as Any, "incoming_caller_name": callerName]) completion() } }
Swift
복사
Push Notification의 payload를 parsing하여 내가 보낸 voipType이라는 인자값을 통해 분기를 해서
VOIP 전화화면을 띄울수도 있고, 내릴수도 있도록 했는데, 다행히 성공적으로 동작을했고 LocalNotification을 통해 부재중알람도 떴다.
하지만 아직 다른곳에 배포를 하거나 할때는 이런 코드를 수동으로 넣어주거나 해야하기에 우선, 해당 라이브러리를 직접다운받아서 설정해줄 필요가 있어서 2주차부터 실행할 생각이다.
이런 이슈들을 해결하는 것은 분명 성취감이 있다.
하지만 한편으로는 이런 생각도 든다. 나는 IOS개발자가 아니고 이쪽진로는 생각치도 않는데 이게 과연 얼마나 도움이될까... 그래도 적응력을 키운다는 부분에서 어떻게든 납득을 하며 이번 이슈를 마친다.

ATDD

드디어 NextStep의 ATDD와 함께 클린 API로 가는 길 3기를 시작했다.
미션내용들은 몇일 미리 볼 수 있어서 먼저 봤을때는 사실 너무 어려워보이고, 부담감이 컸다.
내가 이전 TDD 8주 과정을 좀 빠르게 6주차에 마무리 지을수 있었던 이유는
회사일이 여유가 있어서 회사에서도 반나절이상 미션에 집중할때도 많았고,
와이프가 멀리 지방에 있어 한달에 2번 보기도 힘든 상황이라 미션할 시간이 많았고,
친구들도 다 결혼을 해가면서 바빠지니 술약속도 줄어들어 시간이 많았다.
즉, 시간적으로 엄청난 여유가 있는 시기였기에 빠르게 수료를 했었다.
하지만, 이번 ATDD과정에서는
회사가 프로젝트 진행중이라 회사에서 미션을 진행하기 힘들고,
와이프가 신혼집을 구해서 그런지 좀 더 무리해서 매주 오기 시작했고,
독립을 하니 집안일도 내가 직접 다 해야하면서 시간이 더 부족해졌다.
여러모로 조건이 열악해졌는데, ATDD과정은 시간도 8주의 거의 절반에 가까운 5주과정이고, TDD의 다음레벨정도의 과정이래서 부담감이 큰 상태다. 그래도 이번에도 반드시 수료를 하고야 말겠다는 다짐을 해본다.
근데, 사실 과정을 시작하는 날까지도 나는 ATDD가 뭔지 잘 몰랐다. 그냥 TDD정도만 겨우 아는 수준인데, ATDD는 TDD의 다음과정정도라고 생각하라고해서 들은 것인데, 과정을 시작하고나서야 뒤늦게 공부를하고 포스팅을 했다.
2020년 3월 7일 10시 38분기준으로 미션1의 3단계를 첫 PR한 상태인데, 내가 머리가 안좋은지 코드를 작성하는것도 작성하는거지만, 요구사항 이해가 좀 어려워서 헤메이는 경우가 잦은 것 같다.