iOS
文章平均质量分 84
iOS 实战
AI架构师易筋
工作10+年,AI方向架构师,曾经任职阿里巴巴,世界500强HSBC。擅长大模型LLM原理、架构、深度学习、Transformer、算法、数据结构、设计模式。易筋乃阿里巴巴花名。
展开
-
Swift Combine 使用 handleEvents 操作符调试管道 从入门到精通二十五
目的: 使用断点、打印、记录语句或其他额外的逻辑,以便更有针对性地了解管道内发生的情况。传入数据,不对输出和失败类型或数据进行任何修改。当你在管道中加入该操作符时,可以指定一些可选的闭包,从而让你能够专注于你想要看到的信息。具有特定闭包的操作符是一个打开新窗口的好方法,通过该窗口可以查看管道取消、出错或以其他预期的方式终止时发生的情况。如果每个闭包都包含打印语句,则该操作符将非常像print操作符,具体表现在 使用 print 操作符调试管道。使用。原创 2024-02-20 23:11:41 · 1156 阅读 · 0 评论 -
Swift Combine 使用 print 操作符调试管道 从入门到精通二十四
虽然非常有效,但 print 操作符是一个钝器,它会生成大量的输出,你必须分析和审查它们以得到你想要的信息。如果你想让标识和打印的内容更具选择性,或者如果你需要处理传输的数据才能更有意义地使用它们,那么你可以查看 handleEvents 操作符。你可以在开始时看到初始化订阅的设置,然后看到通知,包括通过 print 操作符传递的值的调试信息。虽然上面的示例内容中未显示它,但你还会在出现错误时看到取消管道的事件,或在发布者报告没有进一步数据时的 completions 事件。要理解简单的管道,使用。原创 2024-02-19 21:36:20 · 1103 阅读 · 0 评论 -
Swift Combine 使用从 PassthroughSubject 预定好的发送的事件测试订阅者 从入门到精通二十三
扩展了 Combine 的 Swift 库。EntwineTest 中包含的关键元素之一是虚拟时间调度器,以及使用此调度器时安排(此技术的一个明显缺点是,它使得测试花费的最短时间至少是测试中的最大的队列延迟。在进行 UI 测试之外,你可能需要测试你的管道时序具有所需的效果。目的:当你想要测试的是管道的时序时,用于测试管道或订阅者。在 Combine 中有许多针对数据时序的操作符,包括。,只要使用时遵守 MIT 证书即可。测试订阅者,在测试中添加。的一部分,一个提供了一些。)结果时间的其他类。原创 2024-02-19 21:26:43 · 1546 阅读 · 0 评论 -
Swift Combine 使用 PassthroughSubject 测试订阅者 从入门到精通二十二
目的:为了测试订阅者或包含订阅者的代码,我们可以使用 PassthroughSubject 模拟发布源,明确地控制哪些数据被发送和何时发送。此模式依赖于订阅者在构建时设置发布者-订阅者生命周期的初始部分,并让代码保持等待直到提供数据。,发送数据以触发管道和订阅者闭包,或跟踪可以被验证的状态更改,即可控制测试代码本身。当你测试订阅者对失败的反应时,这种测试模式也非常有效,否则可能会终止订阅。当你单独测试订阅者时,你可以通过使用。方法触发更新来更精细的控制测试。模拟发布者以及使用相关的。原创 2024-02-18 23:54:29 · 1012 阅读 · 0 评论 -
Swift Combine 使用 XCTestExpectation 测试发布者 从入门到精通二十一
Combine 中的发布者和订阅者接口是非常易于测试的。借助 Combine 的可组合性,你可以利用此优势创建或消费符合协议的 API。以为关键接口,你可以替换任何一方以单独验证你的代码。例如,如果你的代码专注于通过 Combine 从外部 Web 服务中提供其数据,则可能会使此接口遵循。然后,你可以使用该接口独立测试管道的任何一侧。你可以模拟 API 请求和可能响应的数据,包括各种错误条件。这可以包括使用Just或Fail创建的发布者来返回数据,或者更复杂的使用Future。原创 2024-02-18 23:46:08 · 912 阅读 · 0 评论 -
Swift Combine 使用 ObservableObject 与 SwiftUI 模型作为发布源 从入门到精通二十
目的: SwiftUI 包含和协议,它为 SwiftUI 的视图提供了将状态外部化的手段,同时通知 SwiftUI 模型的变化。SwiftUI 视图是基于某些已知状态呈现的声明性结构,当该状态发生变化时,这些当前的结构将失效并更新。我们可以使用 Combine 来提供响应式更新来操纵此状态,并将其暴露回 SwiftUI。此处提供的示例是一个简单的输入表单,目的是根据对两个字段的输入提供响应式和动态的反馈。以下规则被编码到CombineSwiftUI 通过将状态外化为类中的属性,并使用。原创 2024-02-17 11:37:36 · 1057 阅读 · 0 评论 -
Swift Combine 响应NotificationCenter 的更新 从入门到精通十九
通知名称是基于字符串的结构体。创建 NotificationCenter 发布者时,你提供要接收的通知的名称,并可选地提供对象引用,以过滤特定类型的对象。通过 NotificationCenter 发送的 Notifications 为你应用中的事件提供了一个通用的中心化的位置。目的:作为发布者接收 NotificationCenter 的通知,以声明式的对所提供的信息做出响应。这允许将任意的字典(无论是引用类型还是值类型)包含在通知中。你还可以将自己的通知添加到你的应用程序中,在发送通知时,还可以在其。原创 2024-02-17 11:22:01 · 998 阅读 · 0 评论 -
Swift Combine 通过包装基于 delegate 的 API 创建重复发布者 从入门到精通十八
目的: 将 Apple delegate API 之一包装为 Combine 管道来提供值。Future 发布者非常适合包装现有代码以发出单个请求,但它不适用于产生冗长或可能无限量输出的发布者。Apple 的 Cocoa API 倾向于使用对象/代理模式,你可以选择接收任意数量的不同回调(通常包含数据)。其中一个例子是在 CoreLocation 库中,提供了许多不同的数据源。如果你想在管道中使用此类 API 之一提供的数据,你可以将对象包装起来,并使用来暴露发布者。原创 2024-02-16 23:55:00 · 963 阅读 · 0 评论 -
Swift Combine 合并多个管道以更新 UI 元素 从入门到精通十七
ViewController 被配置了多个通过声明式更新的元素。此示例故意模仿许多 Web 表单样式的验证场景,不过是在 UIKit 中使用 Combine。如果这些规则中的任何一个未得到满足,则我们希望禁用提交按钮并显示相关消息,解释需要满足的内容。目的:观察并响应多个 UI 元素发送的值,并将更新的值联合起来以更新界面。它还有一个按钮来提交合并的值,以及两个 labels 来提供反馈。这可以通过设置连接与合并在一起的一系列管道来实现。下面的示例将这些结合起来进行了展示。原创 2024-02-16 23:44:24 · 1377 阅读 · 0 评论 -
Swift Combine 级联多个 UI 更新,包括网络请求 从入门到精通十六
由上游的订阅者触发多个 UI 元素更新. 以下提供的示例是扩展了 通过用户输入更新声明式 UI 例子中的发布者, 添加了额外的 Combine 管道,当有人与所提供的界面交互时以更新多个 UI 元素。原创 2024-02-15 12:25:00 · 1440 阅读 · 0 评论 -
Swift Combine 通过用户输入更新声明式 UI 从入门到精通十五
将 Combine 与 UIKit 集成的模式是设置一个变量,该变量将保持对更新状态的引用,并使用 IBAction 连接控件。像 Combine 这样的框架的主要好处之一是建立一个声明性结构,定义界面将如何根据用户输入进行更新。这个例子与下一个模式 级联多个 UI 更新,包括网络请求 有点重叠,都建立在一个初始的发布者上。模式 级联多个 UI 更新,包括网络请求 在此代码上扩展为各种UI元素的多个级联更新。以下示例是更大的 ViewController 实现中的代码的一部分。原创 2024-02-15 12:06:21 · 1496 阅读 · 0 评论 -
Swift Combine 网络受限时从备用 URL 请求数据 从入门到精通十四
目的:flatMap操作符可以与catch一起使用,以持续处理新发布的值上的错误。flatMap 是用于处理持续事件流中错误的操作符。你提供一个闭包给flatMap,该闭包可以获取所传入的值,并创建一个一次性的发布者,完成可能失败的工作。这方面的一个例子是从网络请求数据,然后将其解码。你可以引入一个catch操作符,以捕获任何错误并提供适当的值。当你想要保持对上游发布者的更新时,这是一个完美的机制,因为它创建一次性的发布者或短管道,发送一个单一的值,然后完成每一个传入的值。原创 2024-02-14 15:43:14 · 1085 阅读 · 0 评论 -
Swift Combine 使用 flatMap 和 catch错误处理 从入门到精通十三
上述示例都假设,如果发生错误情况,订阅者将处理这些情况。但是,你并不总是能够控制订阅者的要求——如果你使用 SwiftUI,情况可能如此。在这些情况下,你需要构建管道,以便输出类型与订阅者的类型匹配。这意味着你在处理管道内的任何错误。例如,如果你正在使用 SwiftUI,并且你希望使用assign在按钮上设置isEnabled如果发布者抛出一个错误(例如),你需要构建一个管道来转换输出类型,还需要处理管道内的错误,以匹配错误类型。如何处理管道内的错误取决于管道的定义方式。原创 2024-02-14 15:35:15 · 985 阅读 · 0 评论 -
Swift Combine 有序的异步操作 从入门到精通十二
目的:使用 Combine 的管道来显式地对异步操作进行排序这类似于一个叫做 “promise chaining” 的概念。虽然你可以将 Combine 处理的和其行为一致,但它可能不能良好地替代对 promise 库的使用。主要区别在于,promise 库总是将每个 promise 作为单一结果处理,而 Combine 带来了可能需要处理许多值的复杂性。任何需要按特定顺序执行的异步(或同步)任务组都可以使用 Combine 管道进行协调管理。原创 2024-02-13 12:03:04 · 2667 阅读 · 0 评论 -
Swift Combine 用 Future 来封装异步请求 从入门到精通十一
目的:使用 Future 将异步请求转换为发布者,以便在 Combine 管道中使用返回结果。promiseFuture 在创建时立即发起其中异步 API 的调用,而不是 当它收到订阅需求时。这可能不是你想要或需要的行为。如果你希望在订阅者请求数据时再发起调用,你可能需要用来包装 Future。如果您想返回一个已经被解析的promise作为Future发布者,你可以在闭包中立即返回你想要的结果。以下示例将单个值true返回表示成功。你同样可以简单地返回false,发布者仍然会将其作为一个成功的。原创 2024-02-13 11:41:57 · 766 阅读 · 0 评论 -
Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十
这可以通过使用 Combine 的搭配一系列处理数据的操作符来轻松完成。最简单的,调用的,然后在数据到达订阅者之前使用和。原创 2024-02-10 00:30:00 · 1045 阅读 · 0 评论 -
Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
本章包括一系列模式和发布者、订阅者和管道的示例。这些示例旨在说明如何使用 Combine 框架完成各种任务。原创 2024-02-10 00:15:00 · 2093 阅读 · 0 评论 -
Swift 使用 Combine 管道和线程进行开发 从入门到精通八
Combine 不是一个单线程的结构。操作符和发布者可以在不同的调度队列或 runloops 中运行。构建的管道可以在单个队列中,也可以跨多个队列或线程传输数据。Combine 允许发布者指定线程调度器,不论是从上游的发布者(操作符)接收数据或者向下游的订阅者发送数据,都使用它调度到指定线程。在与更新 UI 元素的订阅者配合使用时,这一点至关重要,因为更新 UI 始终应该在主线程上。许多操作符可以修改用于进行相关处理的线程或队列。和。原创 2024-02-09 00:30:00 · 1068 阅读 · 0 评论 -
Swift 使用 Combine 进行开发 从入门到精通七
通常从利用现有的发布者、操作符和订阅者来组成管道开始。本书中的许多示例突出了各种模式,其中许多模式旨在对界面内的用户输入提供声明性响应。你可能还希望创建更容易集成到 Combine 的 API。例如,创建一个封装远程 API 的发布者,返回单个结果或一系列结果。或者,你可能正在创建一个订阅者来随着时间的推移去处理和消费数据。原创 2024-02-09 00:15:00 · 1997 阅读 · 0 评论 -
Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
接受一个闭包,该闭包接收从发布者发送的任何结果值。存储对订阅者的引用非常重要,因为当引用被释放销毁时,它将隐含地取消其操作。将从发布者传下来的值应用到由 keypath 定义的对象, keypath 在创建管道时被设置。是用于接收整个管道数据的协议,但通常 the subscriber 指的是管道的末端。订阅者支持取消操作,取消时将终止订阅关系以及所有流完成之前,由发布者发送的数据。当你存储和自己订阅者的引用以便稍后清理时,你通常希望引用销毁时能自己取消订阅。接受的闭包,可以操纵 SwiftUI 中的。原创 2024-02-08 00:30:00 · 912 阅读 · 0 评论 -
Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
操作符是 Apple 参考文档中发布者下包含的一些预构建函数的便捷名称。操作符用来组合成管道。许多操作符会接受开发人员的一个或多个闭包,以定义业务逻辑,同时保持并持有发布者/订阅者的生命周期。一些操作符支持合并来自不同管道的输出、更改数据的时序或过滤所提供的数据。操作符可能还会对操作类型有限制, 还可用于定义错误处理和重试逻辑、缓冲和预先载入以及支持调试。原创 2024-02-08 00:15:00 · 1025 阅读 · 0 评论 -
Swift Combine 发布者publisher的生命周期 从入门到精通四
cancel().failure在上述图表中包含了一组堆积起来的弹珠图, 这是为了突出 Combine 的弹珠图在管道的整体生命周期中的重点。通常,图表推断所有的连接配置都已完成并已发送了数据请求。Combine 的弹珠图的核心是从请求数据到触发任何完成或取消之间的一系列事件。原创 2024-02-07 15:57:16 · 1543 阅读 · 0 评论 -
Swift Combine 管道 从入门到精通三
函数响应式编程的管道可能难以理解。发布者生成和发送数据,操作符对该数据做出响应并有可能更改它,订阅者请求并接收这些数据。这本身就很复杂,但 Combine 的一些操作符还可能改变事件发生的时序 —— 引入延迟、将多个值合并成一个值等等。由于这些比较复杂可能难以理解,因此函数响应式编程社区使用一种称为 弹珠图 的视觉描述来说明这些变化。在探索 Combine 背后的概念时,你可能会发现自己正在查看其他函数响应式编程系统,如RxSwift或。与这些系统相关的文档通常使用弹珠图。原创 2024-02-07 15:35:48 · 1622 阅读 · 0 评论 -
Swift Combine 发布者订阅者操作者 从入门到精通二
你只需要了解几个核心概念,就能使用好 Combine,但理解它们非常重要。这些概念中的每一个都通过通用协议反映在框架中,以将概念转化为预期的功能。原创 2024-02-06 23:55:51 · 1832 阅读 · 0 评论 -
Swift Combine 从入门到精通一
用 Apple 官方的话来说,Combine 是:Combine 是 Apple 用来实现函数响应式编程的库, 类似于 RxSwift。RxSwift 是 ReactiveX 对 Swift 语言的实现。Combine 使用了许多可以在其他语言和库中找到的相同的函数响应式概念,并将 Swift 的静态类型特性应用其中。原创 2024-02-06 23:39:21 · 2006 阅读 · 0 评论 -
翻译: Swift 中的委托保留周期 如何在“纯”Swift(没有@objc)中进行弱协议引用
您需要将协议类型声明为AnyObject.protocol ProtocolNameDelegate: AnyObject { // Protocol stuff goes here}class SomeClass { weak var delegate: ProtocolNameDelegate?}使用AnyObject你说只有类class可以符合这个协议,而结构struct或枚举enum不能。为什么委托应该是弱 var?1. 什么是保留周期和内存泄漏?iOS 中的内存原创 2021-11-13 08:33:49 · 2049 阅读 · 0 评论 -
翻译: 如何使用 Xcode 的内存图调试器检测 iOS 内存泄漏并保留周期
在 DoorDash,我们一直在努力通过提高应用程序的稳定性来提高我们的用户体验。这项工作的主要部分是防止、修复和消除我们大型代码库中的任何保留周期和内存泄漏。为了检测和修复这些问题,我们发现 Memory Graph Debugger 快速且易于使用。在我们的 Dasher iOS 应用程序上显着提高了我们的无 OOM 会话率之后,我们想分享一些关于避免和修复保留周期的技巧,以及使用 Xcode 的内存图调试器的快速介绍,供不熟悉的人使用。如果您对查明问题内存的根本原因感兴趣,请查看我们的新博客文章使用原创 2021-11-13 08:20:51 · 3194 阅读 · 0 评论 -
翻译: Swift 中信号量的美妙之处
在这个故事中,我们将做以下事情:了解什么是信号量了解信号量的工作原理实施和解释2个例子1. 开始吧信号量使我们能够控制多个线程对共享资源的访问。为了简单开始,让我们考虑以下现实生活场景:一位父亲和他的三个孩子坐在家里,然后他拿出一个 iPad……孩子2:我想玩iPad!!!Kid 1 : NO!, 我想先玩……Kid 3 : Ipad!平板电脑!平板电脑!掌声父亲:好的,孩子 2,既然你先问了,而且目前没有人在使用 iPad,那就拿走它,但一旦你完成了就告诉我。剩下的孩子们,请耐.原创 2021-11-13 07:28:52 · 865 阅读 · 0 评论 -
Swift 5 从Model, Struct或Class转Dictionary
将Struct(遵守Codable协议)用JSONEncoder的encode转成Data,然后再用JSONSerialization反序列化成Dictionary对象。/// 直接将Struct或Class转成Dictionaryprotocol Convertable: Codable {}extension Convertable { /// 直接将Struct或Class转成Dictionary func convertToDict() -> Dictionary原创 2021-10-02 11:27:17 · 2248 阅读 · 0 评论 -
翻译:Swift5 使用日期类型:Date、DateFormatter、DateComponent
1. 日期日期是一个特定的时间点,独立于任何日历或时区。要创建一个初始化为当前日期和时间的日期对象,只需像这样写:let date = Date()如果您在 Playground 上运行它,实时视图中显示的日期对象可能看起来与打印出来的有点不同。这是因为实时视图中显示的 Date 对象显示了系统的当前日期和时间。当您打印出来时,它会以 ISO 8601 格式的 UTC 时间显示日期信息。我在 GMT+8 时区,所以比我打印出来的时间早 8 小时。2021-06-08 07:18:29 +0000原创 2021-06-08 15:26:41 · 2361 阅读 · 0 评论 -
翻译:swift 5 iOS Accessibility从入门到精通
在此 iOS 辅助功能教程中,了解如何使用 VoiceOver 和辅助功能检查器让应用更易于访问。各行各业、各个年龄段和不同背景的人都使用智能手机应用程序,包括残疾人士。在设计您的应用时考虑到可访问性,可以帮助每个人使用它们,包括有视力、运动、学习或听力障碍的人。在此 iOS 辅助功能教程中,您将改造现有的应用程序,使其更易于视障人士使用。在此过程中,您将学习如何:使用旁白。使用辅助功能检查器检查您的应用。使用 UIKit 实现可访问性元素。为残障人士打造更好的用户体验。本教程需要 Xco原创 2021-06-01 18:55:42 · 1628 阅读 · 0 评论 -
swift5 修改Accessibility order读取的顺序
Accessibility 读取的顺序是从上到下,从左到右。场景:如果在右侧的小icon,为了更好点击,故意把边距放大了。那么在左边的label读的顺序就靠后了。也就是限度了右边的icon,再读左边的label。从使用者的角度来说,是不能接受的。咋办?解决Swift5 您可以设置视图的 accessiblityElements 数组属性:当父视图为 UIStackView 时也有效view.accessibilityElements = [view1, label2, button3...]原创 2021-06-01 16:16:53 · 574 阅读 · 0 评论 -
swift5 decimal 转换为String 并制定小数点位数
decimal 转换为String 并制定小数点位数。首先转为double或者float,再用String(format: "%.3f", doubleValue) 即可let decimalValue: Decimal = 3.14159let doubleValue = Double(truncating: decimalValue as NSNumber)print(String(format: "%.3f", doubleValue))print(String(format: "%.4f",原创 2021-05-03 22:13:01 · 3747 阅读 · 0 评论 -
翻译: 如何使用Xcode构建xcframework
人们通过构建有用的代码块来构建Framework框架来帮助社区,并将其分发给其他人,这样他们就可以直接使用它,而无需再次重写所有逻辑。它在软件行业中非常有用,您可以始终依靠他人以及他人投入大量时间进行完善和测试以供他人使用的开源库/框架,我们作为软件工程师,在方便时应利用它我们。在大型团队中,人们建立框架以使工作模块化,这有助于大型团队拆分工作,而较小的团队可以孤立地工作。同样,能够在公司内的不同应用程序中重用相同的组件/框架,而不必从一个地方重写或复制/粘贴该代码。在本文中,我将介绍如何构建框架并使原创 2021-04-21 13:07:41 · 1708 阅读 · 4 评论 -
翻译:PropertyWrapper swift 5 aop特性
说明swift 5 提出PropertyWrapper,类似于AOP拦截器的思想,在属性的更改之前,做个拦截处理,以下为官网资料的翻译。PropertyWrapper属性包装器在管理属性存储方式的代码与定义属性的代码之间增加了一层隔离。例如,如果您具有提供线程安全检查或将其基础数据存储在数据库中的属性,则必须在每个属性上编写该代码。使用属性包装器时,定义包装器时,只需编写一次管理代码,然后通过将其应用于多个属性来重用该管理代码。要定义属性包装器,您需要创建定义属性的结构,枚举或类wrappedVal原创 2021-04-11 23:02:39 · 416 阅读 · 0 评论 -
Swift 5 UIStackView中添加自动换行的Label
说明实现UIStackView中添加可以自动换行的Label思路分析:view中添加UIStackView,UIStackView添加Label;UIStackView的constraint为上,左,右;Label的constraint为左,右;Label的numberOfLine = 0Demo 代码如下://// ViewController.swift// StackViewLabelDemo//// Created by zgpeace on 2021/3/23.原创 2021-03-23 13:25:25 · 1604 阅读 · 0 评论 -
翻译:Swift 5创建和使用Framework, XCFramework 从入门到精通
说明了解如何构建iOS框架,该框架可让您在应用程序之间共享代码,模块化代码或将其分发为第三方库。下载代码更新说明:本教程已由Emad Ghorbaninia更新到iOS 14,Xcode 12和Swift 5.3。原始教程由Sam Davies撰写。您是否曾经想在两个应用程序之间共享大量代码,或者想与其他开发人员共享程序的一部分?也许您想对代码进行模块化,就像iOS SDK通过功能将其API分开一样。或者,您可能希望像流行的第三方库一样分发代码。在本教程中,您将把在为iOS创建自定义Calend原创 2021-03-21 22:48:21 · 3072 阅读 · 2 评论 -
TableView全展开实现ContentSizedTableView
最近在用TableView 画Excel表格(关键词Spreadsheet),有个诉求就是要全部展开。简单描述就是要tableView.height == contentSize.height用如下方法可以实现:定义ContentSizedTableViewimport UIKitfinal class ContentSizedTableView: UITableView { override var contentSize:CGSize { didSet {原创 2021-03-14 09:32:59 · 241 阅读 · 0 评论 -
Swift 5 用TableView实现动态Excel表格Spreadsheet
动态Excel 表格实现的功能样子如下:数据由二维数组驱动model = [[String]]行数由有model.count决定每一列间隔相等,column列数由数据个数决定model[0].count。实现方案分析在google 上搜索 Spreadsheet 有很多强大的表格。考虑到简单实现,笔者用UITableview来实现,每一行都是一个UIStackView, 边框用UIView来实现。SpreadSheetView复用的tableView//// SpreadShe原创 2021-03-07 22:22:13 · 1832 阅读 · 0 评论 -
实战RxSwift中的Observable, subscribe, dispose, filter
可观察的生命周期在上图中,可观察到的发射了9个元素。当一个可观察对象发出一个元素时,它会在下一个事件中发出它。Observable发出三个轻击事件,然后结束。这称为completed事件。可观察对象发出error包含错误的事件。如果一个可观察对象发出一个error事件,则它也将终止并且不能再发出其他任何事件。一个observable发出next包含元素的事件。初始化环境创建一个项目命名为RXSwiftDemo,在命令行pod init, 在podfile 中填写如下内容,并运行pod ins原创 2021-02-24 23:09:30 · 1108 阅读 · 0 评论