Load → Decode → Render
590KB의 이미지가 있고, 2048 * 1536의 해상도를 가진다고 가정

Load
- 이미지를 메모리에 로드
- 590KB 파일을 메모리로 불러옴
Decode
- 로딩한 이미지 파일을 GPU가 렌더링 할 수 있는 포맷으로 변환
- 이 과정에서 대략 10MB 크기의 데이터가 됨
- 이미지는 바이너리 데이터이기 때문에 인코딩 된 상태였기 때문에, 디코딩 단계가 필요
Render
- 디코딩 된 이미지를 화면에 렌더링 하는 단계
iOS 이미지 렌더링 과정
iOS에서 네트워크 이미지를 화면에 표시할 때, 단순히 imageView.image = image 한 줄로 끝나는 것처럼 보이지만 실제로는 내부적으로 여러 단계의 렌더링 파이프라인이 실행됩니다.
핵심 흐름은 다음과 같습니다.
Image Load
↓
UIImage 생성
↓
UIImageView에 설정
↓
Layer Tree Commit
↓
Display Preparation (Decode)
↓
GPU Texture Upload
↓
Core Animation Compositing
↓
Framebuffer
↓
Display 출력
이 과정을 코드와 함께 단계별로 살펴보겠습니다.
1. 이미지 다운로드 (압축 데이터 상태)
먼저 네트워크에서 이미지를 다운로드합니다.
let url = URL(string: "https://example.com/photo.jpg")!
URLSession.shared.dataTask(with: url) { data, _, _ in
guard let data else { return }
let image = UIImage(data: data)
DispatchQueue.main.async {
imageView.image = image
}
}.resume()
이 단계에서 메모리에 올라오는 데이터는 다음과 같습니다.
즉 압축된 JPEG / PNG 데이터입니다.
이 시점에는 아직 다음 작업이 수행되지 않았습니다.
- 이미지 디코딩
- GPU 업로드
- 화면 렌더링
2. UIImage 생성
다운로드한 데이터로 UIImage 객체를 생성합니다.
let image = UIImage(data: data)
이 단계는 단순히 이미지 객체를 생성하는 단계입니다.
JPEG / PNG
↓
UIImage 객체
이때 중요한 점은 다음입니다.
이 단계에서 항상 bitmap이 생성되는 것은 아닙니다.
즉 아직 display-ready 상태가 아닐 수 있습니다.
3. UIKit 레이아웃 계산 (Point 좌표)
이미지와 별개로 UIKit은 먼저 레이아웃을 계산합니다.
예를 들어 다음 코드가 있습니다.
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
여기서 100은 Pixel이 아니라 Point입니다.
UIKit은 이 단계에서 다음을 계산합니다.
- View 위치
- View 크기
- AutoLayout 결과
- View hierarchy
즉 이 단계는
4. UIImageView에 이미지 설정
이제 이미지를 화면에 표시합니다.
imageView.image = image
이 코드가 실행되면 내부적으로 다음 일이 일어납니다.
UIImageView
↓
CALayer
↓
layer.contents = CGImage
즉 이미지는 Core Animation Layer에 연결됩니다.
이 코드는 렌더링 요청 코드입니다.
5. RunLoop → Core Animation Commit
UIKit은 즉시 화면을 다시 그리지 않습니다.
다음 RunLoop 사이클에서 Core Animation이 commit을 수행합니다.
imageView.image = image
↓
Layer 상태 변경 기록
↓
RunLoop
↓
CATransaction Commit
이 단계에서 Layer Tree가 렌더링 시스템으로 전달됩니다.
6. Display Preparation (이미지 디코딩)
이미지를 실제로 화면에 표시하려면 압축 이미지를 bitmap으로 변환해야 합니다.
이 과정이 Image Decode입니다.
JPEG / PNG
↓
Decode (CPU)
↓
RGBA Bitmap
예를 들어 다음 3000 × 3000 이미지가 있다고 가정합니다.
디코딩 후 메모리 사용량
3000 × 3000 × 4 bytes
≈ 36MB
이 작업은 CPU에서 수행되는 비용이 큰 작업입니다.
그래서 이미지 디코딩은 스크롤 성능 저하의 주요 원인이 될 수 있습니다.
7. GPU Texture Upload
디코딩된 bitmap은 GPU에서 사용할 수 있도록 Texture로 업로드됩니다.
RGBA Bitmap
↓
GPU Texture
이 단계 이후 GPU가 이미지를 렌더링할 수 있습니다.
8. Point → Pixel 변환
UIKit 좌표는 Point 기반입니다.
예
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
이 값은 100pt입니다.
하지만 디바이스의 screen.scale 값이 3이라면 실제 화면 해상도는 100pt × 3 = 300px 입니다.
즉 렌더링 과정에서 다음 변환이 발생합니다.
Point → Pixel
이 변환은 Layer의 contentsScale을 기준으로 GPU 렌더링 과정에서 처리됩니다.
9. GPU Compositing
GPU는 여러 Layer를 하나의 화면으로 합성합니다.
예
Background Layer
Image Layer
Text Layer
GPU는 다음 작업을 수행합니다.
- transform
- alpha blending
- clipping
- layer compositing
이 과정에서 최종 프레임이 생성됩니다.
10. Framebuffer 생성
GPU 합성 결과는 Framebuffer에 기록됩니다.
예
1170 × 2532 pixel
이 데이터가 실제 화면 출력 데이터입니다.
11. Display 출력
마지막 단계에서는 Display Controller가 Framebuffer 데이터를 읽어 디스플레이에 출력합니다.
Framebuffer
↓
Display Controller
↓
OLED / LCD Pixel
이 단계에서 사용자가 실제 화면을 보게 됩니다.
전체 코드 기준 흐름
정리하면 다음 코드
let image = UIImage(data: data)
imageView.image = image
이후 실제 내부 흐름은 다음과 같습니다.
UIImage 생성
↓
imageView.image = image
↓
CALayer.contents 설정
↓
Layer Tree Commit
↓
Display Preparation (Decode)
↓
GPU Texture Upload
↓
Point → Pixel 변환
↓
GPU Compositing
↓
Framebuffer
↓
Display
핵심 정리
iOS에서 이미지가 화면에 표시되는 과정은 다음과 같습니다.
- 네트워크 또는 번들에서 이미지 데이터가 로드됩니다.
- UIImage 객체가 생성됩니다.
- imageView.image = image로 UIImageView에 이미지가 설정됩니다.
- 다음 RunLoop에서 Core Animation commit이 발생합니다.
- Display Preparation 단계에서 이미지가 bitmap으로 디코딩됩니다.
- bitmap 데이터가 GPU Texture로 업로드됩니다.
- GPU 렌더링 과정에서 Point → Pixel 변환이 적용됩니다.
- Core Animation이 여러 Layer를 GPU에서 합성합니다.
- Framebuffer가 생성됩니다.
- Display Controller가 Pixel 데이터를 화면에 출력합니다.
'iOS' 카테고리의 다른 글
| [iOS] Image: Downsampling (0) | 2025.09.22 |
|---|---|
| [iOS] Image: Scale Factor와 Point, Pixel (0) | 2025.09.19 |
| [iOS] Optimistic UI의 정의 및 적용 사례 (SwiftUI) (2) | 2025.06.08 |
| [iOS] Decimal을 사용해야 하는 이유 (0) | 2025.04.02 |
| [iOS] Module, Library, Framework, Package (0) | 2025.03.16 |