iOS 최적화
앱인토스 Unity 게임을 iOS 환경에서 최적의 성능으로 실행하기 위한 특화된 최적화 기법과 설정을 제공해요.
1. iOS 성능 특성 이해
iOS vs Android 성능 차이점
🍎 iOS 성능 특성
├── 메모리 관리
│ ├── 더 엄격한 메모리 제한
│ ├── ARC (Automatic Reference Counting)
│ └── 메모리 압박 시 강제 종료
├── GPU 성능
│ ├── Metal API 최적화
│ ├── 타일 기반 렌더링
│ └── A-시리즈 칩셋 특성
├── 배터리 관리
│ ├── iOS 전력 관리 시스템
│ ├── 백그라운드 앱 제한
│ └── 열 관리 (Thermal Management)
└── 앱 스토어 가이드라인
├── 64비트 필수
├── 메모리 사용량 제한
└── 성능 기준 준수iOS 기기별 성능 분류
c#
public enum iOSDeviceTier
{
HighEnd, // iPhone 13+ (A15 Bionic+)
MidHigh, // iPhone 11-12 (A13-A14 Bionic)
MidRange, // iPhone X-XS (A11-A12 Bionic)
LowEnd // iPhone 8 이하 (A10 이하)
}
public class iOSDeviceDetector : MonoBehaviour
{
public static iOSDeviceTier GetCurrentDeviceTier()
{
#if UNITY_IOS
string deviceModel = SystemInfo.deviceModel;
// A15+ (iPhone 13+)
if (deviceModel.Contains("iPhone14") || deviceModel.Contains("iPhone15"))
{
return iOSDeviceTier.HighEnd;
}
// A13-A14 (iPhone 11-12)
else if (deviceModel.Contains("iPhone12") || deviceModel.Contains("iPhone13"))
{
return iOSDeviceTier.MidHigh;
}
// A11-A12 (iPhone X-XS)
else if (deviceModel.Contains("iPhone10") || deviceModel.Contains("iPhone11"))
{
return iOSDeviceTier.MidRange;
}
// A10 이하
else
{
return iOSDeviceTier.LowEnd;
}
#else
return iOSDeviceTier.MidRange;
#endif
}
}2. Metal 렌더링 최적화
Metal API 활용
c#
public class iOSMetalOptimizer : MonoBehaviour
{
[Header("Metal 설정")]
public bool enableMetalOptimization = true;
public bool useMetalPerformanceShaders = true;
public bool enableGPUInstancing = true;
[Header("렌더링 최적화")]
public bool useTiledRendering = true;
public bool enableOcclusionCulling = true;
public int maxRenderTargets = 4;
void Start()
{
#if UNITY_IOS
if (enableMetalOptimization)
{
OptimizeForMetal();
}
#endif
}
void OptimizeForMetal()
{
// Metal 전용 설정 적용
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.Metal)
{
// GPU 인스턴싱 활성화
if (enableGPUInstancing)
{
EnableGPUInstancing();
}
// 타일 기반 렌더링 최적화
if (useTiledRendering)
{
OptimizeTiledRendering();
}
// Metal Performance Shaders 사용
if (useMetalPerformanceShaders)
{
EnableMetalPerformanceShaders();
}
Debug.Log("iOS Metal 최적화 적용 완료");
}
}
void EnableGPUInstancing()
{
// GPU 인스턴싱을 통한 드로우콜 감소
var renderers = FindObjectsOfType<MeshRenderer>();
foreach (var renderer in renderers)
{
var materials = renderer.materials;
foreach (var material in materials)
{
if (material.shader.name.Contains("Standard"))
{
material.EnableKeyword("_GPU_INSTANCING");
}
}
}
}
void OptimizeTiledRendering()
{
// iOS 타일 기반 렌더링에 최적화
QualitySettings.antiAliasing = 0; // MSAA 비활성화 (타일 렌더링과 충돌)
// 렌더 타겟 크기 최적화
Screen.SetResolution(Screen.width, Screen.height, true);
// 후처리 효과 최적화
var postProcessVolumes = FindObjectsOfType<UnityEngine.Rendering.PostProcessing.PostProcessVolume>();
foreach (var volume in postProcessVolumes)
{
OptimizePostProcessProfile(volume);
}
}
void OptimizePostProcessProfile(UnityEngine.Rendering.PostProcessing.PostProcessVolume volume)
{
if (volume.profile == null) return;
var deviceTier = iOSDeviceDetector.GetCurrentDeviceTier();
// 기기 등급에 따른 후처리 설정 조정
switch (deviceTier)
{
case iOSDeviceTier.HighEnd:
// 고사양: 모든 효과 활성화
break;
case iOSDeviceTier.MidHigh:
// 중고사양: 일부 효과 품질 조정
DisableExpensiveEffects(volume, false);
break;
case iOSDeviceTier.MidRange:
// 중사양: 기본 효과만 유지
DisableExpensiveEffects(volume, true);
break;
case iOSDeviceTier.LowEnd:
// 저사양: 후처리 비활성화
volume.enabled = false;
break;
}
}
void DisableExpensiveEffects(UnityEngine.Rendering.PostProcessing.PostProcessVolume volume, bool aggressive)
{
var profile = volume.profile;
// 비용이 높은 효과들 비활성화
if (profile.TryGetSettings(out UnityEngine.Rendering.PostProcessing.Bloom bloom))
{
bloom.enabled.value = !aggressive;
}
if (profile.TryGetSettings(out UnityEngine.Rendering.PostProcessing.DepthOfField dof))
{
dof.enabled.value = false; // iOS에서 성능 영향 큰 효과
}
if (profile.TryGetSettings(out UnityEngine.Rendering.PostProcessing.MotionBlur motionBlur))
{
motionBlur.enabled.value = false;
}
if (profile.TryGetSettings(out UnityEngine.Rendering.PostProcessing.ScreenSpaceReflections ssr))
{
ssr.enabled.value = false;
}
}
void EnableMetalPerformanceShaders()
{
// Metal Performance Shaders 활용
// 이미지 처리, 머신러닝 등에 GPU 가속 사용
Debug.Log("Metal Performance Shaders 활성화");
// 앱인토스에 Metal 최적화 적용 알림
AppsInToss.ReportOptimizationApplied("metal_performance_shaders", "iOS Metal API 최적화");
}
}3. iOS 메모리 관리
ARC 호환 메모리 관리
c#
public class iOSMemoryManager : MonoBehaviour
{
[Header("iOS 메모리 설정")]
public long maxMemoryUsageMB = 180; // iOS 권장 제한 (Android보다 낮음)
public bool enableAutoMemoryCleanup = true;
public float memoryCheckInterval = 5f;
[Header("메모리 압박 대응")]
public bool enableMemoryWarningResponse = true;
public bool autoReduceQualityOnWarning = true;
private long lastMemoryUsage = 0;
private int memoryWarningCount = 0;
void Start()
{
#if UNITY_IOS
InitializeiOSMemoryManagement();
#endif
}
void InitializeiOSMemoryManagement()
{
// iOS 메모리 경고 이벤트 등록
Application.lowMemory += OnLowMemoryWarning;
// 정기적인 메모리 체크
if (enableAutoMemoryCleanup)
{
InvokeRepeating(nameof(CheckMemoryUsage), memoryCheckInterval, memoryCheckInterval);
}
Debug.Log("iOS 메모리 관리 시스템 초기화");
}
void CheckMemoryUsage()
{
long currentMemory = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemory(false);
long currentMemoryMB = currentMemory / (1024 * 1024);
// 메모리 사용량 급증 감지
if (lastMemoryUsage > 0)
{
long memoryDelta = currentMemory - lastMemoryUsage;
long deltaMB = memoryDelta / (1024 * 1024);
if (deltaMB > 10) // 10MB 이상 급증
{
Debug.LogWarning($"메모리 사용량 급증 감지: +{deltaMB}MB");
TriggerMemoryCleanup();
}
}
lastMemoryUsage = currentMemory;
// iOS 메모리 제한 체크
if (currentMemoryMB > maxMemoryUsageMB)
{
Debug.LogWarning($"iOS 메모리 사용량 초과: {currentMemoryMB}MB > {maxMemoryUsageMB}MB");
TriggerAggressiveMemoryCleanup();
}
// 앱인토스에 메모리 사용량 리포트
AppsInToss.ReportMemoryUsage(currentMemoryMB, "iOS");
}
void OnLowMemoryWarning()
{
memoryWarningCount++;
Debug.LogWarning($"iOS 메모리 경고 #{memoryWarningCount}");
if (enableMemoryWarningResponse)
{
RespondToMemoryWarning();
}
// 앱인토스에 메모리 경고 리포트
AppsInToss.ReportPerformanceIssue("ios_memory_warning", memoryWarningCount);
}
void RespondToMemoryWarning()
{
// 즉시 메모리 정리
TriggerAggressiveMemoryCleanup();
// 품질 설정 자동 조정
if (autoReduceQualityOnWarning)
{
ReduceQualitySettings();
}
// 백그라운드 작업 중단
StopBackgroundTasks();
}
void TriggerMemoryCleanup()
{
// 가벼운 메모리 정리
System.GC.Collect();
// 사용하지 않는 에셋 언로드
Resources.UnloadUnusedAssets();
Debug.Log("iOS 메모리 정리 수행");
}
void TriggerAggressiveMemoryCleanup()
{
// 적극적인 메모리 정리
System.GC.Collect();
System.GC.WaitForPendingFinalizers();
System.GC.Collect();
// 모든 사용하지 않는 에셋 언로드
Resources.UnloadUnusedAssets();
// 에셋 번들 캐시 정리
if (AppsInTossAssetBundleManager.Instance != null)
{
AppsInTossAssetBundleManager.Instance.UnloadUnusedBundles();
}
// 텍스처 캐시 정리
Caching.ClearCache();
Debug.Log("iOS 적극적 메모리 정리 수행");
}
void ReduceQualitySettings()
{
var deviceTier = iOSDeviceDetector.GetCurrentDeviceTier();
// 기기 등급을 한 단계 낮춤
switch (deviceTier)
{
case iOSDeviceTier.HighEnd:
ApplyQualitySettings(iOSDeviceTier.MidHigh);
break;
case iOSDeviceTier.MidHigh:
ApplyQualitySettings(iOSDeviceTier.MidRange);
break;
case iOSDeviceTier.MidRange:
ApplyQualitySettings(iOSDeviceTier.LowEnd);
break;
case iOSDeviceTier.LowEnd:
ApplyMinimalQualitySettings();
break;
}
Debug.Log($"메모리 경고로 인한 품질 설정 하향: {deviceTier}");
}
void ApplyQualitySettings(iOSDeviceTier targetTier)
{
switch (targetTier)
{
case iOSDeviceTier.HighEnd:
QualitySettings.masterTextureLimit = 0;
QualitySettings.shadowDistance = 150f;
Application.targetFrameRate = 60;
break;
case iOSDeviceTier.MidHigh:
QualitySettings.masterTextureLimit = 1;
QualitySettings.shadowDistance = 100f;
Application.targetFrameRate = 60;
break;
case iOSDeviceTier.MidRange:
QualitySettings.masterTextureLimit = 1;
QualitySettings.shadowDistance = 50f;
Application.targetFrameRate = 30;
break;
case iOSDeviceTier.LowEnd:
QualitySettings.masterTextureLimit = 2;
QualitySettings.shadowDistance = 25f;
Application.targetFrameRate = 30;
break;
}
}
void ApplyMinimalQualitySettings()
{
// 최소 품질 설정 (생존 모드)
QualitySettings.masterTextureLimit = 3;
QualitySettings.shadowDistance = 0f;
QualitySettings.shadows = ShadowQuality.Disable;
QualitySettings.antiAliasing = 0;
Application.targetFrameRate = 20;
Debug.Log("최소 품질 설정 적용");
}
void StopBackgroundTasks()
{
// 백그라운드 로딩 중단
if (AppsInTossLoadingManager.Instance != null)
{
AppsInTossLoadingManager.Instance.PauseBackgroundLoading();
}
// 불필요한 업데이트 중단
var backgroundUpdaters = FindObjectsOfType<MonoBehaviour>()
.Where(mb => mb.GetType().Name.Contains("Background") ||
mb.GetType().Name.Contains("Update"))
.ToArray();
foreach (var updater in backgroundUpdaters)
{
updater.enabled = false;
}
Debug.Log("백그라운드 작업 중단");
}
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
// 백그라운드 진입 시 메모리 정리
TriggerMemoryCleanup();
// iOS 백그라운드 제한에 대비한 최소화
MinimizeMemoryFootprint();
}
}
void MinimizeMemoryFootprint()
{
// 백그라운드에서 메모리 사용량 최소화
// 오디오 중단
AudioListener.pause = true;
// 애니메이션 중단
var animators = FindObjectsOfType<Animator>();
foreach (var animator in animators)
{
animator.enabled = false;
}
// 파티클 시스템 중단
var particleSystems = FindObjectsOfType<ParticleSystem>();
foreach (var ps in particleSystems)
{
ps.Pause();
}
Debug.Log("iOS 백그라운드 메모리 최소화 적용");
}
void OnApplicationFocus(bool hasFocus)
{
if (hasFocus)
{
// 포그라운드 복귀 시 시스템 복원
RestoreFromBackground();
}
}
void RestoreFromBackground()
{
// 백그라운드에서 복귀 시 시스템 복원
AudioListener.pause = false;
var animators = FindObjectsOfType<Animator>();
foreach (var animator in animators)
{
animator.enabled = true;
}
var particleSystems = FindObjectsOfType<ParticleSystem>();
foreach (var ps in particleSystems)
{
ps.Play();
}
Debug.Log("iOS 포그라운드 복귀 - 시스템 복원");
}
}4. iOS 배터리 최적화
전력 효율성 개선
c#
public class iOSBatteryOptimizer : MonoBehaviour
{
[Header("배터리 최적화 설정")]
public bool enableBatteryOptimization = true;
public bool adaptiveFrameRate = true;
public bool thermalManagement = true;
[Header("전력 임계값")]
[Range(0f, 1f)]
public float lowBatteryThreshold = 0.2f; // 20%
[Range(0f, 1f)]
public float criticalBatteryThreshold = 0.1f; // 10%
private float lastBatteryLevel = 1f;
private bool isLowPowerMode = false;
void Start()
{
#if UNITY_IOS
InitializeBatteryOptimization();
#endif
}
void InitializeBatteryOptimization()
{
if (enableBatteryOptimization)
{
InvokeRepeating(nameof(MonitorBatteryStatus), 30f, 30f); // 30초마다 체크
if (thermalManagement)
{
InvokeRepeating(nameof(CheckThermalState), 60f, 60f); // 1분마다 체크
}
}
Debug.Log("iOS 배터리 최적화 시스템 초기화");
}
void MonitorBatteryStatus()
{
float currentBattery = SystemInfo.batteryLevel;
if (currentBattery < 0) return; // 배터리 정보 없음
// 배터리 수준에 따른 최적화
if (currentBattery <= criticalBatteryThreshold && !isLowPowerMode)
{
EnableCriticalPowerMode();
}
else if (currentBattery <= lowBatteryThreshold && !isLowPowerMode)
{
EnableLowPowerMode();
}
else if (currentBattery > lowBatteryThreshold + 0.1f && isLowPowerMode)
{
DisableLowPowerMode();
}
// 배터리 소모율 계산
if (lastBatteryLevel > 0)
{
float batteryDrain = lastBatteryLevel - currentBattery;
if (batteryDrain > 0.02f) // 2% 이상 급격한 소모
{
Debug.LogWarning($"배터리 급속 소모 감지: -{batteryDrain * 100:F1}%");
OptimizePowerConsumption();
}
}
lastBatteryLevel = currentBattery;
// 앱인토스에 배터리 상태 리포트
AppsInToss.ReportBatteryStatus(currentBattery, isLowPowerMode);
}
void EnableLowPowerMode()
{
isLowPowerMode = true;
// 프레임레이트 제한
Application.targetFrameRate = 20;
// 품질 설정 하향
QualitySettings.masterTextureLimit = 2;
QualitySettings.shadowDistance = 25f;
QualitySettings.shadows = ShadowQuality.HardOnly;
// 파티클 효과 감소
ReduceParticleEffects(0.5f);
// 백그라운드 업데이트 빈도 감소
ReduceUpdateFrequency();
Debug.Log("iOS 저전력 모드 활성화");
AppsInToss.ReportOptimizationApplied("low_power_mode", "iOS 배터리 절약 모드");
}
void EnableCriticalPowerMode()
{
isLowPowerMode = true;
// 극도로 제한적인 설정
Application.targetFrameRate = 15;
// 최소 품질
QualitySettings.masterTextureLimit = 3;
QualitySettings.shadowDistance = 0f;
QualitySettings.shadows = ShadowQuality.Disable;
QualitySettings.antiAliasing = 0;
// 효과 대부분 비활성화
DisableNonEssentialEffects();
Debug.LogWarning("iOS 긴급 절전 모드 활성화");
AppsInToss.ReportOptimizationApplied("critical_power_mode", "iOS 긴급 배터리 절약");
}
void DisableLowPowerMode()
{
isLowPowerMode = false;
// 정상 설정 복원
var deviceTier = iOSDeviceDetector.GetCurrentDeviceTier();
RestoreNormalSettings(deviceTier);
Debug.Log("iOS 저전력 모드 해제");
}
void RestoreNormalSettings(iOSDeviceTier deviceTier)
{
switch (deviceTier)
{
case iOSDeviceTier.HighEnd:
Application.targetFrameRate = 60;
QualitySettings.masterTextureLimit = 0;
QualitySettings.shadowDistance = 150f;
break;
case iOSDeviceTier.MidHigh:
Application.targetFrameRate = 60;
QualitySettings.masterTextureLimit = 1;
QualitySettings.shadowDistance = 100f;
break;
case iOSDeviceTier.MidRange:
Application.targetFrameRate = 30;
QualitySettings.masterTextureLimit = 1;
QualitySettings.shadowDistance = 50f;
break;
case iOSDeviceTier.LowEnd:
Application.targetFrameRate = 30;
QualitySettings.masterTextureLimit = 2;
QualitySettings.shadowDistance = 25f;
break;
}
// 효과 복원
RestoreEffects();
}
void CheckThermalState()
{
// iOS 열 상태 체크 (앱인토스 네이티브 API 사용)
string thermalState = AppsInToss.GetThermalState();
switch (thermalState.ToLower())
{
case "normal":
// 정상 상태 - 추가 조치 불필요
break;
case "fair":
// 약간 따뜻함 - 경미한 최적화
OptimizeForThermal(ThermalLevel.Fair);
break;
case "serious":
// 뜨거움 - 적극적 최적화
OptimizeForThermal(ThermalLevel.Serious);
break;
case "critical":
// 과열 - 긴급 최적화
OptimizeForThermal(ThermalLevel.Critical);
break;
}
}
enum ThermalLevel
{
Normal,
Fair,
Serious,
Critical
}
void OptimizeForThermal(ThermalLevel level)
{
Debug.LogWarning($"iOS 열 관리 최적화 적용: {level}");
switch (level)
{
case ThermalLevel.Fair:
// 경미한 최적화
Application.targetFrameRate = Mathf.Min(Application.targetFrameRate, 45);
QualitySettings.lodBias = 0.8f;
break;
case ThermalLevel.Serious:
// 적극적 최적화
Application.targetFrameRate = 30;
QualitySettings.masterTextureLimit = Mathf.Max(QualitySettings.masterTextureLimit, 1);
QualitySettings.shadowDistance *= 0.7f;
ReduceParticleEffects(0.7f);
break;
case ThermalLevel.Critical:
// 긴급 최적화
Application.targetFrameRate = 20;
QualitySettings.masterTextureLimit = 2;
QualitySettings.shadowDistance *= 0.5f;
QualitySettings.shadows = ShadowQuality.Disable;
DisableNonEssentialEffects();
break;
}
AppsInToss.ReportPerformanceIssue($"thermal_{level.ToString().ToLower()}", level.ToString());
}
void OptimizePowerConsumption()
{
// 즉시 전력 소모 최적화
// CPU 사용률 감소
Time.fixedDeltaTime = 0.04f; // 물리 업데이트 빈도 감소
// GPU 부하 감소
QualitySettings.pixelLightCount = 1;
QualitySettings.shadowCascades = 2;
// 불필요한 업데이트 중단
var inefficientComponents = FindObjectsOfType<MonoBehaviour>()
.Where(mb => mb.enabled && IsInefficient(mb))
.ToArray();
foreach (var component in inefficientComponents)
{
component.enabled = false;
}
Debug.Log("전력 소모 최적화 적용");
}
bool IsInefficient(MonoBehaviour component)
{
// 전력 소모가 큰 컴포넌트 식별
string typeName = component.GetType().Name;
return typeName.Contains("Effect") ||
typeName.Contains("Animation") ||
typeName.Contains("Particle") ||
typeName.Contains("Reflection");
}
void ReduceParticleEffects(float intensity)
{
var particleSystems = FindObjectsOfType<ParticleSystem>();
foreach (var ps in particleSystems)
{
var emission = ps.emission;
var rateOverTime = emission.rateOverTime;
rateOverTime.constant *= intensity;
emission.rateOverTime = rateOverTime;
var main = ps.main;
main.maxParticles = Mathf.RoundToInt(main.maxParticles * intensity);
}
}
void ReduceUpdateFrequency()
{
// 업데이트 빈도가 높은 컴포넌트들의 빈도 감소
var frequentUpdaters = FindObjectsOfType<MonoBehaviour>()
.Where(mb => HasFrequentUpdate(mb))
.ToArray();
foreach (var updater in frequentUpdaters)
{
// 업데이트 간격 조정 (구체적 구현은 컴포넌트별로 다름)
AdjustUpdateFrequency(updater);
}
}
bool HasFrequentUpdate(MonoBehaviour component)
{
// 빈번한 업데이트를 하는 컴포넌트 식별
return component.GetType().GetMethod("Update") != null ||
component.GetType().GetMethod("LateUpdate") != null ||
component.GetType().GetMethod("FixedUpdate") != null;
}
void AdjustUpdateFrequency(MonoBehaviour component)
{
// 컴포넌트별 업데이트 빈도 조정
// 실제 구현은 각 컴포넌트의 특성에 따라 다름
}
void DisableNonEssentialEffects()
{
// 필수적이지 않은 효과들 비활성화
var effects = FindObjectsOfType<ParticleSystem>();
foreach (var effect in effects)
{
if (!IsEssentialEffect(effect))
{
effect.gameObject.SetActive(false);
}
}
var audioSources = FindObjectsOfType<AudioSource>();
foreach (var audio in audioSources)
{
if (!IsEssentialAudio(audio))
{
audio.enabled = false;
}
}
}
bool IsEssentialEffect(ParticleSystem effect)
{
// 필수 효과인지 판단
return effect.name.Contains("Essential") ||
effect.name.Contains("UI") ||
effect.transform.parent?.name.Contains("Critical") == true;
}
bool IsEssentialAudio(AudioSource audio)
{
// 필수 오디오인지 판단
return audio.name.Contains("UI") ||
audio.name.Contains("Critical") ||
audio.name.Contains("Toss"); // 앱인토스 관련 사운드
}
void RestoreEffects()
{
// 비활성화된 효과들 복원
var disabledEffects = FindObjectsOfType<ParticleSystem>(true)
.Where(ps => !ps.gameObject.activeInHierarchy)
.ToArray();
foreach (var effect in disabledEffects)
{
effect.gameObject.SetActive(true);
}
var disabledAudio = FindObjectsOfType<AudioSource>(true)
.Where(audio => !audio.enabled)
.ToArray();
foreach (var audio in disabledAudio)
{
audio.enabled = true;
}
}
// 공개 API
public bool IsInLowPowerMode()
{
return isLowPowerMode;
}
public float GetBatteryLevel()
{
return SystemInfo.batteryLevel;
}
public void ForceLowPowerMode(bool enable)
{
if (enable && !isLowPowerMode)
{
EnableLowPowerMode();
}
else if (!enable && isLowPowerMode)
{
DisableLowPowerMode();
}
}
}iOS는 엄격한 메모리 관리와 전력 효율성이 요구돼요.
Metal API 활용과 ARC 호환 메모리 관리, 적응적 품질 조정을 통해 최적의 iOS 경험을 제공하세요.
