Sucha's Blog ~ Archive for December, 2019

19年12月28日 周六 22:09

LWTheme theme/skin manager for iOS8+

LWTheme 是一个支持 iOS8+ 的主题管理库,目前支持诸如以下 UI 元素的属性如颜色、字体随着主题替换而动态更改:

其他的 UI 元素也可以通过统一的底层接口,以及相关工具类,很容易为其添加相关主题管理属性,为已有的设置项增加换肤功能(其实就其底层提供的数据存储、通知能力来说,完全可以扩展到其他方面,不仅仅是换肤啦)。

起因

这个想法,起始于一个支持 iOS13 的 Dark mode 工作中,使用到的一款换肤库真的很不好用,需要花一些时间学习如何为不同的主题属性做设置,注意这里的主题属性居然可以有多个设置入口,另外虽然通过 plist 管理但实际使用时却需要通过一个中间工具来映射 key。一开始使用时,只是有点绕脑,后面觉得其接口设计,以及底层存储通知也许可以用更直接的方式。只是相关工作已经接近尾声,而我还需要更多的时间来实验。

实验的结果,是输出了这个主题管理库,另外为了学习 Swift,专门弄了个 Swift 版本,只不过纯 Swift 其实不支持在 extend 中动态添加存储属性,最后还是得用 @objc 来处理。

上面说到的这款换肤库,也许是 SwiftTheme 的 ObjC 版本。

接口改进

说下 LWTheme 的接口改进::

设置颜色等主题属性,其实是设置一个不同主题下的映射 key,比如

而换主题,或者说换肤时,接口是

LWThemeManager.sharedInstance useMode:LWThemeMode_Light withThemeMap:(NSDictionary *)themeMap

仅仅提供一个设置 NSDictionary 的接口就够了,用户可以自己添加更多的 mode 来区分不同的主题,只是用于业务区分而已,一旦更新 themeMap,就会触发主题替换通知,跟 mode 没有关系。

仅仅提供设置 NSDictionary 的接口,是因为读取 plist 或者网络数据、或者其他格式序列化数据,比如 KeyArchived 不是这个库的功能,它只管换肤,至于数据来源,跟它一点关系都没有,就应该剥离。

主题字典定义

各个主题下具体对应的背景色、图片、字体描述,dictionary 记录的格式,或者说内容,大概是这样:

Light Mode

{
    @"Color" : {
                    @"ViewBackgroundThemeColorKey" : @"#FF0000",
                    @"ButtonThemeTintColorKey" : @"FF0000".
    },
    @"Image" : {
                    @"ImageViewThemeImageNamedKey" : "light_image.png",
    },
    @"Font" : {
                    @"LabelThemeFontKey" : @"_:22",
    }
}

Dark Mode

{
    @"Color" : {
                   @"ViewBackgroundThemeColorKey" : @"#00FF00",
                   @"ButtonThemeTintColorKey" : @"0000FF".
    },
    @"Image" : {
                   @"ImageViewThemeImageNamedKey" : "dark_image.png",
    },
    @"Font" : {
                   @"LabelThemeFontKey" : @"bold:22",
    }
}

会区分颜色、图片名、字体在不同的子 dictionary 下,另外字体的定义是 family:size,下划线表示用 regular,bold、italic、以及其他的font family,需要指定。

底层存储、通知实现

比如设置 lw_backgroundColor,通过 colorKey 拿到实际的 UIColor 后,会设置到 UIVIew.backgroundColor 中,然后调用下面这个底层接口:

- (void)lw_storeValue:(id)value forSetter:(SEL)setter;

这个接口描述了当主题变化时,会通过 setter 回调,传递 value,一个主题属性对应一个 setter。这样就完成了主题替换重新设置主题属性的功能。因此,实在不理解为何需要一个中间工具来映射,也许其精妙我还 get 不到吧。

另外,对于 UIButton 这样,设置字体颜色及 state 配对的属性,value 的的取值有考究,比如可以是一个以 state 为 key 的 Dictionary;而诸如设置 UIButton 的 image tint color 接口,会复杂一些,考虑到 color 可以有 alpha channel,这里需要区分 state 保存原来的 image,colorKey,当主题替换时,需要取当前 colorKey 对应的 UIColor 来 tint 原来的 image,避免当前 image 被带 alpha 的 UIColor 污染后,无法复原。

下面这个接口可以获取 UI 元素存储的主题属性:

- (id)lw_valueForSetter:(SEL)setter;

核心及扩展

可以看到,库的核心是底层的存储、通知实现,这样就支持了 UI 元素为不同的主题属性,仅仅暴露了一个配置主题命名 key 的接口。这两个部分,在主题替换这个前提下,是可以不变的。

变化的部分是,为不同的 UI 元素,或者同一个 UI 元素,添加主题属性。比如把 CALayer 的 cornerRadius 作为主题属性,当主题变化时,配置不同的 cornerRadius,当然容易就可以实现。

结语是,只在模拟器上面跑了一下,应该没啥问题吧,:)

19年12月8日 周日 12:37

Syncthing

早就想着找一个存储备份照片的工具,虽然有很多在云端保存图片的网站、APP,但所有的这些都有着同样的问题,就是当忘记密码后,哭都来不及。即便绑定了邮箱、手机,也只是多了一层安慰而已。典型的就是雅虎中国gg,我的好多信息都绑定在上面,如果不是某次上去看到消息,真的是要哭死。

还有实际上数字内容虽然提供了方便,比如手机、电脑都可以随便通过网络访问,但是呢,如果不在本地保留一个备份,总觉得这个数字内容在物理上不属于自己。举一个不恰当的例子,你没犯错,机房也有被封的风险,最后即便数据有保全,优先权也不在你这,数据求索之路应该不会很短。所以,也算是一个洁癖的痛苦吧。

以上的讨论不包括安全,讨论安全很危险,还有,对于大部分人来说如果安全只是类似于账号、密码这样的安慰,那就太无聊了。

这里介绍一个内容同步工具,我主要是用于局域网内多设备同步,只是目前设备不包括手机,我觉得就存储来说,手机不是一个很好的设备。再说我有好几台拼凑起来的旧电脑,包括 Mac、Linux、FreeBSD,用这个工具还挺方便的。

大概介绍一下 Syncthing 吧,这是一个去中心化的,基于内容块(类似 BitTorrent)的数据同步、分发工具,注意去中心化跟分布式有本质的不同,后面再详细区分。

支持局域网、广域网的内容同步,可以 NAT 穿透。搜索、数据传输,以及客户端本地文件的管理,在这个软件中是分别用不同的子程序处理的。

由于是开源的 go 编写的软件,我们可以看到整个程序的结构(其实 Wiki 有介绍),甚至可以改写其底层传输协议,不过这里不涉及。

比如客户端发现,用专门的 discov 程序,在客户端配置一节,默认是搜索一个 default 列表的 discov 网站,实际上这里可以填写自己配置的 discov 地址,比如,我只希望发现自己配置的其他的客户端,并做同步。对了,discov 是可以配置数字证书的。

数据传输层,如果是局域网,客户端是可以直接传输的。但是对于一些 NAT 内部的客户端,需要其他客户端帮忙传输的,有一层叫 relay 专门用于数据传输,配置界面上也可以配置为仅仅用于 relay,就是我只传递,但是不保存在本地。

因为是去中心化的客户端,并基于内容块的传输,所以,当你的配置的客户端越多,同步的数据量越大,这个优势也就越大。因为类似 BT,你只在其中一台设备上加了内容,其他的设备,都会优先下载还未下载的内容块,这样,对于后续其他未下载的部分,它们会相互查询、传递。

说下在旧机器上配置自动启动,不管是 Linux、FreeBSD,都不算顺利,一方面是 Unix 特有的多用户权限,另外就是 SystemV、RunScript 在启动配置的不同。我之前操作过其他软件的启动,好些年后再配置,又得重头再走一次,痛苦。

在最后放出配置界面之前,细说一下之前提到的分布式、去中心化的不同。

现在所有涉及到云的都是分布式(distributed),特点是对外表现为一个整体,提供高速响应、高可靠性,但是却可以处理任何单一服务器都无法处理的大规模的请求量、数据量。因为内部需要大量的普通服务器做实际业务处理,这里就涉及到了任务调度、数据同步、可靠性保证等等一系列的问题,为了实现这些基础的能力,需要把不同数据中心、机房的大量的服务器集合起来调度,这就是分布式。分布式是为了对外表现为一个响应的统一整体,因规模变大后而选择的方案。

而去中心化(decentralized)则完全不同,它本质上就不是作为统一整体来响应的,而在当前的技术能力上,做不到分布式统一整体表现的高响应。在设计上,是一个自私的系统,就是只区分我和其他。在设计上,分布式把复杂度放在了系统内部,而去中心化则把复杂度放在了 agent 的交互,总觉得这种交互有很多很多的可能,好玩极了。

最后附录一个配置界面的图片,通过网页配置挺方便的,还可以看到进度。