Texture, 현업 사용가이드
안녕하세요 저는 RxSwift를 즐겨사용하며, Texture 커뮤니티에서 활동중인 Vingle iOS개발자 Geektree0101입니다.
한해동안 저에겐 많은 일들이 있었는데요.
- Vingle 4th Tech Talk에서 Texture를 이용한 Newsfeed 성능 개선 발표
- Let’s us go에서 Texture와 RxSwift를 활용한 Reactive wapper만들기 발표
위와 같은 발표를 가졌고 덕분에 곳곳의 유명한 국내 기업들이 도입을 시도하고 있다는 점이 참 놀랍기도 하고 기쁘기도 했습니다.
A UI Framework for Effortless Responsiveness
_Texture_texturegroup.org
이번에 다룰 주제는 Texture를 실제 현업에서 잘 사용하는 법! 에 대해서 적어볼까 합니다.
Getting Started
_Texture_texturegroup.org
Texture는 위와 같이 기본적인 가이드 문서도 있지만 기술적으로 어떻게 쓰는지만 적혀있기 때문에 이걸 현업에 도입할 때 어떤 걸 고려해야하고 어떤 식으로 기초를 튼튼하게 설계해야할지 지난 몇 년간 항상 고민을 했었습니다.
그래서 Vingle iOS team 동료 들과 주로 쓰는 RxSwift와 같은 유명한 라이브러리에 대한 호환성과 기존 UIKit base로 프로덕션을 개발할 때와 Texture를 사용했을 때의 생산성 비교와 같은 다양한 실험과 연구를 했었습니다.
이러한 부분에 희생해주신 Vingle iOS 팀원 분들과 인내를 가져주신 디자인 및 기획팀분들께 감사의 말씀을 드리고 싶습니다.
이 글의 독자들 대상은 다음과 같습니다.
- Texture를 현업에 도입하고 싶으신 분들
- RxSwift를 같이 사용하고 있으신 분들
- Storyboard나 xib 의 constraints 에 대한 유지보수 및 코드리뷰, 개발에 피로를 느끼시는 분들
- Texture 기본적인 내용 간단히 읽어 보신 분들
- UI가 관리가 안되시는 분들
- 코드로만 앱 개발하고 싶고 코드 적게 기능 많이 작성하고 싶은 분들
- 기획팀의 요구량은 엄청 많은데 개발인원이 적은 회사
- UIKit에서 제공해서 기본적인 기능들은 학습하신 분
위에 해당하지 않으시다면
Texture Best Practice
_Keeps the most complex iOS user interfaces smooth and responsive._medium.com
위의 글을 한번 읽어 보시기를 권장드립니다.
< 순서 >
- UI의 모듈화
- UI Tracking
- Pagination
- RxSwift & RxCocoa
- Unit Test
1. UI의 모듈화
여러분들도 아시다 싶이 Vingle은 SNS Application입니다. 생각보다 많은 UI를 가지고 있는데요. 예를 들어 User Profile, User List, Talk List, Talk Show, Q&A List, Q&A Show, Answer List, Interest, Community, Community Desking, Sign In/up, My Interest List, My Talk List, Card Show, Card List etc…….
이렇게 많은데 이 모든 서비스를 3명이서 운영합니다. 이 모든 기능들중 90%가 Texture로 작성되어 있고 약 5~6종 이상의 Major 서비스와 20종 이상에 가까운 UI 컴포넌트들이 불과 몇 달도 안되서 Texture로 Migration 되었습니다.
이거 작업한다고 야근은 정말 서밋날 제외하면 2~3번 밖에 안될 정도로 손에 꼽을 정도 였던거 같습니다.
이렇게 빠르게 Migration 할 수 있었던 비결은 Texture에서 만든 UI를 모듈화 잘한것도 있지만 기존 Xib나 Storyboard, Autolayout으로 만든 UI에 비해 빠르게 이식 및 재사용 할 수 있는 부분이 가장 큰 장점 이였습니다.
대표적인 예를 하나 들어보겠습니다.
Vingle의 Council 에 대한 공용 모듈을 가져와봤습니다.
보시면 각각의 CellView들이 비즈니스에 따라서 서로 다른 부분도 있지만 공통적인 부분도 있습니다. 저희는 이러한 공통적인 부분을 하나의 모듈로 만듭니다.
좀 더 자세히 들어야 보면 다음과 같습니다.
비즈니스 로직에 따라서 변화하거나 교체 및 유무에 따라 변화하는 요소를 모듈화 시키고 다른 서비스에서도 사용할 수 있도록 모듈화 하는 겁니다.
(유저 프로필도 하나의 모듈이라 보시면 됩니다.)
만약 xib로 작업한다 하면 view를 붙여주고 view의 class type을 교체해주고 적용전에 있던 view들의 constraints와 priority 를 확인하면서 붙여야되는 번거로움이 있습니다.
물론 snapKit도 예외는 아닙니다. 정말 명시적으로 코드를 적지 않는 이상 makeConstraints 을 하나하나 체크해줘야하는 부분도 있습니다.
하지만 Texture는 오로지 코드로만 작성하고 어디를 수정해야하고 어디까지 모듈화 시킬수 있는지 확실한 부분이 바로
layoutSpecThatFits method입니다.
저희는 UI Layout을 잡을 때 오로지 layoutSpecThatFits에 심혈을 기울입니다.
물론 저 method에 다 때려박진 않습니다. method로 목적에 맞게 명시적인 method를 작성해서 layoutSpec을 설계합니다.
하단 주황색 영역(Council Extension Component Area)와 붉은색 영역(Council IndicatorArea)을 method로 나타내면 위와 같습니다.
이렇게 만든 method를 공용모듈을 subclass화 시켜 override해주면 간단히 끝이 납니다.
물론 다른 모듈을 가져와서 갖다 붙일 수 있겠죠
- UI의 layout은 오로지 layoutSpecThatFits: 에서 집중한다.
- addSubview이런거 필요없습니다. automaticallyManageSubnodes = true 한번이면 끝!
- UIKit에서 제공해주는 기본적인 Property 당연 접근 가능합니다. 예외가 있으면 node의 view property로 접근해서 사용하면됩니다. (MainThread 주의)
2. UI Tracking
기획자들이 말씀하시기를, “유저 리스트에서 유저 프로필 기준으로 UI가 보일 때 쯤에 시간을 측정을 시작해서 디바이스 상에서 사라질 때 시간을 계산해주세요.”
여러분들은 어떻게 sub-components에 대해서도 독립적으로 Tracking하시습니까?
물론 방법은 다양합니다. scrollViewDidScroll:에서 contain rect으로 처리하는 방법, willDisplayCellView와 같은 UIKit method사용 등등 많지만
변수나 상황이 많습니다. 예를 들어 Background로 갔다가 다시 Foregraound로 올 수도 있고, 디바이스상에 UI는 보이지만 해당 UI를 통해서 다른 스크린으로 present/push됬다 다시 돌아왔을 때 등등…
Texture는 매우 간단한 방법으로 Tracking을 할 수 있습니다.
Intelligent Preloading
_Texture_texturegroup.org
Intelligent preloading이라는 기능을 제공해주는데 기본적인 용도와 다르게 Visible 상태에 대해서 확실히 Tracking을 할 수 있는 점입니다.
여기서 상태가 총 3가지고 didEnter / didExit까지 포함하면 Intelelligent preloading 은 총 6가지의 편의용 method를 제공해줍니다.
각각 method들은 특정 구간에 들어갈 때만 한번 Trigger되며 상태가 변할 때마다 각 state에 맞는 method가 호출이 됩니다.
더 놀라운건 list뿐만 아니라 single screen도 동일한 동작이 적용된다는 점!
저희는 이러한 편의용 메서드와 필요에 따라선 scrollViewDidScroll을 같이 섞어서 사용하기도 합니다. 따라서 저희는 이러한 방식으로 Tracking 함으로 써 기획팀의 요구를 충족시켜주고 있습니다.
3. Pagination
UITableView와 UICollectionView(방향 관계없이) 가 붙은 서비스는 크게 세가지로 나눠질 껍니다. 페이징이 없는 거(설정 페이지), 단방향 페이징(뉴스피드), 양방향 페이징(메시지 서비스)
저는 단방향, 양방향중 단방향 기준으로 설명하겠습니다.
양방향은 다음 링크를 참고해주세요.
How to pre-append ASCellNode like a Chat Application
_How to pre-append ASCellNode like a Chat Application?_medium.com
ASTableNode(UITableView)와 ASCollectionNode(UICollectionView)에서 페이징을 설계할 땐 다음과 같은 순서로 설계를 합니다.
- leadingScreensForBatching (기본값: 2)를 설정해주는데, 이는 list의 끝을 기준으로 디바이스 화면 기준으로 설정된 값만큼 떨어져있는 만큼의 리스트를 더 불러오기 위한 기본값입니다.
- ASTableDelegate or ASCollectionDelegate shouldBatchFetchFor(Table/Collection)Node 를 사용하자!, 저희같은 경우 loading상태 empty, end 상태 등등과 같이 리스트의 상태가 구분되기 때문에 위의 3가지 상태를 제외한 경우에서 batch fetch해라고 boolean값을 true로 리턴해줍니다. 이 method를 작성하지 않으면 default가 false때문에 paging이 되질 않습니다.
- will begin batch with context
tableNode(tableNode: ASTableNode, willBeginBatchFetchWithContext context: ASBatchContext
tableNode를 기준으로 설명하면 역시 앞서 말한 delegete에선 위와 같은 method를 제공해줍니다. 여기서 list를 더 불러오는 api를 호출 해주면 됩니다. 단 완료시 context는 complateBatchFetch를 호출해주어야 합니다.
context.completeBatchFetching(true)
http://texturegroup.org/docs/batch-fetching-api.html 더 자세한건 여기
위의 세가지만 잘 지킨다면 기본적인 Paging service구축이 완성이 됩니다.
기존 UIKit로 fetching하는거와 차이점이라면 scrollView에서 batch fetch시점을 일일히 계산할 필요가 없다는 점입니다. 그리고 상태에 따른 betch trigger를 컨트롤 하는데 있어서 간단합니다.
4. RxSwift & RxCocoa
Texture는 기존 UIKit와 다르게 Rx를 같이 사용하면 생각보다 많은 효과를 볼 수 있습니다. 특히 ASCellNode(UITableCellView, UICollectionCellView)에서 가장 큰 힘을 발휘하는데요.
바로 disposeBag을 reuse에서 dispose를 끊어주고 다시 연결해서 subscribe or bind해줘야하는 번거로움이 없어집니다. 또한 rx logic에 따라서 cell를 유연하게 높이와 layout구조를 별도 height계산없이 자유롭게 변경할 수도 있다는 점입니다.
실질적으로 tableView를 구성할때는
- tableView.register
- tableView.dequeueReusableCell
- rx쓰면 reuse시점에서 disposeBag을 끊어주고 다시 bind….
이런 과정을 거치지만
Texture는
- 그냥 cell을 반환시켜준다
- cell init시점에서 bind해준다
이렇게 끝이 난다는 겁니다.
하지만 RxCocoa에서 제공해주는 rx warpper를 쓰기위해선 view property를 접근하는 방법도 있지만 저는 이러한 부분보단 직접적으로 node에 대한 wrapper가 있으면 더 좋겠다생각해서 아래와 같은 라이브러리를 만들어서 RxSwiftCommunity에 기부했습니다.
RxSwiftCommunity/RxCocoa-Texture
_RxCocoa Extension Library for Texture. Contribute to RxSwiftCommunity/RxCocoa-Texture development by creating an…_github.com
자세한건 위의 repo README.md를 읽어주시면 되겠습니다.
따라서 Rx를 쓰는데도 번거로운 과정을 많이 줄여주기 때문에 Texture와 Rx를 같이 섞어씀과 동시에 생산성이 부쩍늘어난걸 경험할 수가 있었습니다.
위에 언급한 내용 이외에는 나머지는 여러분들이 생각하시는 그대로 쓰면되겠습니다.
5. Unit Test
테스트, 정말 중요합니다. 여러분들이 아시는 정도로 해도 충분합니다. 제가 이번에 보여드릴 부분은 Texture의 경우 어떤 요소를 테스트하면 가장 좋을까에 대해서 예기하자면 어떤 UI의 layout이 기대하는 layout으로 변화하는지 또는 반환하는지 정도, 기대하는 모듈이 붙었는지 정도는 테스트하는 것도 괜찮았던거 같습니다.
다시 한번 더 재탕하겠습니다. 육수는 여러면 재탕해야 우러나는법
위에서 테스트 할 수 있는거 간단히 하나 잡아보면 하단에 있는 주황색영역, Council Extension Components Area을 반환하는지 정도의 간단한 테스트 코드를 보여주고자 합니다.
공용 모듈을 사용하는 서브 모듈은 총 4가지이미 각각에 1개 내지 2개 정도 UI가 분기되는 것을 볼수 있습니다. 저는 간단히 반환하는게 있는지 정도 보기 위해서 nil인지 아닌지만 비교한 테스트 코드를 작성해보았습니다.
이렇게 코드를 작성했을 때 얻을 수 있는 건, 개발자가 기존 공용모듈을 제대로 재사용하고 있는지에 대해서 확실한 검증을 할 수 있는 부분입니다.
이 뿐만 아니라 node가 subnode에 붙어 있는지, 색상 이런 자잘한건 Optional입니다.
- UI의 모듈화
- UI Tracking
- Pagination
- RxSwift & RxCocoa
- Unit Test
지금까지 위의 5가지를 소개해봤는데요. 이 5가지를 저희 팀원 들이 잘지키고 학습을 바탕으로 migration을 여유있게 할 수 있었지 않았나 싶습니다.
결론은 코드를 보다 많이 적게 쓰고 일관성 있고 체계적으로 UI개발과 동시에 우수한 퍼포먼스를 통한 사용자 UX까지 챙길 수 있는것이 핵심입니다.
https://geektree0101.github.io/ 해당 사이트에 오시면 관련된 프로젝트를 쉽게 확인하실 수가 있으며
궁금하신 점은 https://asyncdisplaykit.slack.com/messages/C0V63R86T/ Texture Community Slack 에 DM 또는 general에 남겨주시면 감사하겠습니다.
[채용 공고] Vingle에서 함께 성장할 동료를 찾고 있습니다
_관심사로 세상을 잇다! 관심사 기반 SNS, Vingle_careers.vingle.net