효율적인 개발과 문제 해결에 필수적인 CS 기초 개념을 정리해본다.
CPU, RAM, 저장 장치의 상호작용부터 iOS의 메모리 관리 및 멀티스레딩 기술까지, 각 요소의 원리를 숙지함으로써 더 나은 성능과 안정성을 갖춘 앱을 개발할 수 있다.
1. 컴퓨터 시스템의 주요 구성 요소
컴퓨터 시스템은 CPU, RAM, 저장 장치로 구성된다. 각 구성 요소는 서로 협력하여 데이터를 처리하고 저장한다.
- CPU (Central Processing Unit)
- 컴퓨터의 핵심 연산 장치로, 명령어를 처리하고 산술 및 논리 연산을 수행한다.
- 데이터 처리를 담당하며 RAM에서 데이터를 가져와 연산 후 결과를 저장한다.
- RAM (Random Access Memory)
- 현재 실행 중인 데이터와 프로그램을 임시 저장하는 휘발성 메모리이다.
- CPU가 데이터를 빠르게 읽고 쓰도록 도와주며, 전원이 꺼지면 데이터가 사라진다.
- 저장 장치
- HDD(하드 디스크)나 SSD(솔리드 스테이트 드라이브)는 데이터를 영구적으로 저장한다.
- RAM보다 속도는 느리지만, 전원이 꺼져도 데이터를 보존한다.
CPU와 메모리 간의 데이터 교환
CPU와 메모리는 '버스(Bus)'를 통해 데이터를 주고받는다.
- 데이터 버스: 데이터 전송을 담당.
- 주소 버스: 데이터가 저장된 위치를 전달. CPU가 필요한 메모리 주소를 지정한다.
- 제어 버스: 읽기/쓰기 신호를 전달.
2. 캐시 메모리와 지역성(Locality) 원리
캐시 메모리란?
캐시 메모리는 CPU와 RAM 간의 속도 차이를 줄이기 위한 고속 메모리이다. 자주 사용되는 데이터를 저장해 CPU가 RAM 대신 캐시에서 데이터를 가져올 수 있도록 한다.
캐시의 지역성(Locality)
- 시간 지역성: 최근에 사용된 데이터가 다시 사용될 가능성이 높음.
- 공간 지역성: 현재 참조된 데이터의 인접 데이터가 참조될 가능성이 높음.
3. CPU 아키텍처와 iOS 기기의 AP(Application Processor)
CPU 아키텍처 종류
- ARM
- 저전력 설계로 모바일 기기에 최적화.
- RISC (Reduced Instruction Set Computer)
- 단순하고 제한된 명령어 집합을 사용하여 빠른 실행을 목표로 한다.
- 명령어가 고정된 길이를 가지며, 각 명령어는 하나의 작업을 수행한다.
- 저전력 설계와 높은 효율성으로 모바일 및 임베디드 장치에 적합하다.
- 대표 예시 : ARM 프로세서
- x86
- 고성능 PC와 서버에 적합.
- CISC (Complex Instruction Set Computer)
- 다양한 복잡한 명령어를 제공하여 프로그래밍의 편의성을 높인다.
- 하나의 명령어로 여러 작업을 수행할 수 있어 코드 밀도가 높다.
- 상대적으로 전력 소비가 크고 설계가 복잡하다.
- 대표 예시 : 인텔의 x86 아키텍처
SoC란?
SoC는 한 개의 칩에 여러 컴퓨팅 구성 요소를 통합한 시스템으로, CPU, GPU, RAM 컨트롤러, 네트워크 모듈 등을 단일 칩에 통합한 형태이다. 모바일 기기와 IoT 장치에 최적화되어 있다.
iOS 기기의 AP 특징
AP란 모바일 시스템의 반도체 칩을 말하는데 CPU, GPU, 메모리 컨트롤러, 보안 모듈 등을 통합한 칩이다. AP는 성능과 전력 효율성을 균형 있게 제공한다. (iOS의 SoC > AP)
iOS 운영체제 구조
iOS는 다음과 같은 계층 구조를 갖는다:
- Cocoa Touch: UI와 제스처 처리.
- Media: 그래픽, 오디오, 비디오 처리.
- Core Services: 네트워킹, 데이터 처리.
- Core OS: 하드웨어와 직접 상호작용.

4. iOS의 샌드박스 구조와 보안
샌드박스 구조
샌드박스는 앱이 독립된 실행 환경에서 실행되도록 한다. 다른 앱이나 시스템 파일에 직접 접근하지 못하도록 하여 보안성이 높다.
샌드박스와 커널
샌드박스 구조는 커널을 기반으로 작동하며, 커널은 샌드박스의 보안 정책을 실행하는 핵심 요소이다.
- 보안 정책 시행:
- 커널은 앱 실행 시 보안 정책을 강제 적용하여, 앱이 샌드박스 환경을 벗어나지 못하도록 한다.
- 예를 들어, 앱이 네트워크에 접근하려면 해당 작업이 보안 정책에 따라 허용되어야 하며, 커널이 이를 검증하고 제어한다.
- 시스템 호출 관리:
- 앱이 하드웨어 리소스(카메라, 마이크 등)나 운영체제 서비스(API)에 접근하려면 시스템 호출을 통해 커널과 상호작용한다.
- 커널은 샌드박스 정책에 따라 시스템 호출을 허용하거나 차단한다.
- 격리 및 안정성:
- 커널은 각 앱의 프로세스를 격리하여 다른 앱이나 시스템 전체에 미치는 영향을 최소화한다.
- 앱이 충돌하거나 악성 코드를 실행하려고 할 경우, 커널이 이를 감지하고 격리하여 시스템 안정성을 유지한다.
샌드박스와 커널의 협력 예시
- 파일 접근 제한: 앱이 사용자 파일에 접근하려 하면, 커널은 샌드박스 정책을 확인하고 허용 여부를 결정.
- 네트워크 요청: 앱이 인터넷 연결을 시도하면 커널이 권한을 확인하고 작업을 중개.
- 백그라운드 작업 관리: 샌드박스 정책에 따라 앱이 백그라운드에서 작업을 실행하거나 종료할 수 있도록 커널이 제어.
앱 간 데이터 공유 방법
- URL 스킴 (URL Scheme): 특정 URL을 통해 앱 간 통신을 할 수 있다.
- 앱 그룹 (App Group): 공유 컨테이너를 사용하여, 동일한 개발자가 만든 앱끼리 데이터를 공유할 수 있다.
5. iOS의 다중 태스킹(Multitasking)
iOS는 다중 태스킹을 통해 여러 앱이 동시에 실행될 수 있도록 한다. 백그라운드에서 제한된 작업만 허용해서 배터리 소모를 최소화한다.
백그라운드 작업 유형
- 음악 재생
- 위치 정보 업데이트
- 푸시 알림 처리
핵심 기술
- GCD (Grand Central Dispatch): 효율적인 태스크 관리를 위해 스레드 풀을 활용하는 프레임워크.
-
- 디스패치 큐 (Dispatch Queue):
- Serial Queue: 작업을 순차적으로 실행.
- Concurrent Queue: 작업을 병렬로 실행.
- 메인 큐 (Main Queue): UI 업데이트는 항상 메인 큐에서 실행.
- 디스패치 큐 (Dispatch Queue):
-
- NSOperationQueue: GCD 상위에 구축된 더 높은 수준의 추상화 도구.
* 프로세스와 스레드
차이점
- 프로세스: 독립적인 실행 단위로, 각각의 메모리 공간을 갖는다.
- 스레드: 프로세스 내부에서 실행되는 작업 단위로, 메모리를 공유한다.
iOS에서의 관리
- iOS는 앱 실행 시 단일 프로세스로 동작하고, 여러 스레드를 사용해 병렬 작업을 처리한다.
- GCD를 사용해 작업을 간단히 스케줄링할 수 있다.
* 멀티스레딩이 사용되는 이유
- UI 반응성 유지: 메인 스레드에서 시간이 오래 걸리는 작업을 처리하면 앱이 멈춘 것처럼 보인다.
- 병렬 처리: 작업을 나눠 동시에 실행하면 성능이 향상된다.
6. 메모리 관리 기법과 ARC
ARC는 객체의 참조 카운트를 자동으로 관리하여 메모리를 효율적으로 사용한다. 참조 카운트가 0이 되면 객체가 해제된다.
ARC의 동작 원리
- 객체가 생성되면 '참조 카운트(Reference Count)'가 1로 설정.
- 객체를 참조하는 변수가 증가할 때마다 참조 카운트가 증가.
- 참조가 해제되면 참조 카운트가 감소.
- 참조 카운트가 0이 되면 객체는 메모리에서 해제.
class Example {
var name: String
init(name: String) {
self.name = name
print("\(name) 객체가 생성됨")
}
deinit {
print("\(name) 객체가 해제됨")
}
}
var obj1: Example? = Example(name: "ARC 예제")
obj1 = nil // 참조 카운트가 0이 되어 메모리에서 해제
ARC와 Garbage Collection의 차이
- ARC: 컴파일 시 메모리 관리 코드를 삽입해서 성능이 좋고, 예측이 가능하다.
- Garbage Collection: 런타임에 메모리를 관리하기 때문에 성능에 영향을 줄 수 있다는 단점이 있다.
강한 순환 참조와 약한 참조 예시
class Person {
var name: String
var friend: Person?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 해제됨")
}
}
var john: Person? = Person(name: "John")
var jane: Person? = Person(name: "Jane")
john?.friend = jane
jane?.friend = john
john = nil
jane = nil // 강한 참조 순환으로 인해 메모리 해제되지 않음
class Person {
var name: String
weak var friend: Person?
init(name: String) {
self.name = name
}
deinit {
print("\(name) 해제됨")
}
}
7. iOS의 힙(Heap)과 스택(Stack)
스택(Stack): 함수 호출과 자동 변수 저장
- 저장되는 데이터:
- 함수 호출 시 사용되는 지역 변수.
- 함수 호출의 매개변수와 반환 주소.
- 값 타입(Value Type) 데이터(예: struct, 기본형 데이터 Int, Double 등).
- 특징:
- 스택은 LIFO(Last In, First Out) 구조로 동작한다
- 데이터를 할당하거나 해제하는 속도가 매우 빠르며, 크기가 작다.
- 함수 호출이 끝나면 관련된 메모리가 자동으로 해제된다(수동 관리 불필요).
- 예시 코드에서 a, b, result는 모두 스택에 저장된다.
func calculateSum() {
let a = 10 // 'a'는 스택에 저장됨
let b = 20
let result = a + b
print(result)
}
힙(Heap): 동적 메모리 할당
- 저장되는 데이터:
- 동적으로 생성된 객체.
- 참조 타입(Reference Type) 데이터(예: class).
- 특징:
- 힙은 동적으로 생성된 객체를 저장하는데, 주로 개발자가 명시적으로 메모리를 할당하고 해제해야 했지만, iOS는 'ARC(Automatic Reference Counting)'로 자동 관리한다.
- 메모리 접근 속도는 스택보다 느리지만, 큰 데이터를 저장하거나 객체를 여러 곳에서 공유해야 할 때 적합하다.
- 예시 코드에서 john 객체는 힙에 저장되고, 변수 john은 스택에서 힙 객체를 참조하는 포인터로 동작한다.
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let john = Person(name: "John") // 'john'은 힙에 저장됨
- 예시 코드에서 구조체는 스택에 저장되고, 클래스는 힙에 저장된다.
struct Point {
var x: Int
var y: Int
}
class Circle {
var radius: Double
init(radius: Double) {
self.radius = radius
}
}
let p1 = Point(x: 0, y: 0) // 스택에 저장
let c1 = Circle(radius: 5.0) // 힙에 저장
- 예시 클로저가 함수 내부의 변수(특히 스택에 있는)를 참조하면, 해당 변수가 힙으로 옮겨져 메모리 관리를 받게 된다.
- total은 원래 스택에 저장되지만
- incrementer 클로저가 total을 캡처하면서, total은 힙으로 이동.
- 클로저가 total을 참조하며 값이 업데이트됨.
func makeIncrementer(by amount: Int) -> () -> Int {
var total = 0 // 캡처될 변수 (스택에 위치)
// 클로저가 'total'을 캡처
let incrementer: () -> Int = {
total += amount
return total
}
return incrementer
}
let incrementByFive = makeIncrementer(by: 5)
print(incrementByFive()) // 5
print(incrementByFive()) // 10
'iOS' 카테고리의 다른 글
iOS 개발자가 알아야할 네트워크 관련 개념 정리: TCP/IP, HTTPS, URLSession (0) | 2025.01.11 |
---|