-
ETag 캐싱으로 앱 성능 최적화하기Swift 2024. 11. 26. 18:51
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 ETag를 이용해 네트워크 캐싱을 하여 앱 성능을 최적화하는 방법에 대해 알아볼께요 🙋🏻
ETag 캐싱으로 앱 성능 최적화하기
모바일 앱 개발에서 네트워크 최적화는 사용자 경험을 향상시키는 핵심 요소입니다.
이번 포스팅에선 HTTP 프로토콜의 강력한 기능 중 하나인 ETag(Entity Tag)에 대해 자세히 알아보고, iOS 앱에서 이를 효과적으로 구현하는 방법을 살펴보겠습니다.
ETag?
ETag는 웹 리소스의 특정 버전을 고유하게 식별하는 식별자에요.
서버는 클라이언트에게 리소스를 응답할 때 ETag 헤더를 함께 전송하며, 클라이언트는 이후 요청 시 이 값을 사용해 리소스가 변경되었는지를 확인할 수 있습니다.
아래와 같이 예를 들어서, 서버의 응답 헤더는 다음과 같이 내려올 수 있어요.
HTTP/1.1 200 OK Content-Type: application/json ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
여기서 ETag 값을 활용해보는 겁니다 😃
ETag 동작 원리
1️⃣ 최초 요청
- 클라이언트가 서버에 리소스를 요청
- 서버는 리소스와 함께 ETag 값을 응답
2️⃣ 이후 요청
- 클라이언트는 저장된 ETag 값을 `If-None-Match`헤더에 포함하여 요청
- 서버는 현재 리소스의 ETag와 비교
- 일치하면 304 Not Modified 상태값으로 응답
- 불일치 시 새로운 리소스와 ETag를 담아 응답
요런 동작 원리를 가지고 있습니다.
그럼 Swift에서 어떻게 구현해보는지 한번 살펴볼까요?
Swift에서 ETag 구현하기
class NetworkManager { static let shared = NetworkManager() private let cache = NSCache<NSString, AnyObject>() func fetchData(from urlString: String, completion: @escaping (Result<Data, Error>) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(NetworkError.invalidURL)) return } var request = URLRequest(url: url) // 저장된 ETag가 있다면 요청에 포함 if let savedETag = UserDefaults.standard.string(forKey: "ETag-\(urlString)") { request.addValue(savedETag, forHTTPHeaderField: "If-None-Match") } let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in if let error = error { completion(.failure(error)) return } guard let httpResponse = response as? HTTPURLResponse else { completion(.failure(NetworkError.invalidResponse)) return } // 새로운 ETag 저장 if let newETag = httpResponse.allHeaderFields["ETag"] as? String { UserDefaults.standard.set(newETag, forKey: "ETag-\(urlString)") } switch httpResponse.statusCode { case 200: // 새로운 데이터 if let data = data { self?.cache.setObject(data as AnyObject, forKey: urlString as NSString) completion(.success(data)) } case 304: // 변경되지 않음 if let cachedData = self?.cache.object(forKey: urlString as NSString) as? Data { completion(.success(cachedData)) } default: completion(.failure(NetworkError.unexpectedStatusCode)) } } task.resume() } } enum NetworkError: Error { case invalidURL case invalidResponse case unexpectedStatusCode }
이건 네트워크 매니저를 구현할때 ETag 기능을 활용하도록 같이 구현해본 예시에요.
각자 네트워크를 구현하는 방식에 따라 다를 수 있지만 핵심은 같을겁니다.
UserDefaults와 같이 로컬 저장을 이용해 ETag 값을 저장하고 이용하는 겁니다.
최초 요청 시에는 받아온 ETag를 저장하고 이후 요청에서는 ETag를 헤더에 같이 담아서 보내는거에요.
응답으로는 200으로 떨어지면 새로운 데이터가 왔다는것이기에 데이터를 캐싱합니다.
그리고, statusCode에서 304로 떨어지면 ETag가 같으니 리소스 변경이 없다는 소리이기에 캐싱되어 있던 데이터를 이용할 수 있게 구현합니다.
아주 간단하죠?
조금 더 나아가서 시스템의 URL 캐싱을 활용하면 더 효율적으로 캐싱 구현이 가능합니다.
let configuration = URLSessionConfiguration.default configuration.requestCachePolicy = .returnCacheDataElseLoad configuration.urlCache = URLCache( memoryCapacity: 25_000_000, // 25MB diskCapacity: 50_000_000, // 50MB diskPath: "customPath" ) let session = URLSession(configuration: configuration)
요런식으로 프로젝트 상황에 맞는 캐싱 전략을 직접 생각하고 가져간다면 ETag와 적절히 같이 잘 사용할 수 있습니다.
그럼 이 ETag는 어떤점이 좋을까요?
ETag의 장점
1️⃣ 네트워크 효율성
- 불필요한 데이터 전송 감소
- 대역폭 절약 및 서버 부하 감소
2️⃣ 성능 향상
- 빠른 응답 시간
- 배터리 및 데이터 사용량 절약
3️⃣ 일관성 보장
- 캐싱된 데이터의 최신성을 보장
- 리소스 버전 관리에 용이
그럼 반대로 고려해야할 주의사항도 알아볼까요?
ETag 주의사항
1️⃣ 보안
- 민감 데이터는 적절한 캐시 정책이 필요합니다. (HTTPS 사용 권장)
2️⃣ 메모리 관리
- 캐시 크기를 적절히 설정하고 주기적인 캐시를 정리하도록 설계해야 메모리를 효율적으로 관리할 수 있습니다.
3️⃣ 오류 처리
- 네트워크 오류 상황에 대비한 처리가 필요합니다.
- 캐시 실패 시 폴백 전략 등을 구현해놓는것이 안전합니다.
마무리
ETag는 모바일 앱 성능을 최적화하는 강력한 도구가 될 수 있어요!
적절히 구현한다면 말이죠ㅎㅎ
한번 간단히 앱에서 크게 바꾸지 않아도 적용이 가능하니 도전해보는건 어떨까요~?
레퍼런스
'Swift' 카테고리의 다른 글
Explore the Swift on Server ecosystem (feat. WWDC 2024) (30) 2024.12.02 Consume noncopyable types in Swift (feat. WWDC 2024) (26) 2024.11.18 Go further with Swift Testing (feat. WWDC 2024) (4) 2024.09.30 Meet Swift Testing (feat. WWDC 2024) (6) 2024.09.26 Swift 6 - @retroactive (7) 2024.09.12