轻量 View Controller 实践

MVC 是 iOS 应用开发时常用的一种架构,使用这种架构的目的之一便是为了写出后期可维护的代码。去年我使用 Swift 1.2 编写了我的第一个 iOS 应用,我称它为 Percolator,其中包含了我对 MVC 初步实践。由于自己初学应用开发与设计,在后期维护时发现程序整体结构过于耦合,有些地方违反了 MVC 的设计要求,并且滥用单例,也没有考虑到循环引用等等问题。今年夏天 Xcode8 beta + Swift3.0 beta 放出,利用假期这段时间完成了 Percolator 的重构,这篇手记记录了设计良好架构的一些实践,由于水平有限,若有不正确的地方还望批评指正。

轻量化的 View Controller

分离 Data Source

著名博客 objc.io 的第一篇期刊中介绍了如何对 View Controller 进行瘦身,提出了将 UITableViewUICollectionView 的 Data Source 部分从 View Controller 中单独分离出去的实践,并且提供了示例代码。不过示例代码非常简单,需要进一步阐述清楚这个概念。

在 Percolator 进行重构时我反复测试,最终得出了一种遵循此概念且易于实现和扩展的结构:

分离 Data Source

箭尾对象拥有箭头所指对象,实线为强引用,虚线为弱引用,Controller 强引用 DataSource 与 Model 实例,这里没有画出。在实现时,我们将 Model 实现一个自定 DataProvider 协议,代码如下:

1
2
3
4
5
6
7
8
9
10
11
protocol DataProvider: class {

associatedtype ItemType

func numberOfSections() -> Int
func numberOfItems(in section: Int) -> Int

func item(at indexPath: IndexPath) -> ItemType
func identifier(at indexPath: IndexPath) -> String

}

之后我们创建一个支援泛型的类 TableViewDataSource(一个包含成员 model 的自定类 DataSource 的子类),并利用 DataProvider 所提供的方法实现 UITableViewDataSource 协议,其头部声明如下:

Read More

OpenGL 纹理漫谈

2D 纹理映射

纹理映射一般是在图形的坐标值已经计算完成时附加在顶点信息上的,而非对纹理进行透视、变换再显示。下面逐步介绍如何在 OpenGL 中开启纹理映射,它的顺序有先后但并不唯一,若是想颠倒一下顺序也不妨试试看会有什么效果🙃️

读取纹理图

纹理图的格式一般为原始 RGB 格式,一个字节(8 bits)代表一种颜色通道,一个像素的颜色用三个字节表示,例如白色记为 #FFFFFF,是不是很熟悉,这种表示方式即 RGB888,由或者称其为 24 位真彩色。除此之外还有一些其他常用的格式:RGBA8888,RGB565,RGBA5551……

纹理图通常是正方形,若图形 API 没有严格限制,也可以为矩形。大多数图形卡及图形 API 为了效率的原因,限制图像分辨率为 2 的幂,例如 256*256,具体要求请参考文档。

Photoshop 导出原始 RGB 图像

这里我们使用 Photoshop 将图片裁剪成 512*512 分辨率,然后将其导出为原始 RGB 格式的图像,只要在存储时选择 Photoshop raw 格式即可(别和单反的那种 RAW 搞混了哦)。导出后的文件记录着自左上角向右下角逐行扫描的 RGB 信息。

查看 RAW 文件

使用十六进制文本查看器查看导出的文件,可以看到共 512*512*3 = 786432 字节,查看前三个字节组成的颜色 #444135,大致是深褐色。

Read More

OpenGL 鼠标拾取漫谈

OpenGL 有多种方法可以实现鼠标拾取物体,其中的两种方法比较常用。第一种通常被认为是标准的选择方法,它跟踪被选中像素或包括其周围的一小片区域的所有对象,接下来具体介绍这种方法。第二种是选取一个颜色集,用互不相同的颜色绘制可以被选中的对象,通过检查选中点的颜色缓存的颜色来识别出选择对象。

对象

我们抽象地认为在 glBegin(…)glEnd() 之间绘制的图形为一个对象,它存在于世界坐标系中。通过投影和变换将其转化为图像绘制在屏幕上后,如何通过鼠标选中的屏幕上的像素点来选中一个存在于三维世界坐标系的物体呢,OpenGL 提出了一种强大的解决方案供我们使用。

名称

首先我们区分出可以被选中的物体,将它赋予一个名称(name),在绘制过程中,如果有名称的对象覆盖了选中的像素点,则将名称保存到一个选择缓存中。有趣的是,我们还可以使用层次结构的名称来命名一个物体,对于一辆汽车,我们可以说选中了一个车门,或者选中了一辆汽车,这为我们调整选中的精细度提供了可能。

虽然说是一个 name,但实际上是一个无符号整形(GLuint),用枚举来表示的话确实和名称差不多,嘛~

名称栈

OpenGL 使用栈来保存名称,称其为名称栈(name stack),当绘制到选中的像素点时,则将栈中的全部信息都保存到选择缓存内,理解这个操作对理解拾取的实现是至关重要的。上面所说的层次结构就是使用压栈、出栈的方法实现的。

选择缓存

选择缓存是一个 GLuint 类型的数组,由用户创建,通过 glSelectBuffer(GLsizei size, GLuint *buffer) 函数完成缓存设置。设置完毕后,API 在绘制时将会自动完成名称的记录工作,选择缓存的结构大致如下:

选择缓存

当对象绘制过程中覆盖到选中点时,名称栈中的信息将自动添加到选择缓存中,名称表的长度由名称数确定,若多于一个名称的,按名称添加的层次顺序排序,即先添加(入栈)的名称排在前;zminzmax 记录该对象在深度缓冲中的高度,离视点越远数值越大。

选择缓存中的每一个对象的相关信息可由 [3+N] 个无符号整型值表示,当我们不使用层次结构的名称来命名时,可以认为第 i 个对象的名称为 buf[i][3]。

Read More

可视化超立方体

读过《思考的乐趣》的朋友一定还记得最后一章中那个令人匪夷所思的四维超立方体(hypercube),生活在三维世界中的我们居然也可以去想象更高维的空间,真是有趣。

建模

可视化的第一步就是建模,类似于三维立方体,用四维坐标(x, y, z, w)表示超立方体的顶点。按照每增加一个维度,顶点数翻倍的规律来看,超立方体的顶点是立方体的两倍,即 16 个顶点。一种典型的超立方体的所有顶点可由下表列出。

Read More

Linux 汇编初体验与计算的目的

说起来真的让人难以置信,我校汇编教材是老师自己写自己印的,拿到手的时候不由得想起高中在学校自己的印刷室里帮老师装订复习资料的场景……

好吧,Windows 系统下 的 DEBUG 只能说是勉强够用。先在记事本里写好程序,之后一行行敲入 DEBUG 下,调试起来甚至会简单一些。更进一步,用 MASA 和 LINK 也可以直接将汇编源文件编译成可执行文件。不过这里我以 Linux 为基础,如果对汇编有兴趣,可以尝试着写几个有用的程序,比如说~嗯, 0A0D 0A 间自由转换的转换器?

> 从字节层面看文本文件,Windows 操作系统以 0D 0A(16进制)作为换行标记(End of Line,EOL),而 Linux 则以 0A 作为 EOL。于是,Linux 下编写的文本文件在 Windows 的记事本中会丢失换行符。乱码?噢,不不不,那是因为 Linux 以 Unicode 保存汉字,而 Windows 记事本以 GBK 编码去解析造成的,对于中文世界,情况大多如此。

Read More