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)로 의도 명확화
주의:
allTokenObjects가 Realm 객체를 직접 담고 있다면 “스레드 구속(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
- Delegate는 반드시
- 컬렉션에 클로저 저장/
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인가? 구형class→AnyObject로 갱신했는가? - 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/GCD와 UIKit 테이블/검색/코디네이터 패턴을 제대로 다루는 것에 있습니다.