首页 文章 精选 留言 我的

精选列表

搜索[金融专有云开发测试平台],共10000篇文章
优秀的个人博客,低调大师

【iOS 开发】SnapKit 是怎样炼成的

前言 这是对 Swift 布局框架 SnapKit 的源码的一点分析,尝试搞清,一个好的布局框架,背后都做了些什么。 介绍 SnapKit 中的一些类 ConstraintView 等同于 UIView ConstraintAttributes 用于构造约束关系的各种元素(上下左右等) ConstraintDescription 包含了包括 ConstraintAttributes 在内的各种与约束有关的元素,一个 ConstraintDescription 实例,就可以提供与一种约束有关的所有内容。 ConstraintMaker 构造约束关系的起点,提供了 <code>makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void)</code> 方法来为程序员提供了描述约束的空间,也可以通过 left right top bottom centerX centerY 等属性,去生成一个 ConstraintMakerExtendable 实例(见下面) ConstraintMakerExtendable(继承 ConstraintMakerRelatable) 提供 left right top bottom leading trailing edges size margins 等内容,用以产生一个 ConstraintMakerRelatable 类型的实例 ConstraintMakerRelatable 直接用于构造约束关系,也是常用方法 <code>equalTo(_ other: ConstraintRelatableTarget) -> ConstraintMakerEditable</code> 与 <code>equalToSuperview</code> 的来源。核心方法是 <code>relatedTo(_ other: ConstraintRelatableTarget, relation: ConstraintRelation, file: String, line: UInt) -> ConstraintMakerEditable</code>,返回 ConstraintMakerEditable 类型的实例 ConstraintMakerEditable(继承 ConstraintMakerPriortizable) 在设定约束的宽度、高度以及偏移的时候,提供相应的加减乘除方法,返回 ConstraintMakerPriortizable 类型的实例 ConstraintMakerPriortizable(继承 ConstraintMakerFinalizable) 提供方法来设置约束的 priority,返回 ConstraintMakerFinalizable 类型的实例 ConstraintMakerFinalizable 一个只有一个类型为 ConstraintDescription 的属性的类,正如它的类名,有一个 ConstraintMakerFinalizable 实例,就得到了对于一个约束的完整描述。 至此,我们已经知道 SnapKit 是靠什么来确定了三个东西: 谁在做约束(ConstraintView) 怎么做约束(ConstraintMaker) 约束是什么(ConstraintDescription) let aView = UIView() aView.snp.makeConstraints({ make in make.width.equalToSuperview().dividedBy(2).priority(100) }) 当我们写下这样的语句时,先忽略掉 <code>snp</code> 是什么不管,里面设定 aView 的宽度为它的父视图的一半的这行约束语句,执行了这样的逻辑: ConstraintMaker 提供 <code>makeConstraints</code> 方法来让我们写约束的同时,开始维护了一个 ConstraintDescription 数组,叫 <code>descriptions</code> make 本身是 ConstraintMaker 类型的 在我们写下 <code>.width</code> 时,<code>descriptions</code> 数组第一次加入内容(<code>self.description</code>),同时我们用这个内容生成了一个 ConstraintMakerRelatable 实例 在我们写下 <code>.equalToSuperview()</code> 时,上一步中的内容(<code>self.description</code>)继续添加信息,同时我们用它生成了一个 ConstraintMakerEditable 实例 之后的 <code>.dividedBy(2).priority(100)</code> 使得之前的 ConstraintMakerEditable 实例变成了一个 ConstraintMakerFinalizable 实例,这个实例的 description 属性的类型是 ConstraintDescription,它包含了我们所描述的全部内容。但由于 ConstraintMakerEditable 本身就继承自 ConstraintMakerFinalizable,所以 <code>.dividedBy(2).priority(100)</code> 这一部分即便不写,这条语句在语法上也已经完成。 做个总结:到这里我们发现 ConstraintMaker 以及和它相关的类,构造了一套 DSL 来让我们可以轻松地写出约束语句,而这些语句把信息都放到了一个 ConstraintDescription 实例(<code>self.description</code>)里面,但我们仍然不知道它是如何以 UIKit 里面的 NSLayoutConstraint 的形式作用的。 snp 是什么 SnapKit 里面存在这样一些东西: <code>public protocol ConstraintDSL {}</code> <code>public protocol ConstraintBasicAttributesDSL : ConstraintDSL {}</code> <code>public protocol ConstraintAttributesDSL : ConstraintBasicAttributesDSL {}</code> <code>public struct ConstraintViewDSL: ConstraintAttributesDSL {}</code> 上面我们知道了 <code>aView</code> 作为一个 UIView,它同时也就是一个 ConstraintView,ConstraintView 有一个 snp 的属性,这给我们提供了入口来通过 SnapKit 给任意的 UIView 或 AppKit 里面的 NSView 通过 <code>.snp</code> 这样的语法来写约束。 这个 snp 属性的类型就是结构体 ConstraintViewDSL 一看就是面向协议的写法,通过一个个的 extension 来给 protocol 添加功能,最后用 struct 实现出来,就有了 snp 这个属性。 let topView = UIView() let centerView = UIView() centerView.snp.makeConstraints({ make in make.top.equalTo(topView.snp.bottom).offset(16) }) 这段代码展现了 snp 的两个作用: snp 有 left top right bottom edges size 等一大堆属性,这些属性的类型是 ConstraintItem,这是用于构造约束位置关系的 snp 作为 ConstraintViewDSL,有 <code>prepareConstraints</code> <code>makeConstraints</code> <code>remakeConstraints</code> <code>updateConstraints</code> <code>removeConstraints</code> 等函数,我们最常用的是 <code>makeConstraints</code> ,传入一个 closure,在里面写约束关系。这里要注意,我们使用的 <code>makeConstraints</code> 方法来源于 ConstraintViewDSL,但真正实现了构造约束的其实是我们上文里面写的 ConstraintMaker 里面的 <code>makeConstraints</code> 方法,见图: 约束是如何作用的 到现在我们还是没说,从 snp 到 ConstraintMaker,再到 ConstraintMakerFinalizable 的 description 属性,到底哪里创建了 NSLayoutConstraint,答案其实在之前提过多次的 ConstraintMaker 里面 // public class ConstraintMaker internal static func makeConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) { let maker = ConstraintMaker(item: item) closure(maker) var constraints: [Constraint] = [] for description in maker.descriptions { guard let constraint = description.constraint else { continue } constraints.append(constraint) } for constraint in constraints { constraint.activateIfNeeded(updatingExisting: false) } } internal static func updateConstraints(item: LayoutConstraintItem, closure: (_ make: ConstraintMaker) -> Void) { guard item.constraints.count > 0 else { self.makeConstraints(item: item, closure: closure) return } let maker = ConstraintMaker(item: item) closure(maker) var constraints: [Constraint] = [] for description in maker.descriptions { guard let constraint = description.constraint else { continue } constraints.append(constraint) } for constraint in constraints { constraint.activateIfNeeded(updatingExisting: true) } } 我们传入一个闭包来写约束关系时,这个闭包给叫做 maker 的 ConstraintMaker 实例写入了信息,遍历 maker 的 descriptions 之后(我们之前说一条约束语句最终得到一个 self.description,但往往会有多条约束,所以 ConstraintMakerFinalizable 里面的 self.description,在 ConstraintMaker 里被一个数组维护),我们得到了 Constraint 数组。 Constraint 这个类还没有介绍过,不过上面这个核心方法加上以前的内容,已经可以让我们猜出来,约束是怎么写出来的了: 其他内容补充 1 随便写了两句,展示一下各个方法传入的参数的类型,发现有各种 Target,貌似很复杂,不过点开之后发现是这种景象: 说白了就是因为 <code>equalTo:</code> 这个方法里面能传的参数类型比较多,手动来一个一个限制一下,我们看到 ConstraintRelatableTarget 这里可以放一些原生的可以代表数字的类型,外加四个自定义的 Constraint 类型。其他的 Target 协议也差不多是这种情况。 个人觉得这种做法还是挺值得学习的。 其他内容补充 2 SnapKit 里面用来表示位置主体的类其实不是 ConstraintView,而是 ConstraintItem 我们管这个“主体”叫 target,一个 target,再加上一个 ConstraintAttributes 实例,就可以组成一个 ConstraintItem。 有 attributes 属性很好理解,因为比如我们去做对齐,可以是 aView 的 top 和 bView 的 bottom 对齐,而不能是 aView 和 bView 对齐。但是为什么 target 的类型是 AnyObject 而不是 ConstraintView,即 UIView 或 NSView 呢? 在 ConstraintViewDSL 里面,target 确实是 ConstraintView 类型, 但在 ConstraintLayoutSupportDSL 里面,target 是 ConstraintLayoutSupport 类型, 在 ConstraintLayoutGuideDSL 里面,target 是 ConstraintLayoutGuide 类型 这部分就不具体解释了,想一探究竟的去看 LayoutConstraintItem.swift 这个文件吧。

优秀的个人博客,低调大师

docker 开发常用命令总结

Docker 常用命令总结,镜像下载,到docker容器创建,常用docker命令的 增删查 1.镜像下载,从hub.docker.com中下载最新版本的postgres docker pull postgres 2.首次启动一个容器,名称为--name docker-postgresql,把容器/root文件挂在到 ~/codehub下 -v ~/codehub:/root:ro,设置postgres密码为123456-e POSTGRES_PASSWORD=123456 docker run --name docker-postgresql -v ~/codehub:/root:ro -e POSTGRES_PASSWORD=123456 -d postgres 3.开启容器 docker start docker-postgresql 4.执行容器(已经开启的)并进入容器内部命令行 docker exec -it docker-postgresql bash 5.停止容器 docker stop docker-postgresql 6.查看所有容器(包含启动或者未启动的) docker ps -a 7.删除某个容器 docker rm docker-postgresql 8.查看正在运行的容器 docker ps 9.查看已经下载的镜像 docker images 10.查看容器详情 docker inspect docker-postgresql 11.私有仓库配置(客户端) 映射IP地址 vim /etc/hosts 192.168.0.160 reg.rzdocker.com sudo mkdir -p /etc/docker/certs.d/reg.rzdocker.com:5000 sudo scp -r ruizhi@192.168.0.160:/home/ruizhi/registry/certs/registry.crt /etc/docker/certs.d/reg.rzdocker.com:5000

优秀的个人博客,低调大师

【iOS 开发】用泛型简化 instantiateViewController

使用 storyboard 的时候,我们经常会写出下面这样的代码,用来跳转到其他页面 if let editVC = self.storyboard?.instantiateViewController(withIdentifier: "EditVC") as? EditVC { self.navigationController?.pushViewController(editVC, animated: true) } 最多改成这样 guard let editVC = self.storyboard?.instantiateViewController(withIdentifier: "EditVC") as? EditVC else { return } 实在是太冗长了,简化第一步,或许我们可以直接将 <code>EditVC</code> 这个类型字符串化变成 <code>"EditVC"</code>,代码变成: guard let editVC = self.storyboard?.instantiateViewController(withIdentifier: String(describing: EditVC.self)) as? EditVC else { return } 嗯,更长了,但是既然 <code>EditVC</code> 使用了两次,这使得我们有机会把这个过程封装成一个函数,而 <code>EditVC</code> 是唯一需要传递的参数 func instantiateVC(vc:UIViewController) -> UIViewController? { let id = String(describing: vc.self) return storyboard?.instantiateViewController(withIdentifier: id) } // how to use guard let editVC = instantiateVC(vc: EditVC()) as? EditVC else { return } 封装之后依然尴尬: 因为我们不能确定每次都需要函数返回 <code>EditVC</code> ,所以只能用 <code>UIViewController</code> 当做返回值,这使得我们还需要再加上 <code>as? EditVC</code> 去做类型转换,相当于还是手动使用了两次 <code>EditVC</code> ,而不是一次 传入的参数是 <code>EditVC()</code> 而不是 <code>EditVC</code> ,看起来丑陋 一次解决两个问题的答案是:泛型! 我们在函数中限定泛型 <code>T</code> 为 <code>UIViewController</code> ,但 <code>T</code> 具体是我们的 App 中的哪个子类我们不去管,通过函数的参数来指定 T 的具体类型,随后确定出我们函数的返回值为那个我们指定的 <code>T</code> 最终结果: extension UIViewController { func instantiateVC<T: UIViewController>(type: T.Type) -> T? { let id = String(describing: T.self) return storyboard?.instantiateViewController(withIdentifier: id) as? T } } // how to use guard let editVC = instantiateVC(type: EditVC.self) else { return } 前后对比: // before guard let editVC = storyboard?.instantiateViewController(withIdentifier: "EditVC") as? EditVC else { return } // after guard let editVC = instantiateVC(type: EditVC.self) else { return } 提醒: 这里我假装这个函数回避掉了直接使用字符串去传入 <code>Storyboard ID</code> 的风险,但可以这么做的前提是,这里的 <code>EditVC</code> 到 <code>"EditVC"</code> 可以直接字符串化。 如果你也要这么做的话,至少要保证你的类名的字符串化的结果和你的 <code>Storyboard ID</code> 是有固定关联的。比如 <code>EditVC</code> 对应 <code>"editvc"</code> 或 <code>"EDITVC"</code> 或 <code>"editVC"</code> (这样你只要修改字母大小写就可以了),而不是 <code>"abc123"</code> 这种随便写的东西。

资源下载

更多资源
Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册