SRC alanysis for CoTi


0. 실행 맥락에서 본 한 줄 분석

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    for token in coordinator?.allTokenObjects ?? [] {
        if token.contractAddress == customTokenAddress {
            coordinator?.mark(token: token, isHidden: false)
            print("\(token.name)")
        }
    }
}

해설

  • GCD: DispatchQueue.main.asyncAfter메인 스레드에서 3초 후 실행(정밀 타이머 아님, UI 스레드 예약).
  • 옵셔널 체이닝/병합: coordinator?.… ?? []nil 안전 처리.
  • 동등성 비교: contractAddress 비교는 Equatable 구현/정규화(소문자, 체크섬) 전제가 필요.
  • UI 스레드 규칙: mark가 UI 업데이트를 유발한다면 메인 스레드에서 안전.
  • 메모리 안전(개선안): DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { [weak coordinator] in guard let coordinator = coordinator else { return } for token in coordinator.allTokenObjects where token.contractAddress == customTokenAddress { coordinator.mark(token: token, isHidden: false) print("\(token.name)") } }
    • [weak coordinator]로 클로저 캡처 순환 방지
    • where로 필터 간결화
    • .seconds(3)로 의도 명확화

주의: allTokenObjectsRealm 객체를 직접 담고 있다면 “스레드 구속(thread confinement)” 규칙 위반 소지가 있습니다. UI 스레드에서 만든 컬렉션이라면 UI 스레드에서만 접근하세요. 다른 스레드에서 쓴다면 ThreadSafeReference 등의 안전 이관이 필요합니다.


1. 언어 기초(이 코드베이스에서 꼭 쓰는 문법)

  • 옵셔널(?, !), 옵셔널 체이닝, nil 병합 연산자(??)
  • 클로저: 캡처 리스트([weak self]/[unowned self]), 탈출(escaping) 클로저, 캡처 시점 주의
  • 프로토콜위임(Delegate): weak var delegate: …? (참조순환 방지), AnyObject 제약
  • 열거형 + 연관값: TransactionType처럼 상태·행위를 타입으로 모델링
  • 제네릭: GeneralTableViewSectionHeader<UISearchBar> 같은 제네릭 뷰/셀
  • Access Control: private, fileprivate, internal로 의존성 최소화
  • lazy var: 뷰 생성 시점 지연, 클로저 내 self 캡처 주의
  • 익스텐션: 역할별 확장(UITableViewDataSource, UISearchResultsUpdating 등)로 가독성 향상

2. 메모리 모델 & ARC

  • ARC 기본 원리: 강한 참조 카운트가 0이 되어야 해제
  • 순환 참조 방지:
    • Delegate는 반드시 weak
    • 비동기 클로저에서 [weak self] 기본, 정말로 non-nil 보장 시 unowned
  • 컬렉션에 클로저 저장/Dispatch 예약 시 화면이 사라져도 클로저가 살아 있을 수 있음 → [weak self] + 조기 리턴 패턴

3. 동시성 & GCD(Grand Central Dispatch)

  • 메인 스레드 규칙: UIKit 접근은 항상 DispatchQueue.main
  • 작업 분배: 무거운 연산은 DispatchQueue.global(qos:)로 돌리고, 결과만 메인으로 복귀
  • 지연 실행: asyncAfter는 타이머 대용이지만 정확도 보장 X, 취소 불가 → 상태 체크로 우회
  • 대안: Swift Concurrency(async/await, Task, Task.sleep) 전환 시 취소 가능 설계 용이

4. 비동기 패턴: PromiseKit & 커스텀 Subscribable

  • PromiseKit:
    • promise.done { ... }.catch { ... }
    • 에러 무시 의도 시 cauterize()
    • UI 갱신은 done 클로저에서 DispatchQueue.main.async 보장 또는 done(on: .main)
  • Subscribable 패턴(간이 Reactive):
    • .subscribe { [weak self] value in … } + unsubscribe로 누수 방지
    • 값이 옵셔널로 올 수 있음 → guard let로 안전 처리

5. 아키텍처: MVC + MVVM + Coordinator 혼합

  • ViewController는 화면 제어(UI 이벤트, 테이블/컬렉션 데이터소스/딜리게이트)
  • ViewModel은 표시 데이터/상태, 필터링/검색/섹션 로직 보유
  • Coordinator는 화면 전환/라우팅/흐름(예: QR 스캐너 공통 진입점)
  • 장점: 의존성 주입 쉬움(예: presentScanner 클로저), 테스트 분리 용이
  • 주의: 비즈니스 로직이 VC로 새지 않게 하고, VM/서비스로 밀어내기

6. UIKit 구성 요소 정복(이 코드에서 쓰는 핵심)

  • UITableView/UICollectionView
    • 셀/헤더 등록·재사용: register, dequeueReusableCell
    • 섹션 헤더: viewForHeaderInSection, 높이 제어
    • 스와이프 액션: trailingSwipeActionsConfigurationForRowAt
    • 성능: 동적 높이 계산, 불필요한 reloadData() 최소화, diff 적용 고려
  • UISearchController
    • searchResultsUpdater, isActive 상태에 따른 UI 전환
    • iOS 13+: obscuresBackgroundDuringPresentation 사용(구 dimsBackgroundDuringPresentation)
  • UIRefreshControl
    • beginRefreshing()/endRefreshing() 호출 타이밍
  • Navigation Bar Items
    • 시스템 심볼(qrcode.viewfinder, doc.text)
    • 좌/우 바버튼 동시 구성, 접근성 라벨/액션
  • 레이아웃
    • NSLayoutConstraint.activate, anchorsConstraint(to:) 헬퍼
    • 안전영역/키보드 회피(KeyboardChecker 같은 유틸)

7. 리소스 & 다국어

  • R.swift: R.string.localizable.*, R.image.*, R.color.*로 타입 안전한 리소스 접근
  • 주의: 문자열 키 변경 시 컴파일 타임에서 오류 감지 → 런타임 크래시 예방

8. 영지식(?) 토큰·주소·네트워크 모델링 팁

  • 주소 비교: 체크섬/대소문자/네트워크 체인ID를 고려한 Equatable
  • 토큰 타입: native/erc20/erc721/erc1155/erc875 등 enum으로 분기
  • 서버 사전: ServerDictionary<WalletSession>처럼 네트워크별 세션 주입
  • 표시 포맷: EtherNumberFormatter로 단위/자릿수 처리

9. 데이터 영속화: RealmSwift를 쓸 때

  • 스레드 구속: Realm 객체는 생성된 스레드에서만 유효
  • 크로스 스레드 이동: ThreadSafeReference 또는 “값 스냅샷”으로 변환 후 전달
  • 라이프사이클: Notification 토큰 해제, 객체가 해제되기 전에 invalidate() 주의
  • 컬렉션: Results는 lazy → UI 갱신 전 스냅샷(Array)로 고정하는 전략 고려

10. 권한/보안/프라이버시

  • 카메라 권한: NSCameraUsageDescription 필수, 권한 흐름(iOS 설정 이동)
  • URL 처리: wc:, ethereum:, http(s):// 딥링크 허용/검증
  • 클립보드: 사용자 알림(복사 토스트/alert) 제공

11. 에러 처리 & 복구력

  • 사용자 친화 메시지: ErrorView/LoadingView/EmptyView 삼종세트
  • PromiseKit: 체인의 마지막에 catch 또는 cauterize
  • 경계 방어: 네트워크·권한·서버 세션 nil 시 빠른 리턴/플로우 중단

12. 성능·안정성 체크리스트

  • 메인 스레드 블로킹 금지: JSON 파싱, 이미지 디코딩 등은 백그라운드
  • 컬렉션뷰 깜빡임 최소화: 실제 데이터 변경 시에만 reloadData()
  • 메모리: 셀에서 강한 캡처 지양, 이미지 캐싱, 대형 뷰의 재사용
  • 로깅: debugPrint는 개발 빌드에서만, 운영은 경량 로거 + 샘플링

13. Swift Concurrency로의 점진 전환 가이드(선택)

  • 브리지: PromiseKit → async/await로 감싸기 (withCheckedThrowingContinuation)
  • 취소 지원: Task + try Task.checkCancellation()
  • 메인 액터: @MainActor로 UI 메서드 보호

14. 테스트 전략

  • 단위: ViewModel 로직(필터/검색/정렬), 주소 비교, 포맷터
  • UI: 스냅샷 테스트(섹션 헤더/셀 레이아웃)
  • 흐름: Coordinator 라우팅 테스트(의존성 주입으로 프리젠터 더블 사용)
  • 권한/딥링크: 카메라 거부/허용, wc:/ethereum: 파서 테스트

15. 코드 리뷰 체크리스트(요약)

  • UI 접근은 메인 스레드인가?
  • 비동기 클로저 [weak self]/조기 리턴 처리 되어 있는가?
  • Realm 객체 스레드 규칙 위반 없나?
  • 주소 비교/체인 구분이 명확한가?
  • 사용자 피드백(에러/로딩/빈 상태) 제공되는가?
  • 리소스는 R.swift로 타입 안전 접근하는가?
  • 델리게이트는 weak인가? 구형 classAnyObject로 갱신했는가?
  • iOS 13 API 변화(obscuresBackgroundDuringPresentation) 반영했는가?

16. 미세 팁(현 코드베이스 특화)

  • UISearchController의 헤더 재부착 타이밍 이슈는 0.1초 지연 후 세그먼트 선택 재설정으로 애니메이션 깨짐 방지(이미 코드에 구현).
  • 컬렉션뷰 첫 로딩 시 “잘못된 카운트” 방지를 위해 필터 상태별 방어적 카운트 반환(이미 구현).
  • navigationItem.leftBarButtonItem?.debugInfo() 같은 디버깅 유틸은 릴리즈에서 제거 또는 가드.
  • TokenViewControllerDelegate: class → **AnyObject**로 현대화 권장.

부록 A. asyncAfter 사용시 대체/보완 패턴

  • 상태 조건 대기가 목적이면 폴링보다는 “발행-구독(Subscribe)”로 전환(토큰 목록 변경 이벤트에서 처리)
  • 정확한 지연/취소가 필요하면 Task.sleep + Task 취소 지원
  • 뷰 생명주기와 연동: viewDidDisappear에서 플래그 세우고 클로저 내부에서 검사

부록 B. 안전한 리팩토링 예시

func activateToken(after delay: TimeInterval = 3,
                   address: AlphaWallet.Address,
                   using coordinator: TokensCoordinatorType) {
    DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak coordinator] in
        guard let coordinator = coordinator else { return }
        guard let token = coordinator.allTokenObjects.first(where: { $0.contractAddress == address }) else { return }
        coordinator.mark(token: token, isHidden: false)
        print("✅ 토큰 활성화 완료: \(token.name)")
    }
}
  • 의존성(딜레이, 주소, 코디네이터) 주입
  • 단일 책임 + 테스트 용이

핵심은 옵셔널/클로저/ARC/GCDUIKit 테이블/검색/코디네이터 패턴을 제대로 다루는 것에 있습니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다