Swift. Xcode 디버깅과 LLDB 기본 명령어

LLDB가 뭔가요 LLVM + Debugger LLVM 위에서 동작하는 디버거 LLVM(Low Level Virtual Machine) 프로그래밍 언어를 CPU 아키텍처가 이해할 수 있도록 컴파일 하는 과정을 생각해보면 (프로그래밍 언어의 종류 * CPU 아키텍처의 종류) 만큼의 경우의 수가 생긴다. LLVM은 LLVM-IR이라는 중간 언어를 만들어서 프로그래밍 언어를 LLVM-IR로 변역하고 이 LLVM-IR을 CPU 아키텍처에 맞는 기계어로 번역하게 해주는 역할을 함 어려운건 여기까지! XCode Debugging BreakPoint 브레이크 포인트가 걸린 라인 직전 까지 실행 됨 활성화/비활성화 할 수 있음 다양한 옵션 지정 가능 (이름, LLDB 커맨드, 로그 작성 등등)… Debugging Bar Disable/enable breakpoints: 브레이크 포인트들을 활성화/비활성화 Pause/continue execution: 다음 브레이크 포인트가 나올 때 까지 진행 Step over: 다음 라인으로 넘어가기 Step into: 현재 브레이크 포인트가 걸려있는 메소드로 들어가기 Step out: 현재 메소드에서 벗어나기 LLDB 직접 사용해보기 Print: p 현재 메모리 상태에 있는 것을 출력한다. ...

November 20, 2024

SniffMEET. RLS와 테이블 권한

문제 Supabase에서 anon key로 Public 테이블에 접근하면 데이터가 보이는데, 익명 로그인 후 받은 JWT access token으로 접근하면 200 OK만 오고 데이터가 비어 있었다. 처음에는 authenticated role이 anon role보다 상위 권한이라고 생각해서 더 헷갈렸다. 하지만 Supabase에서는 role의 상하관계보다 RLS 정책을 어떻게 작성했는지가 더 중요했다. 확인 JWT가 만료된 경우에는 401 Unauthorized가 발생했다. 반면 RLS 조건에 맞지 않는 경우에는 요청 자체는 성공해서 200 OK가 오지만, row가 필터링되어 데이터가 내려오지 않았다. authenticated 유저가 읽을 수 있도록 다음 policy를 추가하자 데이터를 읽을 수 있었다. ...

November 20, 2024

SniffMEET. JWT와 Supabase RLS 정책에 연결하기

Supabase Auth로 로그인하면 클라이언트는 Access Token으로 JWT를 받는다. Supabase는 요청에 포함된 JWT를 검증하고, 그 안의 사용자 정보와 역할을 RLS 정책에 전달한다. JWT의 클레임 JWT는 헤더, 페이로드, 서명으로 구성된다. 각 부분은 Base64URL로 인코딩되지만 암호화되지는 않으므로 페이로드에 비밀 정보를 넣으면 안 된다. 서명은 헤더와 페이로드가 변조되지 않았는지 검증하는 데 사용한다. Supabase의 JWT에는 다음과 같은 클레임이 포함된다. sub: 사용자의 고유 ID role: RLS를 적용할 PostgreSQL 역할 iat: 토큰이 발급된 시각 exp: 토큰이 만료되는 시각 Supabase에서 JWT 사용하기 RLS는 PostgreSQL이 행 단위로 접근을 제한하는 기능이다. Supabase에서는 auth.uid()로 현재 JWT의 sub 값을 가져와 행의 사용자 ID와 비교할 수 있다. ...

November 20, 2024

SniffMEET. 익명 로그인 세션의 생성·복원·갱신 흐름

익명 로그인 생성, 복원, 갱신 로직 구현 JWT, Refresh Token, 세션의 개념이 아직 명확하게 정리되지 않은 상태에서 익명 계정을 먼저 만들게 됐다. 개념이 명확하지 않아서, 세션 관리에 꽤 애를 먹었는데, 앱 재실행 이후에도 익명 계정을 어떻게 이어서 사용할지부터 정리할 필요가 있었다. 생성, 복원, 갱신 로직은 서로 밀접하게 연결되어 있어서, 처음 앱을 실행했을 때와 다시 실행했을 때의 흐름을 먼저 구분해야 했다. 실제 문제 해결 과정 먼저 시점별로 세션 상태를 표로 정리했다. ...

November 14, 2024

SniffMEET. 여러 VIPER 구현에서 Router 구조 비교하기

VIPER에서 Router가 화면 전환을 담당한다는 원칙은 분명하다. 하지만 현재 화면을 어떻게 참조하는지, 다음 모듈은 누가 만드는지, AppRouter가 반드시 필요한지는 구현마다 달랐다. SniffMEET의 화면 구조를 정하기 전에 여러 VIPER 프로젝트를 비교하며 Router의 책임 범위를 확인했다. 무엇을 확인했나 분석 기준은 다음 세 가지였다. Router가 현재 ViewController를 어떻게 참조하는가 화면 전환 시 다음 VIPER 모듈을 누가 조립하는가 여러 모듈에 걸친 전환을 AppRouter가 담당하는가 구현 비교 구현 모듈 조립 화면 전환 AppRouter iOS-Viper-Architecture WireFrame의 정적 메서드 출발 View를 인자로 전달 없음 ios-architecture 정적 팩토리 메서드 전환 사례가 적음 없음 Viperit 프레임워크가 모듈을 관리 모듈이 직접 자신을 표시 AppModules로 모듈 관리 iOS-Architecture-Sample 모듈별 생성 앱 단위 Router가 화면 계층 관리 싱글톤 AppRouter 구현은 달랐지만 공통점도 있었다. View나 Presenter가 직접 다음 화면을 생성하지 않았고, 화면 조립과 전환을 UI 바깥의 객체로 분리하고 있었다. ...

November 12, 2024

Software Engineering. VIPER 아키텍처

VIPER란 VIPER는 화면을 View, Interactor, Presenter, Entity, Router로 나누는 아키텍처 패턴이다. 각 객체의 역할과 의존 방향을 제한해 UI, 비즈니스 로직, 화면 전환을 분리한다. 구성 요소 View 화면을 그리고 사용자 입력을 Presenter에 전달한다. Presenter가 전달한 값을 UI에 반영하며, 비즈니스 로직은 직접 처리하지 않는다. protocol ExampleViewProtocol: AnyObject { func displayMessage(_ message: String) func displayError(_ message: String) } final class ExampleViewController: UIViewController { var presenter: (any ExamplePresenterProtocol)? override func viewDidLoad() { super.viewDidLoad() presenter?.viewDidLoad() } @IBAction private func buttonTapped(_ sender: UIButton) { presenter?.handleButtonTapped() } } extension ExampleViewController: ExampleViewProtocol { func displayMessage(_ message: String) { label.text = message } func displayError(_ message: String) { errorLabel.text = message } } Presenter View의 입력을 해석해 Interactor나 Router에 작업을 요청한다. Interactor가 반환한 Entity는 화면에 표시할 값으로 가공해 View에 전달한다. ...

November 11, 2024

SniffMEET. UITabBarController VIPER 컨테이너로 다루기

VIPER 모듈을 사용하더라도, 직접 탭 바를 구현하지 않는 이상 UITabBarController는 사용해야 한다. 일반적인 VIPER 화면처럼 View, Presenter, Interactor, Router를 모두 갖춘 독립 모듈로 보기엔 애매한 것 같다. 특히 Interactor는 여기서 아무 의미가 없어보이고, Router는 UITabBarController에 내장되어 있으니까… 모듈 빌더 router, interactor를 생성하고 이를 가지고 있는 presenter를 생성해서 뷰에 집어넣는 방식 HomeModuleBuilder는 Home 화면에 필요한 VIPER 구성 요소를 한 곳에서 조립한다. ViewController를 만들고, Router와 Interactor를 생성한 뒤 Presenter에 주입한다. 마지막으로 NavigationFactory를 통해 화면을 UINavigationController로 감싸서 반환한다. ...

November 11, 2024

DevOps. GitHub Actions 워크플로우

들어가며 GitHub Actions는 저장소에서 발생한 이벤트를 기준으로 빌드, 테스트, 배포 같은 작업을 자동화한다. 자동화 과정은 .github/workflows 디렉토리의 YAML 파일로 정의한다. 처음 workflow를 작성할 때는 name, on, jobs, steps가 각각 무엇을 나타내는지부터 이해할 필요가 있다. 전체 구조 GitHub Actions의 실행 흐름은 다음과 같다. Event -> Workflow -> Job -> Runner -> Step 이 흐름을 YAML로 표현하면 다음과 같다. name: Build and Test run-name: Build triggered by @${{ github.actor }} on: push: branches: - main pull_request: branches: - main workflow_dispatch: jobs: build: runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Build run: echo "Build" test: needs: build runs-on: macos-latest steps: - uses: actions/checkout@v4 - name: Test run: echo "Test" name과 run-name name은 workflow 자체의 이름이다. GitHub 저장소의 Actions 탭에서 workflow를 구분할 때 사용한다. ...

November 5, 2024

UIKit. TableViewCell 스와이프 동작 넣기

기본 메소드들 // 오른쪽에서 왼쪽으로 스와이프 tableView(_:trailingSwipeActionsConfigurationForRowAt:) // 왼쪽에서 오른쪽으로 스와이프 tableView(_:leadingSwipeActionsConfigurationForRowAt:) 구현 순서 1. UIContextualAction을 사용해서 액션 구현 let deleteAction = UIContextualAction(style: .destructive, title: "삭제") { (action, view, completionHandler) in // 삭제 로직 completionHandler(true) } 2. UISwipeActionsConfiguration에 액션 등록 // 액션 여러개 등록 가능 let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) 3. 전체 코드 func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let deleteAction = UIContextualAction(style: .destructive, title: "삭제") { (action, view, completionHandler) in // 삭제 로직 completionHandler(true) } let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) configuration.performsFirstActionWithFullSwipe = false return configuration } 기타 색깔, 이미지 지정 가능 deleteAction.backgroundColor = .systemRed deleteAction.image = UIImage(systemName: "trash") performsFirstActionWithFullSwipe true: 스와이프 하면 첫 번째 액션이 즉시 실행 false: 스와이프 된 상태에서 나타는 버튼을 눌러서 액션 실행 실제 적용 코드 (이슈 트래킹 앱) func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { let deleteAction = makeDeleteAction() let closeAction = makeCloseAction() let configuration = UISwipeActionsConfiguration(actions: [deleteAction, closeAction]) configuration.performsFirstActionWithFullSwipe = false return configuration } private func makeDeleteAction() -> UIContextualAction { let deleteAction = UIContextualAction(style: .destructive, title: nil) { (action, view, completionHandler) in // TODO: 얼럿 띄우고 삭제 로직 completionHandler(true) } deleteAction.image = UIImage(systemName: "trash") return deleteAction } private func makeCloseAction() -> UIContextualAction { let closeAction = UIContextualAction(style: .normal, title: nil) { (action, view, completionHandler) in // TODO: 이슈 닫기 로직 completionHandler(true) } closeAction.image = UIImage(systemName: "archivebox.fill") closeAction.backgroundColor = .systemIndigo return closeAction }

September 20, 2024

UIKit. UIView와 CALayer

UIView는 화면의 직사각형 영역을 나타내고, 그 안에 콘텐츠를 관리하는 객체이다. 대부분의 UI 요소가 UIView를 상속받으며, 화면에 콘텐츠를 보여주고 터치나 제스처를 감지하는 역할을 한다. UIView로 콘텐츠 표시하기 UIView는 UIKit에서 제공하는 메소드나 Core Graphics를 이용해 직사각형 영역에 콘텐츠를 그릴 수 있다. 배경색만 있는 간단한 UIView를 만들어보자. var magentaRectView: UIView = { let magentaRectView = UIView() magentaRectView.backgroundColor = .magenta return magentaRectView }() 오토레이아웃 제약은 임의로 정했다. UIView는 내부에 다른 UIView를 포함할 수 있다. 상위 뷰에 포함된 뷰를 서브 뷰라고 한다. UIViewController의 view 프로퍼티도 UIView 인스턴스이기 때문에, 위 마젠타 사각형도 UIView 내부의 UIView이다. 좀 더 직관적으로 확인하려면, 마젠타 사각형 안에 검은색 사각형을 추가해보자. ...

September 17, 2024