Skip to content

나만의 링크달을 저장하고 관리하세요

Notifications You must be signed in to change notification settings

ModuTeam/Public_iOS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

링크모아

나만의 링크달을 저장하고 관리하세요.


프로젝트 소개

링크모아는 소중한 링크들을 쉽게 찾아보고 정리할 수 있는 서비스입니다.
다양한 주제에 맞게 링크를 정리하고 공유함으로써 지식을 쉽게 공유할 수 있습니다.


iOS 개발 팀원

허원 🧑‍💻 권범철 🧑‍💻
@heoblitz @Kimbaechu

프레임 워크

  • UIKit
  • SwiftUI
  • WidgetKit

라이브러리

  • Moya ( 네트워크 레이어 )
  • Kanna ( meta data 파싱 )
  • Kingfisher ( 이미지 캐싱 )
  • Toast-Swift ( alert )
  • RxSwift
  • SwiftLint

아키텍처

  • MVVM

데이터 바인딩 방식

  • RxSwift

도입배경 복잡한 비동기 처리를 쉽게 처리한다는 장점

MVVM에 적합한 처리방식


협업 방식

git-flow ( 코드 리뷰를 거친 PR 만 develop 브랜치에 반영 )

앱 배포시에는 별도의 release 브랜치를 생성함 ( ex. release/1.0.1 )


목차



기능

시연 영상



설계

폴더 구조

폴더 구조는 Context ( 주요 기능 ) 으로 구분했고,
내부에서 MVVM 를 각각 나누었음



모델 설계

문자열 데이터 중 종류를 나타내는 값은 오타 등의 문제를 방지하지 위해 enum으로 표현

struct UsersFolder: Codable {
    let isSuccess: Bool
    ...

    struct Result: Codable {
        let index: Int
				...
        let type: FolderType
        ...

        enum CodingKeys: String, CodingKey {
            case index = "folderIdx"
            ...
        }
    }
    
    enum FolderType: String, Codable {
        case privateFolder = "private"
        case publicFolder = "public"
    }
}

뷰모델 설계

ViewModel의 Input과 Output을 구분하여 사용.
Input은 Replay가 필요없고 state보다 event가 어울리기 때문에 Signal을 사용함 Output은 UI에 필요하고 state의 성격이 필요하기 때문에 Driver를 주로 사

final class SurfingViewModel: ViewModelType {

    struct Input {
        let fetchTopTenFolders: Signal<Void>
        let fetchLikedFolders: Signal<Void>
    }
    
    struct Output {
        var sections: Driver<[SurfingSectionModel]>
        var errorMessage: Signal<String>
    }
    
    private let networkManager = RxSurfingManager()
    private let disposeBag = DisposeBag()
    private let errorMessage: PublishRelay<String> = PublishRelay()
    
    func transform(input: Input) -> Output {
       ...
        return Output(sections: sections.asDriver(), errorMessage: errorMessage.asSignal())
    }
}


문제 해결

share extension memory issue ( 허원 )

  • 문제점
    사파리에서 공유 버튼을 누르고, 폴더 선택 VC 로 접근하면 앱이 죽는 문제
    share extension 의 메모리 제한은 대략 15MB 정도

  • 해결
    GIF 기능을 제한하는 별도의 파라미터를 update 메서드에 추가함
    메인 앱에서는 GIF 를 지원하고, share extension 에서는 예외 처리 하도록 구성


custom bottom alert view ( 허원 )

  • 문제점
    UX 특성 상 아래에서 올라오는 custom bottom alert 가 많았음.
    기존에는 사용되는 VC 마다 present 메서드를 일일히 추가했었는데, 코드 중복성이 매우 증가되었음.

  • 해결
    extension 으로 메서드를 모든 VC 에 추가하는 방식은 비효율적이라고 판단함
    따라서 최대한 POP 방식을 충족하기 위해 Where Self 구문을 사용하였음.
    CustomAlert 라는 Protocol 을 구현하였고, 필요한 VC 만 채택하여 사용할 수 있도록 함


hide category View ( 범철 )

  • 문제점
    스크롤을 내리면 카테고리가 사라지고 스크롤 위치에 상관없이 스크롤을 올리면 바로 카테고리가 나타나는 구조
    스크롤 뷰를 최상단과 최하단에서는 contentSize보다 더 올리고 내릴 수 있어서 예상하지 못한 레이아웃 깨짐 현상 발생

  • 해결
    컨텐츠 높이와 스크롤뷰 높이, 컨텐츠오프셋을 log로 확인하면서 해결방법을 찾음
    컨텐츠높이 + 인셋 > 스크롤뷰높이 일 때만 제약이 바뀌도록 함


Infinite Scroll Reload ( 범철 )

  • 문제점
    스크롤이 바닥에 도착하면 다음 데이터를 받아오는 구조
    마지막 페이지에서 스크롤 바닥에 도착하면 api를 여러번 호출하는 문제 발생

  • 해결
    한 번에 20개씩 페이징처리하는데 이보다 적은 데이터를 받아올 경우
    마지막 페이지이므로 더이상 api 호출하지 않게 변경



배운 점

iOS 개발의 협업 방식을 배웠고, 좋은 코드를 위해 같이 고민해가는 경험을 할 수 있었음 (허원)

  • 구조, viewModel 구현 방식은 다른 뛰어난 프로젝트, 포스팅을 많이 참고했음 ( kickstarter, 카카오 개발자님 블로그 등)
  • 코드 리뷰 과정을 통해 더 나은 코드를 작성할 수 있도록 같이 고민해보고 Apple 코딩 컨벤션을 충족하려 노력했음
    ex ) let dict: [String : String] let dict: [String: String] 👍

내가 맡은 파트보다 프로젝트 전체를 고려하는 개발을 하게 되었음 (범철)

  • 협업을 하면서 담당 파트가 아닌 부분도 검토하면서 중복을 줄임
  • bottom alert view의 경우 약간의 UI와 로직만 변경하면 재사용할 수 있음
  • 최초 동료가 만들어놓은 코드를 재사용이 가능하도록 리팩토링해서 재사용함

About

나만의 링크달을 저장하고 관리하세요

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published