[주제]
Android와 iOS의 OS 차이
[목차]
1.커널 & 아키텍처 & 샌드박스
2. LifeCycle & 메모리
3. 파일시스템 / 스토리지 접근
4. 권한(Permission) 시스템
[커널 & 아키텍처 & 샌드박스]
Android - iOS 비교
1.커널&아키텍처
| Android | iOS | |
| 커널 | Linux 커널 기반 - POSIX Message Queue, POSIX Shared Memory, unix domain socket 등 사용 가능 |
XNU 커널 (Mach 마이크로 커널+ BSD 서브시스템 혼합) - POSIX Shared Memory, unix domain socket 등 사용 가능 |
| 개발사 | Google (오픈소스 AOSP) | Apple (클로즈드 소스) |
| 런타임 | ART (Android Runtime)* | Objective-C 런타임 / Swift* |
| 언어 기반 | Java/Kotlin → DEX 바이트코드* | Swift/ObjC → 네이티브 컴파일* |
-----------------------------------------------------------------!!!!!!!!!!!!!!!!!!!!!!!---------------------------------------------------------------------
2. 샌드박스(=앱에게 주어진 격리된 실행 공간)
| Android | iOS | |
| 격리 기준 | ||
| system call 허용 여부 | ||
| 샌드박스 | 앱마다 고유한 UID가 부여됨 * | 커널이 UID 단위로 리소스 접근 통제 * |
격리 기준 UID (신원) system call 종류 (행위)
system call 허용 여부 허용 (호출 가능)
일부 차단 (호출 불가)
e.g. OS 전역 네임스페이스 등록
통제 시점 커널 내부 (객체 접근 시) 커널 진입 전 (Seatbelt)
같은 앱 내부 자유롭게 사용 가능 동일하게 차단
차단 주체 Linux 커널 (UID mismatch) Seatbelt 프로파일
격리 기준과 차단 상세
앱 실행시 Sandbox Profile이 적용
Profile이 허용된 system call 목록을 정의
e.g.
앱 A (UID 10001) → /dev/shm/queue_a (소유자: 10001)
앱 B (UID 10002) → /dev/shm/queue_a 접근 시도
→ 커널: UID 불일치 → 접근 불가능
단, 같은 앱 내부라면 자유롭게 접근 가능
shm_open() 호출 시도
→ Profile 허용 목록에 없어 호출 실패
XNU 커널에서 system call 차단
iOS 앱은 실행 시 Apple이 정의한 Sandbox Profile이 적용되어 특정 system call을 막음
e.g.
shm_open() - 데이터 저장 공간 생성 허용 샌드박스 차단
sem_open() - 동시 접근 제어 허용 샌드박스 차단
shmget() - shm_open()의 구형 방식 허용 샌드박스 차단
→ iOS에서 샌드박스 정책으로 iOS에서 boost::message_queue 사용 불가능
-----------------------------------------------------------------!!!!!!!!!!!!!!!!!!!!!!!---------------------------------------------------------------------
3.IPC 통신
| Android | iOS | |
| 소켓 종류 | boost::message_queue | Unix Domain Socket |
| 연결 실패시 | 큐에 보관 | 연결 전까지 유실 |
| 큐/버퍼 가득 참 | send() 블로킹 | send() 블로킹 |
| 프로세스 종료 시 | 큐 파일 남아있음 | 소켓 파일 삭제 필요 |
+ 프로세스 종료 시
| Android | iOS | |
| 잔존 파일의 역할 | 실제 메시지를 담는 저장소(버퍼) | 단순 연결 주소(엔드포인트) |
| 남아있을 때 영향 | 오히려 유리함 — 재시작 후에도 이전 메시지를 이어받을 수 있음 | 불리함 — 재시작 시 같은 경로 bind() 실패의 원인이 되므로 반드시 삭제 필요 |
- boost::message_queue는 내부적으로 shm_open()으로 공유 메모리 객체를 생성함. 이 공유 메모리는 프로세스 생명주기와 독립적
- UDS는 bind() 호출 시 단순히 주소 역할만 하는 소켓 파일(inode)를 생성. 이전에 생성된 경로에 대해 재시작시 다시 bind()를 시도하면 boost::message_queue와는 다르게 Address already in use 에러가 발생함
이슈
수신 모듈 초기화 되지 않은 상태에서 IPC 통신 동작
iOS에서 수신 모듈이 초기화 되지 않았을 때 IPC 통신시도시 내용이 유실됨
해결
NFC 내부적으로 iOS 환경에서 bForce = true 옵션의 SendEvent 실패시 벡터에 저장
이후 수신 모듈이 켜지면 저장된 이벤트를 재전송 하도록 대응
-> message_queue가 해주던 "알아서 보관해줄께"를 NFC에서 직접 구현한 셈
[LifeCycle & 메모리 관리]
Android - iOS 비교
백그라운드 전환
| Android | iOS | |
| 백그라운드 진입 | onPause → onStop | inactive → background |
| 백그라운드 제한 | 상대적으로 관대 | 엄격한 실행 시간 제한 |
| 백그라운드 작업 | 실행이 보장됨 실행 시간 제한 없음 즉시 실행 조건부로 또는 그냥 가능 |
OS 재량으로 실행됨 실행 시간이 각각 수십 초 ~ 수 분, 30초 이내로 짧음 즉시 실행 불가능 Thread 실행 백그라운드에서도 계속 모든 Thread 중단 |
| 앱 종료 감지 | onDestroy 비교적 신뢰 가능 | 종료 감지 콜백 없음 |
라이프사이클
| Android | iOS |
![]() |
![]() |
| Foreground에서 Background 전환 Resumed → onPaush() → Paused→ onStop() → Stopped Background에서 Foreground 전환 Stopped → onRestart() → Started → onResume() → Resumed |
Foreground에서 Background 전환 Active → (willResignActive) → Inactive → (didEnterBackground) → Background Background에서 Foreground 전환 Background → (willEnterForeground) → Inactive → (didBecomeActive) → Active |
| iOS는 Inactive가 중간 상태로 반드시 거침 iOS의 Suspend는 Android의 Stopped와 비슷하지만, 메모리 부족 시 콜백 없이 강제로 종료됨 iOS는 Android의 onDestroy()에 해당하는 명시적 종료 콜백이 없음 iOS에서 Background → Suspended 전환, Suspended에서 앱 종료를 OS가 전적으로 결정함 |
메모리 관리
| Android | iOS | |
| 메모리 관리 | ART (Android Runtime) - 런타임에 힙을 스캔, 참조되지 않는 객체를 찾아 회수 |
ARC (Automatic Reference Counting) - 컴파일에 컴파일러가 객체 참조/해제 시점마다 retain/release 호출 코드를 자동으로 삽입 |
| 백그라운드 프로세스 | 상대적으로 유지 허용 | 적극적으로 종료 (메모리 부족 시) |
| OOM Killer | Linux OOM Killer 동작* | Jetsam (iOS 전용 메모리 관리 데몬)* |
| 멀티태스킹 | 진정한 멀티태스킹* | 제한적 멀티태스킹 (상태 저장 후 동결)* |
이슈
iOS에서 백그라운드 전환시 프로세스 상태체크 타이머가 중지됨
원인
백그라운드 실행 정책 차이
해결
AppStatus가 Foreground로 변경될 때 체크 시작, Background로 변경될 때 체크 종료 및 TickTime 목록 초기화
[파일시스템 / 스토리지 접근]
| Android | iOS | |
| Overview | ![]() |
![]() |
| 앱 내부 저장소 | 앱 전용 공간으로 UID가 다른 앱은 커널이 접근 차단 - 한 건물(파일 시스템) 안에서 각자 방 열쇠(UID)가 다름 |
앱별로 UUID 컨테이너(Documents, Library, tmp가 존재)가 생성 - 애초에 건물 자체가 앱마다 따로 존재 |
| 외부 저장소 접근 | SD카드, Downloads 폴더 자유 접근 가능 | 불가 — 외부 저장소 개념 자체가 없음 |
| 앱간 파일 공유 | FileProvider가 URI로 변환해 전달 | 반드시 OS가 중개하는 방식만 허용 |
Android 앱간 파일 공유
| A 앱 | FileProvider | B 앱 | ||
| 앱의 파일 경로 data/app.a/ |
-> | content://URI 변환 | -> | URI로 읽기 (직접 경로는 모름) |
iOS 앱간 파일 공유 - 타 앱으로 보내기
| A 앱 | B 앱 | 비고 |
| UIActivityViewController 표시 | ![]() |
|
| 공유 시트에서 B 앱 선택 | ||
| OS가 파일을 B 앱애 전달 | ||
| Share Extension이 파일 수신 |
iOS 앱간 파일 공유 - 타 앱에서 받아오기
| A 앱 | B 앱 | 비고 |
| 파일 공유 요청 | ||
| UIDocumentPicker 표시 | ![]() |
|
| 사용자가 파일 선택 | ||
| OS가 보안 범위 URL 발급 | ||
| URL로 파일 접근 |
[권한(Permission) 시스템]
| Android | iOS | |
| 권한 선언 위치 | AndroidManifest.xml | Info.plist |
| 권한 설치시 자동 부여 | Normal Permission외 자동 부여 (Android Normal Permission 예 INTERNET / BLUETOOTH / SET_ALARM / USE_PINGERPRINT...) |
전부 런타임 부여 |
| 권한 미선언 시 | 런타임 요청 거부 | 앱 크래시 |
| 런타임 재요청 | 가능 | 불가 — Settings 유도만 (Apple은 권한 변경 주도권을 앱이 아닌 사용자에게만 줌 -> iOS에서 권한을 거부하면 denied로 기록되고 해당 권한 관련 다이얼로그 표시를 아예 차단함) |
| 목적 문자열 | 불필요 | 필수 (없으면 크래시) "This app use ~ "가 목적 문자열 Info.plist에 키-값 형태로 선언됨 |





