- Published on
FileManager va NSCache yordamida rasmlarni yuklash va saqlash
- Authors
- Name
- ShoxruxC
- @iOSdasturchi
Loyiha arxitekturasi
Bu video — oldingi bir necha videoda o'rgangan barcha narsalar: Combine, FileManager, NSCacheni haqiqiy ilovada birgalikda qo'llash.
Papka tuzilmasi (MVVM + Utilities)
DownloadingImages/
├── Models/
│ └── PhotoModel.swift
├── Views/
│ ├── DownloadingImagesBootcamp.swift ← asosiy ekran
│ ├── DownloadingImagesRow.swift ← har bir qator
│ └── DownloadingImageView.swift ← bitta rasm
├── ViewModels/
│ ├── DownloadingImagesViewModel.swift ← asosiy ekran uchun
│ └── ImageLoadingViewModel.swift ← bitta rasm uchun
└── Utilities/
├── PhotoModelDataService.swift ← JSON yuklash
├── PhotoModelCacheManager.swift ← NSCache
└── PhotoModelFileManager.swift ← FileManager
Utilities papkasi — Model, View, ViewModel-ga tegishli bo'lmagan, lekin ilovaning istalgan joyidan foydalaniladigan yordamchi klasslar.
PhotoModel — ma'lumot modeli
API: https://jsonplaceholder.typicode.com/photos
struct PhotoModel: Identifiable, Codable {
let albumId: Int
let id: Int
let title: String
let url: String
let thumbnailUrl: String
}
Identifiable—ForEach-da id-siz ishlatish uchunCodable— JSON-dan avtomatik decode qilish uchun
PhotoModelDataService — JSON yuklash (singleton)
import Combine
import Foundation
class PhotoModelDataService {
static let instance = PhotoModelDataService()
private init() {
downloadData()
}
@Published var photoModels: [PhotoModel] = []
var cancellables = Set<AnyCancellable>()
func downloadData() {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/photos") else { return }
URLSession.shared
.dataTaskPublisher(for: url)
.subscribe(on: DispatchQueue.global(qos: .background))
.receive(on: DispatchQueue.main)
.tryMap(handleOutput)
.decode(type: [PhotoModel].self, decoder: JSONDecoder())
.sink(
receiveCompletion: { _ in },
receiveValue: { [weak self] returnedModels in
self?.photoModels = returnedModels
}
)
.store(in: &cancellables)
}
private func handleOutput(output: URLSession.DataTaskPublisher.Output) throws -> Data {
guard
let response = output.response as? HTTPURLResponse,
response.statusCode >= 200,
response.statusCode < 300
else {
throw URLError(.badServerResponse)
}
return output.data
}
}
Nima uchun alohida servis? — Agar bu yuklash mantig'ini DownloadingImagesViewModel-ga to'g'ridan-to'g'ri yozganda, faqat shu ViewModel-dan foydalanishimiz mumkin bo'lardi. Alohida singlton servis yaratilganda, ilovaning istalgan joyidan PhotoModelDataService.instancega murojaat qilib, bir xil yuklab olingan ma'lumotlardan foydalanish mumkin.
DownloadingImagesViewModel — asosiy ekran
import Combine
import Foundation
class DownloadingImagesViewModel: ObservableObject {
@Published var dataArray: [PhotoModel] = []
var cancellables = Set<AnyCancellable>()
let dataService = PhotoModelDataService.instance
init() {
addSubscriber()
}
func addSubscriber() {
dataService.$photoModels
.sink { [weak self] returnedPhotoModels in
self?.dataArray = returnedPhotoModels
}
.store(in: &cancellables)
}
}
ViewModel dataService.$photoModels-ga subscriber qo'shadi — ma'lumot yuklanishi bilanoq dataArray yangilanadi.
DownloadingImagesBootcamp — asosiy ekran view
struct DownloadingImagesBootcamp: View {
@StateObject var vm = DownloadingImagesViewModel()
var body: some View {
NavigationView {
List {
ForEach(vm.dataArray) { model in
DownloadingImagesRow(model: model)
}
}
.navigationTitle("Downloading Images")
}
}
}
DownloadingImagesRow — har bir qator
struct DownloadingImagesRow: View {
let model: PhotoModel
var body: some View {
HStack {
DownloadingImageView(
url: model.url,
key: "\(model.id)"
)
.frame(width: 75, height: 75)
VStack(alignment: .leading) {
Text(model.title)
.font(.headline)
Text(model.url)
.foregroundColor(.gray)
.italic()
}
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
PhotoModelCacheManager — NSCache singleton
import SwiftUI
class PhotoModelCacheManager {
static let instance = PhotoModelCacheManager()
private init() { }
var photoCache: NSCache<NSString, UIImage> = {
let cache = NSCache<NSString, UIImage>()
cache.countLimit = 200
cache.totalCostLimit = 1024 * 1024 * 200 // ~200 MB
return cache
}()
func add(key: String, value: UIImage) {
photoCache.setObject(value, forKey: key as NSString)
}
func get(key: String) -> UIImage? {
return photoCache.object(forKey: key as NSString)
}
}
PhotoModelFileManager — FileManager singleton
import SwiftUI
class PhotoModelFileManager {
static let instance = PhotoModelFileManager()
private init() {
createFolderIfNeeded()
}
let folderName = "downloaded_photos"
private func getFolderPath() -> URL? {
return FileManager
.default
.urls(for: .cachesDirectory, in: .userDomainMask)
.first?
.appendingPathComponent(folderName)
}
private func getImagePath(key: String) -> URL? {
guard let folder = getFolderPath() else { return nil }
return folder.appendingPathComponent("\(key).png")
}
private func createFolderIfNeeded() {
guard let url = getFolderPath() else { return }
if !FileManager.default.fileExists(atPath: url.path) {
do {
try FileManager.default.createDirectory(
at: url,
withIntermediateDirectories: true,
attributes: nil
)
print("Created folder")
} catch {
print("Error creating folder: \(error)")
}
}
}
func add(key: String, value: UIImage) {
guard
let data = value.pngData(),
let url = getImagePath(key: key)
else { return }
do {
try data.write(to: url)
} catch {
print("Error saving to FileManager: \(error)")
}
}
func get(key: String) -> UIImage? {
guard
let url = getImagePath(key: key),
FileManager.default.fileExists(atPath: url.path)
else { return nil }
return UIImage(contentsOfFile: url.path)
}
}
ImageLoadingViewModel — bitta rasm uchun
Bu — har bir DownloadingImageView uchun alohida ViewModel. U avval cache-ni tekshiradi, yo'q bo'lsa internetdan yuklaydi:
import SwiftUI
import Combine
class ImageLoadingViewModel: ObservableObject {
@Published var image: UIImage? = nil
@Published var isLoading: Bool = false
let urlString: String
let imageKey: String
let manager = PhotoModelCacheManager.instance
// FileManager versiyasi uchun:
// let manager = PhotoModelFileManager.instance
var cancellables = Set<AnyCancellable>()
init(url: String, key: String) {
self.urlString = url
self.imageKey = key
getImage()
}
func getImage() {
// 1. Avval cache-dan tekshir
if let savedImage = manager.get(key: imageKey) {
image = savedImage
print("Getting saved image")
} else {
// 2. Cache-da yo'q — internetdan yukla
downloadImage()
print("Downloading image now")
}
}
func downloadImage() {
isLoading = true
guard let url = URL(string: urlString) else {
isLoading = false
return
}
URLSession.shared
.dataTaskPublisher(for: url)
.map { UIImage(data: $0.data) }
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { [weak self] _ in
self?.isLoading = false
},
receiveValue: { [weak self] returnedImage in
guard let self, let image = returnedImage else { return }
self.image = image
// Cache-ga saqla
self.manager.add(key: self.imageKey, value: image)
self.isLoading = false
}
)
.store(in: &cancellables)
}
}
DownloadingImageView — bitta rasm view
struct DownloadingImageView: View {
@StateObject var loader: ImageLoadingViewModel
let url: String
let key: String
init(url: String, key: String) {
self.url = url
self.key = key
_loader = StateObject(
wrappedValue: ImageLoadingViewModel(url: url, key: key)
)
}
var body: some View {
ZStack {
if loader.isLoading {
ProgressView()
} else if let image = loader.image {
Image(uiImage: image)
.resizable()
.clipShape(Circle())
}
}
}
}
Muhim:
@StateObject-niinitichida_loader =sintaksisi bilan ishga tushirish kerak — bu URL-ni to'g'ri uzatishning yagona usuli.
NSCache vs FileManager — real farq
| Xususiyat | NSCache | FileManager |
|---|---|---|
| Saqlash joyi | RAM (xotira) | Disk |
| Ilova yopilganda | O'chadi | Qoladi |
| Xotira sarfi | Yuqori (~300MB) | Past (~48MB) |
| Tezlik | Juda tez | Biroz sekinroq |
| Avtomatik tozalash | ✅ (limit oshsa) | ❌ (qo'lda) |
| Qurilmalar orasida | ❌ | ✅ (iCloud bilan) |
Qaysi biri qachon
- NSCache: Joriy sessiya davomida qayta-qayta ko'riladigan rasmlar (masalan, scroll qilayotganda)
- FileManager: Har safar ilova ochilganda kerak bo'lgan rasmlar (profil rasmi, asosiy ekran rasmlari)
Cache optimallashtirish — xotira kuzatuvi
Simulator-da Xcode-ning Memory ko'rsatkichini kuzating:
- NSCache ishlatganda: ~300MB gacha ko'tarilishi mumkin →
countLimitvatotalCostLimitmuhim - FileManager ishlatganda: ~48MB atrofida → disk-ga saqlanadi, RAM-ga ta'sir minimal
NSCache-ning ulug'vorligi: totalCostLimit oshsa, eski elementlarni avtomatik chiqarib tashlaydi — siz qo'lda hech narsa qilmasangiz ham.
Ma'lumot oqimi (to'liq arxitektura)
PhotoModelDataService (singleton)
↓ @Published photoModels
DownloadingImagesViewModel
↓ dataArray
DownloadingImagesBootcamp (View)
↓ ForEach
DownloadingImagesRow (View)
↓ url + id
DownloadingImageView (View)
↓
ImageLoadingViewModel
├── Cache-dan olish (PhotoModelCacheManager)
└── Yuklab olish (URLSession + Combine)
↓
Cache-ga saqlash
Bu arxitektura reusable bo'lgani uchun, DownloadingImageView-ni PhotoModel-dan tashqari istalgan URL va ID bilan ishlatish mumkin — masalan, UserModel rasmlari uchun ham.