DownloadingImages 下载缓存图片,显示图片文字列表

news2025/1/11 3:54:41

1. 用到的技术点:

  1) Codable : 可编/解码 JSON 数据

  2) background threads : 后台线程

  3) weak self : 弱引用

  4) Combine : 取消器/组合操作

  5) Publishers and Subscribers : 发布者与订阅者

  6) FileManager : 文件管理器

  7) NSCache : 缓存

2. 网址:

  2.1 测试接口网址:

jsonplaceholdericon-default.png?t=N7T8https://jsonplaceholder.typicode.com/

  2.2 JSON 转 Model 网址:

quicktypeicon-default.png?t=N7T8https://app.quicktype.io/

3. 项目结构图

4. Model 层

  4.1 创建 PhotoModel.swift 文件

import Foundation

struct PhotoModel: Identifiable, Codable{
    let albumId: Int
    let id: Int
    let title: String
    let url: String
    let thumbnailUrl: String
}

/*
 {
   "albumId": 1,
   "id": 1,
   "title": "accusamus beatae ad facilis cum similique qui sunt",
   "url": "https://via.placeholder.com/600/92c952",
   "thumbnailUrl": "https://via.placeholder.com/150/92c952"
 }
 */

5. 工具类

  5.1 创建请求数据服务类,PhotoModelDataService.swift

import Foundation
import Combine

/// 请求数据服务
class PhotoModelDataService{
    
    // 单例模式 Singleton
    static let instance = PhotoModelDataService()
    // 返回 JSON 数据,解码成模型
    @Published var photoModel:[PhotoModel] = []
    // 随时取消请求
    var cancellables = Set<AnyCancellable>()
    
    // 只能内部实例化,保证一个 App 只有一次实例化
    private init() {
        downloadData()
    }
    
    // 测试接口网址: https://jsonplaceholder.typicode.com/
    // 下载数据
    func downloadData(){
        // 获取 URL
        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 { completion in
                switch(completion){
                case .finished:
                    break
                case .failure(let error):
                    print("Error downloading data. \(error)")
                    break
                }
            } receiveValue: { [weak self] returnedPhotoModel in
                guard let self  = self else { return }
                self.photoModel = returnedPhotoModel
            }
            // 随时取消
            .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
    }
}

  5.2 创建图片缓存管理器类,PhotoModelCacheManager.swift

import Foundation
import SwiftUI

/// 图片缓存管理器
class PhotoModelCacheManager{
    // 单例模式
    static let instance = PhotoModelCacheManager()
    // 只能内部实例化,保证一个 App 只有一次实例化
    private init() {}
    // 图片数量缓存,计算型属性
    var photoCache: NSCache<NSString, UIImage> = {
        let cache = NSCache<NSString, UIImage>()
        cache.countLimit = 200
        cache.totalCostLimit = 1024 * 1024 * 200  // 200mb
        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)
    }
}

  5.3 创建储存图片文件管理类,PhotoModelFileManager.swift

import Foundation
import SwiftUI

// 存储图片文件管理器
class PhotoModelFileManager{
    // 单例模式
    static let instance = PhotoModelFileManager()
    let folderName = "downloaded_photos"
    
    private init(){
        createFolderIfNeeded()
    }
    
    // 创建存放图片的目录
    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)
                print("Created folder success.")
            } catch let error {
                print("Error creating folder. \(error)")
            }
        }
    }
    
    // 创建文件夹路径
    private func getFolderPath()-> URL?{
        return FileManager
            .default
            .urls(for: .cachesDirectory, in: .userDomainMask)
            .first?
            .appendingPathComponent(folderName)
    }
    
    // .../downloaded_photos
    // .../downloaded_photos/image_name.png
    /// 获取图片路径
    /// - Parameter key: 名字
    /// - Returns: 图片路径
    private func getImagePath(key: String) -> URL?{
        guard let folder = getFolderPath() else { return nil}
        return folder.appendingPathComponent(key + ".png")
    }
    
    // 添加图片
    func add(key: String, value: UIImage){
        // 获取数据和路径
        guard let data = value.pngData(),
              let url  = getImagePath(key: key) else { return }
        // 文件写人数据
        do {
            try data.write(to: url)
            print("Saving to file success.")
        } catch let error {
            print("Error saving to file manager. \(error)")
        }
    }
  
    // 获取图片
    func get(key: String) -> UIImage?{
        guard
            let path = getImagePath(key: key)?.path,
            FileManager.default.fileExists(atPath: path) else {
            //print("Error getting path.")
            return nil
        }
        return UIImage(contentsOfFile: path)
    }
}

6. ViewModel 层

  6.1 创建下载图片 ViewModel 类,DownloadingImageViewModel.swift

import Foundation
import Combine

class DownloadingImageViewModel: ObservableObject{
    
    // 数组模型
    @Published var dataArray:[PhotoModel] = []
    // 请求数据服务
    let dataService = PhotoModelDataService.instance
    // 取消操作
    var cancellables = Set<AnyCancellable>()
    
    init() {
        addSubscribers()
    }
    
    // 订阅数据
    func addSubscribers(){
        dataService.$photoModel
            .sink {[weak self] returnedPhotoModel in
                guard let self = self else { return }
                self.dataArray = returnedPhotoModel
            }
            .store(in: &cancellables)
    }
}

  6.2 创建图片加载 ViewModel 类,ImageLoadingViewModel.swift

import Foundation
import SwiftUI
import Combine

class ImageLoadingViewModel: ObservableObject{
    
    @Published var image: UIImage?
    @Published var isLoading: Bool = false
   
    // 取消
    var cancellables = Set<AnyCancellable>()
    
    // 缓存管理器
    let manager = PhotoModelFileManager.instance
    
    let urlString: String
    let imageKey: String
    
    init(url: String, key: String) {
        urlString = url
        imageKey = key
        getImage()
    }
    
    // 获取图片
    func getImage() {
        if let saveImage =  manager.get(key: imageKey){
            image = saveImage
            print("Getting saved image.")
        }else{
            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 { [weak self] _ in
                self?.isLoading = false
            } receiveValue: { [weak self] returnedImage in
                guard
                    let self = self,
                    let image = returnedImage else { return }
                self.image = image
                // 下载的图像保存在缓存中
                self.manager.add(key: imageKey, value: image)
            }
            .store(in: &cancellables)
    }
}

7. 创建 View 层

  7.1 创建下载,缓存,显示图片视图,DownloadingImageView.swift

import SwiftUI

/// 下载,缓存,显示图片
struct DownloadingImageView: View {
    @StateObject var loaderViewModel: ImageLoadingViewModel
    
    init(url: String, key: String) {
        // _ : 加载器  wrappedValue: 包装器
        _loaderViewModel = StateObject(wrappedValue: ImageLoadingViewModel(url: url, key: key))
    }
    
    var body: some View {
        ZStack {
            if loaderViewModel.isLoading{
                ProgressView()
            }else if let image = loaderViewModel.image{
                Image(uiImage: image)
                    .resizable()
                    .clipShape(Circle())
            }
        }
    }
}

struct DownloadingImageView_Previews: PreviewProvider {
    static var previews: some View {
        DownloadingImageView(url: "https://via.placeholder.com/600/92c952", key: "1")
            .frame(width: 75, height: 75)
            .previewLayout(.sizeThatFits)
    }
}

  7.2 创建下载显示图片文字行视图,DownloadingImagesRow.swift

import SwiftUI

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)
        }
    }
}

struct DownloadingImagesRow_Previews: PreviewProvider {
    static var previews: some View {
        DownloadingImagesRow(model: PhotoModel(albumId: 1, id: 1, title: "title", url: "https://via.placeholder.com/600/92c952", thumbnailUrl: "thumbnaolUrl here"))
            .padding()
            .previewLayout(.sizeThatFits)
    }
}

  7.3 创建下载显示图片文字列表视图,DownloadingImagesBootcamp.swift

import SwiftUI

// Codable : 可编/解码 JSON 数据
// background threads : 后台线程
// weak self : 弱引用
// Combine : 取消器/组合操作
// Publishers and Subscribers : 发布者与订阅者
// FileManager : 文件管理器
// NSCache : 缓存

struct DownloadingImagesBootcamp: View {
    @StateObject var viewModel = DownloadingImageViewModel()
    
    var body: some View {
        NavigationView {
            List {
                ForEach(viewModel.dataArray) { model in
                   DownloadingImagesRow(model: model)
                }
            }
            .navigationTitle("Downloading Images")
        }
    }
}

struct DownloadingImagesBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        DownloadingImagesBootcamp()
    }
}

8. 效果图:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1071739.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker入门加实战—docker安装并配置阿里云加速

docker入门加实战—docker安装并配置阿里云加速 为什么要学习docker 在开发和部署项目的过程中&#xff0c;经常会遇到如下问题&#xff1a; 软件安装包名字复杂&#xff0c;不知道去哪里找安装软件和部署项目步骤复杂&#xff0c;容易出错 这就是我们今天要学习Docker技术…

7个在Github上的flutter开源程序

阅读大量代码是提高开发技能的最佳方法之一。该开源项目是了解最佳实践、编码风格和许多其他主题的最佳场所。 软件开发最受欢迎的领域之一是跨平台移动应用程序开发。Flutter 是您可以使用的最流行的跨平台移动应用程序开发工具之一。今天&#xff0c;我们将了解 7 个开源 Flu…

sqlalchemy 连接池

报错 sqlalchemy.exc.TimeoutError: QueuePool limit of size 100 overflow 10 reached, connection timed out, timeout 30 (Background on this error at: http://sqlalche.me/e/3o7r) 查看数据库未活动超时时间 show variables like "interactive_timeout";一般…

R可视乎|灯芯柱状图代码解读

简介 这篇推文代码来源于&#xff1a;TidyTuesday&#xff0c;主要想学习如何绘制灯芯柱状图&#xff08;名字小编瞎取的&#xff09;&#xff0c;最终结果如下&#xff1a; 注释&#xff1a;与普通柱状图相比&#xff0c;灯芯柱状图不仅可以展示随时间变化的总体趋势&#xf…

AQS内部的体系架构

AQS本质上是一个双向队列&#xff0c;加一个状态位state。内部靠Node节点形成队列。 AQS由state和CLH变体的虚拟双端队列组成。 AQS的内部类Node类 属性说明&#xff1a; 内部结构&#xff1a;

继续改进 另外一种方法 还是可以用CourseExend类

优势&#xff1a;可以映射许多类上 很强大 用在名称不统一上 原理 1.先把查询结果包装 取新别名&#xff08;&#xff08; 就是结果集的映射&#xff08;&#xff09; 2.把中结果集映射相应的pojo类上 第一步基于类创建 第二步 注意字属性的写法 teacher 是属性 属性的属性 …

手机待办事项app哪个好?

手机是日常很多人随身携带的设备&#xff0c;手机除了拥有通讯功能外&#xff0c;还能帮助大家高效管理日常工作&#xff0c;借助手机上的待办事项提醒APP可以快速地帮助大家规划日常事务&#xff0c;提高工作的效率。 过去&#xff0c;我也曾经在寻找一款能够将工作任务清晰罗…

33 WEB漏洞-逻辑越权之水平垂直越权全解

目录 前言水平&#xff0c;垂直越权&#xff0c;未授权访问Pikachu-本地水平垂直越权演示(漏洞成因)墨者水平-身份认证失效漏洞实战(漏洞成因)原理越权检测-Burpsuite插件Authz安装测试(插件使用)修复防御方案 前言 越权漏洞文章分享&#xff1a;https://www.cnblogs.com/zhen…

LockSupport是做什么的?深入理解Java的三种线程等待通知机制

文章目录 一、LockSupport概述1、LockSupport是什么2、三种等待唤醒机制3、其他线程等待唤醒方式&#xff08;了解&#xff09; 二、代码实例分析1、使用wait()notify()&#xff08;1&#xff09;代码实例&#xff08;2&#xff09;分析总结 2、使用await()signal()&#xff08…

乌班图20.04简易部署k8s+kuboard第三方面板

1. 问题&#xff1a; 使用官方只能说步骤挺全。 &#x1f604;出错&#xff1f;出错不管&#xff0c;无论是系统问题&#xff0c;版本兼容问题&#xff0c;网络插件问题&#xff0c;还是防火墙问题&#xff0c;我只能说特异性问题分析检索起来很难很难。 新人很难搞懂&#x…

基于指数趋近律的机器人滑模轨迹跟踪控制算法及MATLAB仿真

机械手是工业制造领域中应用最广泛的自动化机械设备&#xff0c;广泛应用于工业制造、医疗、军工、半导体制造、太空探索等领域。它们虽然形式不同&#xff0c;但都有一个共同的特点&#xff0c;即能够接受指令&#xff0c;并能准确定位到三维(或二维)空间的某一点进行工作。由…

RabbitMQ之topic(主题)Exchange解读

目录 基本介绍 使用场景 演示架构 工程概述 RabbitConfig配置类&#xff1a;创建队列及交换机并进行绑定 MessageService业务类&#xff1a;发送消息及接收消息 主启动类RabbitMq01Application&#xff1a;实现ApplicationRunner接口 基本介绍 在rabbitmq中&#xff0c;…

【目标检测】大图包括标签切分,并转换成txt格式

前言 遥感图像比较大&#xff0c;通常需要切分成小块再进行训练&#xff0c;之前写过一篇关于大图裁切和拼接的文章【目标检测】图像裁剪/标签可视化/图像拼接处理脚本&#xff0c;不过当时的工作流是先将大图切分成小图&#xff0c;再在小图上进行标注&#xff0c;于是就不考…

[NewStarCTF 2023 公开赛道] week1 Crypto

brainfuck 题目描述&#xff1a; [>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<-]>>>>>>>.>----.<-----.>-----.>-----.<<<-.>>..…

深入了解归并排序:原理、性能分析与 Java 实现

归并排序&#xff08;Merge Sort&#xff09;是一种高效且稳定的排序算法&#xff0c;其优雅的分治策略使它成为排序领域的一颗明珠。它的核心思想是将一个未排序的数组分割成两个子数组&#xff0c;然后递归地对子数组进行排序&#xff0c;最后将这些排好序的子数组合并起来。…

在JavaScript中,什么是IIFE(Immediately Invoked Function Expression)?它的作用是什么?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

现代化战机之路:美国空军U-2侦察机基于Jenkins和k8s的CI/CD架构演进

▲ 点击上方"DevOps和k8s全栈技术"关注公众 华为北京研究所Q27大楼 随着技术的不断进步&#xff0c;军事领域也在积极采纳现代化工具来提高战备水平和效率。美国空军的U-2侦察机项目是一个鲜明的例子&#xff0c;它成功地借助Jenkins和Kubernetes&#xff08;k8s&…

Oracle修改数据之后提交事务如何回滚?

在 MySQL 和 Oracle 数据库中&#xff0c;事务提交后都无法回滚。 在 MySQL 中&#xff0c;恢复机制是通过回滚日志&#xff08;undo log&#xff09;实现的&#xff0c;所有事务进行的修改都会先记录到这个回滚日志中&#xff0c;然后在对数据库中的对应行进行写入。当事务已经…

IDE环境要注意统一编码,否则出现中文乱码找不到头绪

最近遇到在IDEA开发项目时&#xff0c;保存中文为乱码的现象&#xff0c;如图&#xff1a; 看了项目配置文件的编码都是UTF-8&#xff0c;在别的开发机上运行都正常&#xff0c;就是这台机器上有问题。 同事一时也找不到方法&#xff0c;因为没遇到同样的事情。 一直怀疑是编…

Spring源码解析——IOC之循环依赖处理

什么是循环依赖 循环依赖其实就是循环引用&#xff0c;也就是两个或则两个以上的bean互相持有对方&#xff0c;最终形成闭环。比如A依赖于B&#xff0c;B依赖于C&#xff0c;C又依赖于A。如下图所示&#xff1a; 注意&#xff0c;这里不是函数的循环调用&#xff0c;是对象的相…