iOS/Common

iOS 메모리 관리와 최적화

TDCIAN 2024. 4. 1. 23:19

 

메모리 관리는 iOS 앱 개발에서 중요한 주제 중 하나입니다. 메모리는 디바이스에서 정보를 저장하는 데 사용되는 모든 하드웨어를 포함하며, 앱이 실행될 때 실행 가능한 모든 명령어가 RAM에 로드됩니다. 이 때, 시스템은 Heap이라고 불리는 RAM의 일부를 클레임하며, 이곳에서 클래스의 모든 인스턴스가 앱이 실행되는 동안 생존합니다. 메모리 관리는 Heap 메모리의 생명 주기를 관리하면서 더 이상 필요하지 않은 객체가 해제되어 메모리를 재사용할 수 있도록 하는 과정을 의미합니다.

 

 

iOS 디바이스의 메모리 제약과 앱 메모리 제한

iOS 디바이스의 메모리 제한은 디바이스의 RAM 용량에 따라 달라집니다. 예를 들어, iPhone XS부터 iPhone 13까지는 3GB 이상의 RAM을 가진 디바이스에서 사용 가능한 가상 주소 공간이 약 15.375GB입니다. 반면, iPhone 6s부터 iPhone X까지는 1GB 이상의 RAM을 가진 디바이스에서 사용 가능한 가상 주소 공간이 약 11.375GB입니다.

 

메모리 워드 크기와 데이터 정렬

메모리 워드(word) 크기는 메모리에서 한 번에 읽거나 쓸 수 있는 데이터의 크기를 나타냅니다. 메모리 워드 크기와 데이터 정렬(alignment)은 메모리 액세스 성능에 큰 영향을 미칩니다. 데이터가 정확하게 정렬되어 있지 않으면 메모리 액세스 시 성능 저하가 발생할 수 있습니다. 따라서, 메모리 액세스 패턴을 최적화하고 데이터 정렬을 적절히 관리하여 성능을 향상시키는 것이 중요합니다.

 

포인터 크기(32비트, 64비트)에 따른 메모리 사용량 차이

iOS는 64비트 프로세서를 사용하며, 이는 포인터 크기가 64비트임을 의미합니다. 이는 앱이 더 많은 메모리를 사용할 수 있음을 의미하지만, 메모리 관리에 더 많은 주의가 필요하게 만듭니다. 또한, 64비트 프로세서는 더 큰 데이터 타입을 지원하므로, 이를 활용하여 메모리 사용량을 최적화할 수 있습니다.

 

iOS 앱에서 대용량 데이터를 다룰 때 메모리 사이즈를 고려한 최적화 방안

대용량 데이터를 다룰 때는 메모리 사용량을 최소화하고, 데이터를 효율적으로 관리하는 것이 중요합니다. 예를 들어, 대용량 이미지를 처리할 때는 이미지를 압축하거나 필요한 경우에만 메모리에 로드하여 메모리 사용량을 줄일 수 있습니다. 또한, 이미지 캐싱을 통해 이미지를 메모리에 저장하고, 필요할 때 빠르게 접근할 수 있도록 하여 앱의 성능을 향상시킬 수 있습니다.

 

네트워크 연결이 정상일 때 로컬에 내용을 저장하고, 네트워크 연결이 끊어졌을 때 로컬에 저장된 내용을 보여주는 코드 예시

import UIKit
import Combine

class ViewController: UIViewController {
    
    private let postsService: PostsService = .init()
    private var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .systemBackground
        
        self.postsService.fetchPosts().sink { completion in
            if case .failure(let error) = completion {
                print(">>> failure error - \(error)")
            }
        } receiveValue: { (posts: [Post]) in
            print(">>> posts count: \(posts.count)")
        }
        .store(in: &self.cancellables)
    }
}

struct Post: Codable {
    let userId: Int
    let id: Int
    let title: String
    let body: String
}

class PostsService {
    
    private let remoteService: PostsRemoteService = .init()
    private let offlineService: PostsOfflineService = .init()
    
    func fetchPosts() -> AnyPublisher<[Post], Error> {
        return self.remoteService.fetchPosts().catch { [weak self] error in
            print(">>> get offline because of an error - error: \(error)")
            return self?.offlineService.getOffline() ?? Empty().eraseToAnyPublisher()
        }.handleEvents(receiveOutput: { [weak self] posts in
            print(">>> save posts offline - posts.count: \(posts.count)")
            self?.offlineService.saveOffline(posts: posts)
        })
        .eraseToAnyPublisher()
    }
}

class PostsRemoteService {
    func fetchPosts() -> AnyPublisher<[Post], Error> {
        let urlString = "https://jsonplaceholder.typicode.com/posts"
        
        let url = URL(string: urlString)!
        return URLSession.shared.dataTaskPublisher(for: url)
            .map(\.data)
            .decode(type: [Post].self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

class PostsOfflineService {
    
    private let defaults = UserDefaults.standard
    private let key = "posts"
    
    func saveOffline(posts: [Post]) {
        if let encoded = try? JSONEncoder().encode(posts) {
            self.defaults.set(encoded, forKey: key)
        }
    }
    
    func getOffline() -> AnyPublisher<[Post], Error> {
        do {
            guard let data: Data = self.defaults.object(forKey: self.key) as? Data else {
                return Just([])
                    .setFailureType(to: Error.self)
                    .eraseToAnyPublisher()
            }
            let decoded: [Post] = try JSONDecoder().decode([Post].self, from: data)
            return Just(decoded)
                .setFailureType(to: Error.self)
                .eraseToAnyPublisher()
        } catch {
            return Fail(error: error).eraseToAnyPublisher()
        }
    }
}

 

 

 

 

정보 출처: phind