Swift Combine 级联多个 UI 更新,包括网络请求 从入门到精通十六

Combine 系列

  1. Swift Combine 从入门到精通一
  2. Swift Combine 发布者订阅者操作者 从入门到精通二
  3. Swift Combine 管道 从入门到精通三
  4. Swift Combine 发布者publisher的生命周期 从入门到精通四
  5. Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
  6. Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
  7. Swift 使用 Combine 进行开发 从入门到精通七
  8. Swift 使用 Combine 管道和线程进行开发 从入门到精通八
  9. Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
  10. Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十
  11. Swift Combine 用 Future 来封装异步请求 从入门到精通十一
  12. Swift Combine 有序的异步操作 从入门到精通十二
  13. Swift Combine 使用 flatMap 和 catch错误处理 从入门到精通十三
  14. Swift Combine 网络受限时从备用 URL 请求数据 从入门到精通十四
  15. Swift Combine 通过用户输入更新声明式 UI 从入门到精通十五
    在这里插入图片描述

1. 级联多个 UI 更新,包括网络请求

目的: 由上游的订阅者触发多个 UI 元素更新

以下提供的示例是扩展了 通过用户输入更新声明式 UI 例子中的发布者, 添加了额外的 Combine 管道,当有人与所提供的界面交互时以更新多个 UI 元素。

此视图的模式从接受用户输入的文本框开始,紧接着是一系列操作事件流:

  1. 使用一个 IBAction 来更新 @Published username 变量。
  2. 我们有一个订阅者(usernameSubscriber)连接到 $username 发布者,该发布者发送值的更新,并尝试取回 GitHub user。 结果返回的变量 githubUserData(也被 @Published 标记)是一个 GitHub 用户对象的列表。 尽管我们只期望在这里获得单个值,但我们使用列表是因为我们可以方便地在失败情况下返回空列表:无法访问 API 或用户名未在 GitHub 注册。
  3. 我们有 passthroughSubject networkActivityPublisher 来反映 GithubAPI 对象何时开始或完成网络请求。
  4. 我们有另一个订阅者 repositoryCountSubscriber 连接到 $githubUserData 发布者,该发布者从 github 用户数据对象中提取出仓库个数,并将其分配给要显示的文本字段。
  5. 我们有一个最终的订阅者 avatarViewSubscriber 连接到 $githubUserData,尝试取回与用户的头像相关的图像进行显示。

返回空列表很有用,因为当提供无效的用户名时,我们希望明确地移除以前显示的任何头像。 为此,我们需要管道始终有值可以流动,以便触发进一步的管道和相关的 UI 界面更新。 如果我们使用可选的 String? 而不是 String[] 数组,可选的字符串不会在值是 nil 时触发某些管道,并且我们始终希望管道返回一个结果值(即使是空值)。

以 assign 和 sink 创建的订阅者被存储在 ViewController 实例的 AnyCancellable 变量中。 由于它们是在类实例中定义的,Swift 编译器创建的 deinitializers 会在类被销毁时,取消并清理发布者。

许多喜欢 RxSwift 的开发者使用的是 “CancelBag” 对象来存储可取消的引用,并在销毁时取消管道。 可以在这儿看到一个这样的例子:https://github.com/tailec/CombineExamples/blob/master/CombineExamples/Shared/CancellableBag.swift. 这与 Combine 中在 AnyCancellable 类型上调用 store 函数是相似的,它允许你将订阅者的引用保存在一个集合中,例如 Set<AnyCancellable>

管道使用 subscribe 操作符明确配置为在后台队列中工作。 如果没有该额外的配置,管道将被在主线程调用并执行,因为它们是从 UI 线程上调用的,这可能会导致用户界面响应速度明显减慢。 同样,当管道的结果分配给或更新 UI 元素时,receive 操作符用于将该工作转移回主线程。

为了让 UI 在 @Published 属性发送的更改事件中不断更新,我们希望确保任何配置的管道都具有 <Never> 的失败类型。 这是 assign 操作符所必需的。 当使用 sink 操作符时,它也是一个潜在的 bug 来源。 如果来自 @Published 变量的管道以一个接受 Error 失败类型的 sink 结束,如果发生错误,sink 将给管道发送终止信号。 这将停止管道的任何进一步处理,即使有变量仍然被更新。

UIKit-Combine/GithubAPI.swift

import Foundation
import Combine

enum APIFailureCondition: Error {
    case invalidServerResponse
}

struct GithubAPIUser: Decodable {  // 1
    // A very *small* subset of the content available about
    //  a github API user for example:
    // https://api.github.com/users/heckj
    let login: String
    let public_repos: Int
    let avatar_url: String
}

struct GithubAPI {  // 2
    // NOTE(heckj): I've also seen this kind of API access
    // object set up with with a class and static methods on the class.
    // I don't know that there's a specific benefit to making this a value
    // type/struct with a function on it.

    /// externally accessible publisher that indicates that network activity is happening in the API proxy
    static let networkActivityPublisher = PassthroughSubject<Bool, Never>()  // 3

    /// creates a one-shot publisher that provides a GithubAPI User
    /// object as the end result. This method was specifically designed to
    /// return a list of 1 object, as opposed to the object itself to make
    /// it easier to distinguish a "no user" result (empty list)
    /// representation that could be dealt with more easily in a Combine
    /// pipeline than an optional value. The expected return type is a
    /// Publisher that returns either an empty list, or a list of one
    /// GithubAPUser, with a failure return type of Never, so it's
    /// suitable for recurring pipeline updates working with a @Published
    /// data source.
    /// - Parameter username: username to be retrieved from the Github API
    static func retrieveGithubUser(username: String) -> AnyPublisher<[GithubAPIUser], Never> {  // 4

        if username.count < 3 {  // 5
            return Just([]).eraseToAnyPublisher()
        }
        let assembledURL = String("https://api.github.com/users/\(username)")
        let publisher = URLSession.shared.dataTaskPublisher(for: URL(string: assembledURL)!)
            .handleEvents(receiveSubscription: { _ in  // 6
                networkActivityPublisher.send(true)
            }, receiveCompletion: { _ in
                networkActivityPublisher.send(false)
            }, receiveCancel: {
                networkActivityPublisher.send(false)
            })
            .tryMap { data, response -> Data in  // 7
                guard let httpResponse = response as? HTTPURLResponse,
                    httpResponse.statusCode == 200 else {
                        throw APIFailureCondition.invalidServerResponse
                }
                return data
            }
            .decode(type: GithubAPIUser.self, decoder: JSONDecoder())  // 8
            .map {
                [$0]  // 9 
            }
            .catch { err in  // 10
                // When I originally wrote this method, I was returning
                // a GithubAPIUser? optional.
                // I ended up converting this to return an empty
                // list as the "error output replacement" so that I could
                // represent that the current value requested didn't *have* a
                // correct github API response.
                return Just([])
            }
            .eraseToAnyPublisher()  // 11
            return publisher
    }
}
  1. 此处创建的 decodable 结构体是从 GitHub API 返回的数据的一部分。 在由 decode 操作符处理时,任何未在结构体中定义的字段都将被简单地忽略。
  2. 与 GitHub API 交互的代码被放在一个独立的结构体中,我习惯于将其放在一个单独的文件中。 API 结构体中的函数返回一个发布者,然后与 ViewController 中的其他管道进行混合合并。
  3. 该结构体还使用 passthroughSubject 暴露了一个发布者,使用布尔值以在发送网络请求时反映其状态。
  4. 我最开始创建了一个管道以返回一个可选的 GithubAPIUser 实例,但发现没有一种方便的方法来在失败条件下传递 “nil” 或空对象。 然后我修改了代码以返回一个列表,即使只需要一个实例,它却能更方便地表示一个“空”对象。 这对于想要在对 GithubAPIUser 对象不再存在后,在后续管道中做出响应以擦除现有值的情况很重要 —— 这时可以删除 repositoryCount 和用户头像的数据。
  5. 这里的逻辑只是为了防止无关的网络请求,如果请求的用户名少于 3 个字符,则返回空结果。
  6. handleEvents 操作符是我们触发网络请求发布者更新的方式。 我们定义了在订阅和终结(完成和取消)时触发的闭包,它们会在 passthroughSubject 上调用 send()。 这是我们如何作为单独的发布者提供有关管道操作的元数据的示例。
  7. tryMap 添加了对来自 github 的 API 响应的额外检查,以将来自 API 的不是有效用户实例的正确响应转换为管道失败条件。
  8. decode 从响应中获取数据并将其解码为 GithubAPIUser 的单个实例。
  9. map 用于获取单个实例并将其转换为单元素的列表,将类型更改为 GithubAPIUser 的列表:[GithubAPIUser]
  10. catch 运算符捕获此管道中的错误条件,并在失败时返回一个空列表,同时还将失败类型转换为 Never
  11. eraseToAnyPublisher 抹去链式操作符的复杂类型,并将整个管道暴露为 AnyPublisher 的一个实例。

2. UIKit-Combine/GithubViewController.swift

import UIKit
import Combine

class ViewController: UIViewController {

    @IBOutlet weak var github_id_entry: UITextField!
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
    @IBOutlet weak var repositoryCountLabel: UILabel!
    @IBOutlet weak var githubAvatarImageView: UIImageView!

    var repositoryCountSubscriber: AnyCancellable?
    var avatarViewSubscriber: AnyCancellable?
    var usernameSubscriber: AnyCancellable?
    var headingSubscriber: AnyCancellable?
    var apiNetworkActivitySubscriber: AnyCancellable?

    // username from the github_id_entry field, updated via IBAction
    @Published var username: String = ""

    // github user retrieved from the API publisher. As it's updated, it
    // is "wired" to update UI elements
    @Published private var githubUserData: [GithubAPIUser] = []

    // publisher reference for this is $username, of type <String, Never>
    var myBackgroundQueue: DispatchQueue = DispatchQueue(label: "viewControllerBackgroundQueue")
    let coreLocationProxy = LocationHeadingProxy()

    // MARK - Actions

    @IBAction func githubIdChanged(_ sender: UITextField) {
        username = sender.text ?? ""
        print("Set username to ", username)
    }

    // MARK - lifecycle methods

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let apiActivitySub = GithubAPI.networkActivityPublisher  // 1
        .receive(on: RunLoop.main)
            .sink { doingSomethingNow in
                if (doingSomethingNow) {
                    self.activityIndicator.startAnimating()
                } else {
                    self.activityIndicator.stopAnimating()
                }
        }
        apiNetworkActivitySubscriber = AnyCancellable(apiActivitySub)

        usernameSubscriber = $username  // 2
            .throttle(for: 0.5, scheduler: myBackgroundQueue, latest: true)
            // ^^ scheduler myBackGroundQueue publishes resulting elements
            // into that queue, resulting on this processing moving off the
            // main runloop.
            .removeDuplicates()
            .print("username pipeline: ") // debugging output for pipeline
            .map { username -> AnyPublisher<[GithubAPIUser], Never> in
                return GithubAPI.retrieveGithubUser(username: username)
            }
            // ^^ type returned in the pipeline is a Publisher, so we use
            // switchToLatest to flatten the values out of that
            // pipeline to return down the chain, rather than returning a
            // publisher down the pipeline.
            .switchToLatest()
            // using a sink to get the results from the API search lets us
            // get not only the user, but also any errors attempting to get it.
            .receive(on: RunLoop.main)
            .assign(to: \.githubUserData, on: self)

        // using .assign() on the other hand (which returns an
        // AnyCancellable) *DOES* require a Failure type of <Never>
        repositoryCountSubscriber = $githubUserData  // 3
            .print("github user data: ")
            .map { userData -> String in
                if let firstUser = userData.first {
                    return String(firstUser.public_repos)
                }
                return "unknown"
            }
            .receive(on: RunLoop.main)
            .assign(to: \.text, on: repositoryCountLabel)

        let avatarViewSub = $githubUserData  // 4
            .map { userData -> AnyPublisher<UIImage, Never> in
                guard let firstUser = userData.first else {
                    // my placeholder data being returned below is an empty
                    // UIImage() instance, which simply clears the display.
                    // Your use case may be better served with an explicit
                    // placeholder image in the event of this error condition.
                    return Just(UIImage()).eraseToAnyPublisher()
                }
                return URLSession.shared.dataTaskPublisher(for: URL(string: firstUser.avatar_url)!)
                    // ^^ this hands back (Data, response) objects
                    .handleEvents(receiveSubscription: { _ in
                        DispatchQueue.main.async {
                            self.activityIndicator.startAnimating()
                        }
                    }, receiveCompletion: { _ in
                        DispatchQueue.main.async {
                            self.activityIndicator.stopAnimating()
                        }
                    }, receiveCancel: {
                        DispatchQueue.main.async {
                            self.activityIndicator.stopAnimating()
                        }
                    })
                    .receive(on: self.myBackgroundQueue)
                    // ^^ do this work on a background Queue so we don't impact
                    // UI responsiveness
                    .map { $0.data }
                    // ^^ pare down to just the Data object
                    .map { UIImage(data: $0)!}
                    // ^^ convert Data into a UIImage with its initializer
                    .catch { err in
                        return Just(UIImage())
                    }
                    // ^^ deal the failure scenario and return my "replacement"
                    // image for when an avatar image either isn't available or
                    // fails somewhere in the pipeline here.
                    .eraseToAnyPublisher()
                    // ^^ match the return type here to the return type defined
                    // in the .map() wrapping this because otherwise the return
                    // type would be terribly complex nested set of generics.
            }
            .switchToLatest()
            // ^^ Take the returned publisher that's been passed down the chain
            // and "subscribe it out" to the value within in, and then pass
            // that further down.
            .receive(on: RunLoop.main)
            // ^^ and then switch to receive and process the data on the main
            // queue since we're messing with the UI
            .map { image -> UIImage? in
                image
            }
            // ^^ this converts from the type UIImage to the type UIImage?
            // which is key to making it work correctly with the .assign()
            // operator, which must map the type *exactly*
            .assign(to: \.image, on: self.githubAvatarImageView)

        // convert the .sink to an `AnyCancellable` object that we have
        // referenced from the implied initializers
        avatarViewSubscriber = AnyCancellable(avatarViewSub)

        // KVO publisher of UIKit interface element
        let _ = repositoryCountLabel.publisher(for: \.text)  // 5
            .sink { someValue in
                print("repositoryCountLabel Updated to \(String(describing: someValue))")
        }
    }

}
  1. 我们向我们之前的 controller 添加一个订阅者,它将来自 GithubAPI 对象的活跃状态的通知连接到我们的 activityIndicator
  2. IBAction 更新用户名的地方(来自我们之前的示例 通过用户输入更新声明式 UI)我们让订阅者发出网络请求并将结果放入一个我们的 ViewController 的新变量中(还是 @Published)。
  3. 第一个订阅者连接在发布者 $githubUserData 上。 此管道提取用户仓库的个数并更新到 UILabel 实例上。 当列表为空时,管道中间有一些逻辑来返回字符串 “unknown”。
  4. 第二个订阅者也连接到发布者 $githubUserData。 这会触发网络请求以获取 github 头像的图像数据。 这是一个更复杂的管道,从 githubUser 中提取数据,组装一个 URL,然后请求它。 我们也使用 handleEvents 操作符来触发对我们视图中的 activityIndi​​cator 的更新。 我们使用 receive 在后台队列上发出请求,然后将结果传递回主线程以更新 UI 元素。 catch 和失败处理在失败时返回一个空的 UIImage 实例。
  5. 最终订阅者连接到 UILabel 自身。 任何来自 FoundationKey-Value Observable 对象都可以产生一个发布者。 在此示例中,我们附加了一个发布者,该发布者触发 UI 元素已更新的打印语句。

虽然我们可以在更新 UI 元素时简单地将管道连接到它们,但这使得和实际的 UI 元素本身耦合更紧密。 虽然简单而直接,但创建明确的状态,以及分别对用户行为和数据做出更新是一个好的建议,这更利于调试和理解。 在上面的示例中,我们使用两个 @Published 属性来保存与当前视图关联的状态。 其中一个由 IBAction 更新,第二个使用 Combine 发布者管道以声明的方式更新。 所有其他的 UI 元素都依赖这些属性的发布者更新时进行更新。

3. 运行结果

在这里插入图片描述

json from https://api.github.com/users/zgpeace

{
  "login": "zgpeace",
  "id": 2386257,
  "node_id": "MDQ6VXNlcjIzODYyNTc=",
  "avatar_url": "https://avatars.githubusercontent.com/u/2386257?v=4",
  "gravatar_id": "",
  "url": "https://api.github.com/users/zgpeace",
  "html_url": "https://github.com/zgpeace",
  "followers_url": "https://api.github.com/users/zgpeace/followers",
  "following_url": "https://api.github.com/users/zgpeace/following{/other_user}",
  "gists_url": "https://api.github.com/users/zgpeace/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/zgpeace/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/zgpeace/subscriptions",
  "organizations_url": "https://api.github.com/users/zgpeace/orgs",
  "repos_url": "https://api.github.com/users/zgpeace/repos",
  "events_url": "https://api.github.com/users/zgpeace/events{/privacy}",
  "received_events_url": "https://api.github.com/users/zgpeace/received_events",
  "type": "User",
  "site_admin": false,
  "name": "zgpeace",
  "company": "HSBC",
  "blog": "https://blog.csdn.net/zgpeace",
  "location": "guangzhou",
  "email": null,
  "hireable": null,
  "bio": "AI, CNN, AutoDrive, Architect, Full Stack Developer\r\n\r\nEver work in Alibaba",
  "twitter_username": "zgpeace",
  "public_repos": 138,
  "public_gists": 5,
  "followers": 12,
  "following": 12,
  "created_at": "2012-09-20T13:55:28Z",
  "updated_at": "2023-12-27T06:44:33Z"
}

参考

https://heckj.github.io/swiftui-notes/index_zh-CN.html

代码

https://github.com/heckj/swiftui-notes

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

点餐|外卖订餐小程序|基于微信小程序的外卖订餐系统设计与实现(源码+数据库+文档)

点餐|外卖订餐小程序目录 目录 基于微信小程序的外卖订餐系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户微信端功能模块 2、管理员服务端功能模块 3、商家务端功能模块 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设…

Spring Boot整合 Cache 以Redis服务 处理数据缓存

目录 一、SpringBoot框架 二、什么是cache 三、redis介绍 四、高速缓存 一、SpringBoot框架 Spring Boot是一个基于Java的开源框架&#xff0c;用于快速构建独立的、可运行的、生产级的Spring应用程序。它简化了Spring应用程序的配置和部署过程&#xff0c;并提供了许多开…

git stash 正确用法

目录 一、背景 二、使用 2.1 使用之前&#xff0c;先简单了解下 git stash 干了什么&#xff1a; 2.2 git stash 相关命令 2.3 使用流程 1. 执行 git stash 2. 查看刚才保存的工作进度 git stash list 3. 这时候在看分支已经是干净无修改的(改动都有暂存到 stash) 4. 现在…

山脉的个数/攀登者

题目描述 攀登者喜欢寻找各种地图&#xff0c;并且尝试攀登到最高的山峰。 地图表示为一维数组&#xff0c;数组的索引代表水平位置&#xff0c;数组的元素代表相对海拔高度。其中数组元素0代表地面。 例如&#xff1a;[0,1,2,4,3,1,0,0,1,2,3,1,2,1,0]&#xff0c;代表如下…

代码随想录刷题笔记 DAY 26 | 组合总和 No.39 | 组合求和 II No.40 | 分割回文串 No.131

文章目录 Day 2501. 组合总和&#xff08;No. 39&#xff09;1.1 题目1.2 笔记1.3 代码 02. 组合求和 II&#xff08;No. 40&#xff09;2.1 题目2.2 笔记2.3 代码 03. 分割回文串&#xff08;No. 131&#xff09;3.1 题目3.2 笔记3.3 代码 Day 25 01. 组合总和&#xff08;No…

【计算机网络】网络层之IP协议

文章目录 1.基本概念2.协议头格式3.网段划分4.特殊的IP地址5.IP地址的数量限制6.私有IP地址和公网IP地址7.路由 1.基本概念 IP地址是定位主机的&#xff0c;具有一个将数据报从A主机跨网络可靠的送到B主机的能力。 但是有能力就一定能做到吗&#xff0c;只能说有很大的概率。…

寒假作业:2024/2/14

作业1&#xff1a;编程实现二维数组的杨辉三角 代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {int n;printf("please enter n:");scanf("%d",&n);int a…

【Spring MVC篇】返回响应

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【Spring MVC】 本专栏旨在分享学习Spring MVC的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 一、返回静态页面…

164基于matlab的奇异值分解、小波降噪、zoom细化

基于matlab的奇异值分解、小波降噪、zoom细化。程序已调通&#xff0c;可直接运行。 164 奇异值分解 小波降噪 zoom细化 (xiaohongshu.com)

vue学习106-120

创建项目p106 router&#xff0c;store和app.vue不用删 清一下router里的路由配置 vant组件库p107 目标&#xff1a;认识第三方vue组件库vant-ui&#xff08;cv战士&#xff09; 封装好了的组件整合在一起就是组件库 http://vant-contrib.gitee.io/vant/v2/#/zh-CN/ vue2用va…

python自学...

一、稍微高级一点的。。。 1. 闭包&#xff08;跟js差不多&#xff09; 2. 装饰器 就是spring的aop 3. 多线程

开窗,挖槽,放电齿,拼版

我们在阻焊层画线&#xff0c;就相当于去掉绿油阻焊&#xff0c;开窗一般是用在大电流走线的时候。先画要走的导线&#xff0c;之后切换到TopSolder或者Bottom Solder层&#xff0c;然后Place->line 画一条和原来先粗细一样的线即可&#xff01;但走电流的仍然是导线&#x…

第21讲关于我们页面实现

关于我们页面实现 关于锋哥页面author.vue 我们这里用一个vip宣传页面&#xff0c;套一个web-view <template><web-view src"http://www.java1234.com/vip.html"></web-view> </template><script> </script><style> <…

信息学奥赛一本通1258:【例9.2】数字金字塔

1258&#xff1a;【例9.2】数字金字塔 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 44051 通过数: 26272 【题目描述】 观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径&#xff0c;使路径经过数字的和最大。每一步可以从当前点走到左下方…

[嵌入式系统-15]:RT-Thread -1- 简介与技术架构

目录 一、RT-Thread简介 1.1 简介 1.2 功能特点 1.3 发展历史 1.4 应用场合 1.5 与Linux的比较 1.6 ​​​​​​​RT-Thread优缺点 二、技术架构 2.1 分层架构​编辑 2.2 功能组件 2.3 应用程序接口RT-Thread API 2.4 应用程序接口&#xff1a;RT-Thread API、POS…

[HCIE]vxlan --静态隧道

实验目的:1.pc2与pc3互通&#xff08;二层互通&#xff09;&#xff1b;2.pc1与pc3互通&#xff08;三层互通&#xff09; 实验说明&#xff1a;sw1划分vlan10 vlan20 ;sw2划分vlan30&#xff1b;上行接口均配置为Trunk 实验步骤&#xff1a; 1.配置CE1/CE2/CE3环回口互通&a…

房屋租赁系统的Java实战开发之旅

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

springboot191教师工作量管理系统

简介 【 毕设 源码 推荐 javaweb 项目】 基于 springbootvue 的教师工作量管理系统&#xff08;springboot191&#xff09; 适用于计算机类毕业设计&#xff0c;课程设计参考与学习用途。仅供学习参考&#xff0c; 不得用于商业或者非法用途&#xff0c;否则&#xff0c;一切后…

【调试】pstore原理和使用方法总结

什么是pstore pstore最初是用于系统发生oops或panic时&#xff0c;自动保存内核log buffer中的日志。不过在当前内核版本中&#xff0c;其已经支持了更多的功能&#xff0c;如保存console日志、ftrace消息和用户空间日志。同时&#xff0c;它还支持将这些消息保存在不同的存储…

Vulnhub靶场 DC-6

目录 一、环境搭建 二、主机发现 三、漏洞复现 1、wpscan工具 2、后台识别 dirsearch 3、爆破密码 4、rce漏洞利用 activity monitor 5、rce写shell 6、新线索 账户 7、提权 8、拿取flag 四、总结 一、环境搭建 Vulnhub靶机下载&#xff1a; 官网地址&#xff1a…