24.07.31 Today I Learned

2024. 7. 31. 20:19

Xcode Instruments 란?

  • Xcode에서 제공하는 앱의 성능 분석 및 디버깅 도구
  • 앱 개발자라면 메모리 릭이 나지 않는 앱을 개발해야하므로, 메로리 릭에 대한 분석을 할 때 Xcode Instruments를 활용하면 좋음
  • Xcode Instruments를 통해서 성능 분석, 메모리 릭 분석을 할 수 있음
  • 메모리 릭 분석을 하면서 스택 트레이스(Stack Trace)도 함께 확인 가능

Stack Trace

: Stack을 Trace(추적)한다.

코드를 실행하면 스택에 쌓이면서 코드를 수행하게 됨. 이를 콜 스택이라고함. 코드를 콜 한 순서를 알 수 있는 스택. 이 스택을 뒤돌아보며 코드가 수행된 기록을 훑어보는 것을 스택 트레이스라고 함

 

프로파일링

: 앱의 성능을 분석하고 최적화하는 과정을 프로파일링이라고 함

 

코드 작성

메모리 누수가 나는 상황은 순환 참조가 나는 상황이므로 ClassA <-> ClassB 가 서로를 순환 참조 하도록 설정함.

 

그리고  Xcode 상단에 Product - Profile - Leaks를 누르면 아래 이미지처럼 나오는데

Instruments

 버튼을 Tap했을 때 메모리 누수가 발생하고 아래 순환하는 듯한 이미지를 보여줌

Call Tree

Call Tree로 설정하고 하단 탭에 있는 Call Tree에서  Invert Call Tree, Hide System Libraries 를 체크해주면 Call Tree를 좀더 보기 쉽게 해줌

 

Reveal in Xcode

이걸 누르면 어디서 메모리 누수가 나는지 볼 수 있음(해당 파일 위치로 이동해서 해당 컨트롤러에서 메모리 누수가 난다는걸 알려줌)


프로세스와 스레드란?

프로세스와 스레드는 모두 컴퓨터에서 작업을 수행하는 흐름 단위. 실행중인 iOS 앱을 프로세스라고 부를 수 있으며, 프로세스 내부에는 여러 개의 스레드가 존재함.

프로세스 : 실행중인 프로그램을 프로세스라고 함

  • 실행하기 위해 메모리 위에 올라온 프로그램을 프로세스라고 함
  • iOS앱 개발을 했을 떄의 결과물인 App도 iOS운영체제에 속한 프로그램임
  • iOS앱을 실행하면 프로세스라고 부를 수 있음

스레드 : 프로세스 내에서 작업을 수행하는 단위

  • 한 개의 프로세스 내에서 여러개의 스레드가 동시에 작업을 수행할 수 있음
  • 즉, 실행중인 iOS앱을 프로세스라고 표현하므로 iOS앱이 실행 중일때 여러가지 스레드가 동시에 일을함
  • 크게 메인 스레드와 백그라운드 스레드로 구분할 수 있음
  • 여러 개의 스레드를 가지고 동시에 작업을 하는 것을 멀티 스레딩이라고 함

예를 들면, 레스토랑으로 비유를 하면, 

  • 프로세스 : 레스토랑
  • 스레드 : 요리사

레스토랑 자체는 "고객에게 음식을 제공한다" 라는 큰 흐름의 작업을 수행하고, 음식을 제공하기 위해서는 여러 명의 요리사가 동시에 다른 작업을 수행한다로 예시를 들 수 있다.

  • 요리사1 : 재료 손질
  • 요리사2 : 요리
  • 요리사3 : 디저트 제공 등

 

유튜브로 예시를 들면,

  • 프로그램 : 앱에 설치된 유튜브 앱
  • 프로세스 : 유투브 앱을 실행시킨 상태
  • 스레드 : 유튜브 앱을 실행하며 필요한 여러가지 단위 작업들
    • 메인 스레드 : 아이폰에 유튜브 UI를 그리는 작업
    • 스레드 1 : 유저의 구독 목록을 불러오는 작업
    • 스레드 2 : 서버에서 동영상 정보를 불러오는 작업 등

동기, 비동기

 

동기(sync)

: 어떤 스레드에서 작업 A를 처리하다가 새로운 작업 B가 들어오면,

그 작업 B를 수행하고 완료할 때까지 기다렸다가 작업 A를 수행하는 작업처리방식

-> 직렬적으로 작업을 수행

 

비동기(async)

: 어떤 스레드에서 작업 A를 처리하다 새로운 작업 B가 들어오면, 다른 스레드에 작업 B를 넘기고 수행하고 있던 작업 A는 멈추지 않고 병렬적으로 동시에 수행하는 작업 처리 방식. 작업 B가 완료되면 완료되었다는 결과와 소식을 전해받음

-> 병렬적으로 작업을 수행

 

예를 들어 어떤 스레드에서 UI 를 그리는 작업을 하다가, 네트워크 통신을 해서 어떤 데이터를 받는 작업이 새로 들어왔다고 가정해봅시다.

 

동기적으로 작업 수행

: UI 를 그리다가 멈추고, 네트워크 통신 작업을 한 뒤 서버에서 결과가 도착했을 때 다시 UI 그리는 작업 시작.

 

비동기적으로 작업 수행

: UI 를 그리다가, 네트워크 통신 작업이 들어오면 다른 스레드에 작업을 넘기고 UI 작업은 이어서 계속 수행. 서버 결과가 돌아왔을 때 응답을 돌려받음.

 

앱에서 처리해야할 여러가지 작업들을 모두 하나의 스레드에서 동기적으로 처리한다면, 앱의 성능이 매우 떨어지게 될 것입니다. 따라서 개발자는 반드시 동기/비동기 개념과 스레드의 개념에 대해서 이해해야 합니다.


DispatchQueue, GCD 개념

:애플 공식문서에서 Grand Central Dispatch (GCD) 는 동시성 프로그래밍을 돕는 프레임워크라고 명시하고 있음. iOS, macOS, watchOS 등등에 사용할 수 있는 동시성 프레임 워크. 동시성 프로그래밍이란 여러가지 일을 동시에 작업하도록 하는 프로그래밍을 의미. 그 중에서 Swift 에서 사용하는 GCD 기술로 Dispatch Queue 가 있음. 즉 GCD 안에 DispatchQueue 가 포함되는 개념.

즉, 위에서 배웠던대로 여러 가지의 스레드를 가지고 멀티 스레딩 작업을 하며, 비동기적으로 여러가지 작업을 수행하기 위해서는 Dispatch Queue 를 사용하면 됨.

https://developer.apple.com/documentation/DISPATCH

 

Dispatch | Apple Developer Documentation

Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.

developer.apple.com

 

DispatchQueue

  • Dispatch : 보내다, 발송하다
  • Queue : 큐. 자료구조 중 큐를 의미

Queue의 개념

  • 여러가지 자료구조 중 하나로, 먼저 들어온 데이터가 먼저 나간다는 특징을 가짐 (선입선출)
  • 우선순위 큐는 데이터가 나가는 우선순위 규칙을 부여하고, 우선순위에 따른 순서대로 데이터를 내보냄
  • 쉽게 생각하면 데이터를 집어넣고, 내부 규칙에 따른 순서대로 데이터를 내보내는 자료구조

→ 과거 프로그래밍에서는 멀티 스레딩, 비동기 작업을 하기 위해서는 개발자가 직접 스레드를 생성하고, 명시하고, 어떤 스레드에 작업을 할당해줄지 스스로 판단한 뒤 넘겨줘야 해야했는데, 이는 복잡하고 어려우며 개발자가 할 일이 많았음.

 

→ 이를 해결하는 시스템이 DispatchQueue 다. 개발자는 작업이 생겼을 때 그저 Dispatch Queue 에 작업을 넘겨줌. 그러면 운영체제가 DispatchQueue 에 들어간 작업들을 살피며 적절한 스레드에 할당하게 되니, 개발자의 수고가 덜어졌다.

 

즉, DispatchQueue어떤 스레드로 작업을 보내기 위한 Queue 를 의미한다. 

개발자가 작업을 DispatchQueue 에 보내면, 알아서 적절한 스레드를 찾아 작업을 할당시킨다.

 

// DispatchQueue 문법

DispatchQueue.{큐종류}.{qos옵션}.{sync/async} {
		// 수행할 작업 코드 작성
}
// 큐 종류: Main / Global / Custom
// qos: Quality Of Service
// sync: 동기적으로 작업 수행
// async: 비동기적으로 작업 수행

 

DispatchQueue 종류

1. 메인 큐

  • UI 업데이트와 관련된 작업을 처리
  • 앱의 메인 스레드에서 실행
  • Serial Queue.(직렬 큐)

2. 글로벌 큐

  • 백그라운드 작업을 처리
  • Concurrent Queue. (동시성 큐)

3. 커스텀 큐

  • 개발자가 직접 생성하고 관리하는 큐
  • 직렬 또는 동시성 큐로 설정 가능

Serial(직렬) vs Concurrent(동시성)

  • Serial Queue (직렬 큐)
    • 들어온 작업들은 한 가지 쓰레드에 모두 보내는 큐
    • 큐에 추가된 작업을 한 번에 하나씩 순서대로 실행하는 큐
    • 쓰레드 하나에 모든 작업을 할당하기 때문에 작업 완료 순서가 보장됨
  • Concurrent Queue (동시성 큐)
    • 큐에 추가된 여러 작업을 동시에 실행할 수 있는 큐
    • 여러 스레드에서 작업이 동시에 병렬적으로 실행 가능
    • 작업의 완료 순서는 보장되지 않음

DispatchQueue 사용법

 

MainQueue 사용

DispatchQueue.main.async {
    // UI 업데이트 코드
    self.label.text = "작업 완료!"
}

 

GlobalQueue 사용

DispatchQueue.global().async {
    // 네트워크 통신 또는 계산이 무거운 작업을 백그라운드에서 수행
    let result = self.someHeavyComputation()
    
    DispatchQueue.main.async {
        // 결과를 메인 스레드에서 UI에 반영
        self.updateUI(with: result)
    }
}

 

Custom Queue 사용

// label 에 큐의 고유한 이름 설정.
// attributes 에 serial/concurrent 설정.
// 설정하지 않으면 default 값은 serial.
let customQueue = DispatchQueue(
		label: "com.myapp.customqueue", 
		attributes: .concurrent
)

customQueue.async {
    // 커스텀 큐에서 실행할 작업
}

Qos (Quality Of Service)

작업의 중요도를 시스템에 알려주는 방법.

이를 통해 시스템은 리소스(CPU 시간, 에너지 등)를 효율적으로 분배할 수 있다.

이 작업이 어느정도의 중요도인지를 OS 에게 알리고 작업을 할당하는데 도움을 준다고 이해 가능.

  1. User Interactive (최고 우선순위)
    • 설명: 지금 당장 해야하는 작업
    • 예시: UI 업데이트, 애니메이션 등
    • 특징: 매우 빠르게 처리되어야 하는 작업
  2. User Initiated (높은 우선순위)
    • 설명: 사용자가 기다리고 있다는 것을 알림
    • 예시: 버튼 클릭 후 데이터 로딩
    • 특징: 몇 초 안에 완료되어야 하는 작업
  3. Default (기본 우선순위)
    • 설명: 평범한 작업
    • 예시: 일반적인 백그라운드 작업
    • 특징: 특별한 지정이 없을 때 사용됨
  4. Utility (낮은 우선순위)
    • 설명: 천천히 해도 되는 작업
    • 예시: 데이터 다운로드, 백업
    • 특징: 시간이 걸리지만 즉시 필요하지 않은 작업
  5. Background (최저 우선순위)
    • 설명: 언제 끝나도 상관없는 작업
    • 예시: 대용량 데이터 정리, 동기화
    • 특징: 사용자가 직접적으로 인지하지 못하는 작업
  6. Unspecified (시스템 결정)
    • 설명: 어떤 우선순위로 처리해도 상관없는 작업
    • 특징: 시스템이 알아서 우선순위 결정

예제 코드

 

SerialQueue + sync + async 예제 코드

import Foundation

let serialQueue = DispatchQueue(label: "com.myapp.myqueue")

serialQueue.sync {
    print("Task 1 started")
    Thread.sleep(forTimeInterval: 2)
    print("Task 1 finished")
}

print("hello 1")

serialQueue.async {
    print("Task 2 started")
    Thread.sleep(forTimeInterval: 2)
    print("Task 2 finished")
}

print("hello 2")

serialQueue.async {
    print("Task 3 started")
    Thread.sleep(forTimeInterval: 2)
    print("Task 3 finished")
}
// print 출력이 찍히는 순서를 확인하기

 

ConcurrentQueue + sync + async 예제 코드

import Foundation

let concurrentQueue = DispatchQueue(
    label: "com.myapp.myqueue",
    attributes: .concurrent
)

// DispatchQueue 에 작업을 보내는데 sync 한 작업을 보낸다.
concurrentQueue.sync {
    print("Task 1 started")
    Thread.sleep(forTimeInterval: 5)
    print("Task 1 finished")
}

// DispatchQueue 에 작업을 보내는데 async 한 작업을 보낸다.
// 즉 이 코드 블럭 아래의 작업은 곧바로 수행된다.
concurrentQueue.async {
    print("Task 2 started")
    Thread.sleep(forTimeInterval: 4)
    print("Task 2 finished")
}

concurrentQueue.sync {
    print("Task 3 started")
    Thread.sleep(forTimeInterval: 3)
    print("Task 3 finished")
}

 

ConcurrentQueue + async + qos 예제 코드

import Foundation

let concurrentQueue = DispatchQueue(
    label: "com.myapp.myqueue",
    attributes: .concurrent
)

concurrentQueue.sync {
    print("Task 1 started")
    Thread.sleep(forTimeInterval: 2)
    print("Task 1 finished")
}

// qos: background
concurrentQueue.async(qos: .background) {
    print("Task background started")
    Thread.sleep(forTimeInterval: 2)
    print("Task background finished")
}

// qos: userInteractive
concurrentQueue.async(qos: .userInteractive) {
    print("Task userInteractive started")
    Thread.sleep(forTimeInterval: 2)
    print("Task userInteractive finished")
}

// qos: utility
concurrentQueue.async(qos: .utility) {
    print("Task utility started")
    Thread.sleep(forTimeInterval: 2)
    print("Task utility finished")
}
// print 출력이 찍히는 순서를 확인하기

동기 비동기 추가공부!

동기 : 작업이 순차적으로 실행되는 방식.

// 예시코드

func fetchData() {
    print("Start fetching data...")
    
    // 동기 작업: 데이터를 가져오는 함수 (예를 들어, 네트워크 요청)
    let data = synchronousNetworkRequest() 
    
    print("Data fetched: \(data)")
    print("Finished fetching data.")
}

fetchData()

// 이 경우, synchronousNetworkRequest() 함수가 실행이 끝날 때까지 프로그램은 대기하고, 그 후에야 다음 줄이 실행됨

 

비동기 : 작업이 병렬로 실행될 수 있는 방식. 한 작업이 완료되기 전에 다른 작업이 시작될 수 있고, 작업이 완료될 때까지 대기하지 않고 다른 작업을 계속해서 실행할 수 있음

// 예시

func fetchData() {
    print("Start fetching data...")
    
    // 비동기 작업: 데이터를 가져오는 함수 (예를 들어, 네트워크 요청)
    asynchronousNetworkRequest { data in
        print("Data fetched: \(data)")
    }
    
    print("Finished fetching data.")
}

fetchData()

/* 여기서 asynchronousNetworkRequest 함수는 비동기적으로 동작하며, 
네트워크 요청이 완료되면 콜백을 통해 데이터를 처리한다. 
print("Finished fetching data.")는 네트워크 요청이 끝나기 전에 출력될 수 있다. 
*/

https://leedoseo.tistory.com/159

 

 

5. 반복문

1. for - in 반복문: 컬렉션, 범위, 문자열, 시퀀스 등의 항목을 순회할 때 사용// 구문for item in collection { // 반복할 코드}// 배열 순회let numbers = [1, 2, 3, 4, 5]for number in numbers { print(number)}// 범위 순회for

leedoseo.tistory.com

https://leedoseo.tistory.com/160

 

6. 옵셔널

옵셔널: 값이 있을 수도 있고 없을 수도 있음을 나타내는 데이터 타입. 옵셔널을 사용하면 값이 없음을 nil로 표현할 수 있다. 옵셔널은 안전하게 값을 처리하고, 값이 없는 경우를 명확하게 나타

leedoseo.tistory.com

 

https://github.com/Leedoseo/CodingTest/blob/main/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4/0/120804.%E2%80%85%EB%91%90%E2%80%85%EC%88%98%EC%9D%98%E2%80%85%EA%B3%B1/%EB%91%90%E2%80%85%EC%88%98%EC%9D%98%E2%80%85%EA%B3%B1.swift

 

'Today I Learned > 2024' 카테고리의 다른 글

24.08.02 Today I Learned  (0) 2024.08.05
24.08.01 Today I Learned  (0) 2024.08.01
24.07.30 Today I Learned  (0) 2024.07.30
24.07.29 Today I Learned  (0) 2024.07.29
24.07.26 Today I Learned  (0) 2024.07.26

BELATED ARTICLES

more