Sucha's Blog ~ Welcome

25年10月19日 周日 22:54

Flutter(1)

各种原因,换了一家公司,原来那家创业公司8月底老板突然内部会议上说发不出下个月工资了(-_-),我就火速离职了,毕竟 App 当时也做的差不多了,我也没法举债上班,毕竟没股份。

也不知道新公司怎么看得上我,因为我之前没有 Flutter 的经验,只有 Swift + ObjC 的经验,ObjC 和 Swift 都用过连续 4+ 年左右,总的 iOS 也有 10+ 年 的经验了。

我第一周是从 dart 语言看起的,看得头大,第一周收获不大,老板可不会等你那么久,于是第二周是直接上手做页面了,其实 dart 语言只懂皮毛,开了 4 个 AI 页面,东问西问,之所以要开那么多,是因为有些问题需要缓存着,同一个问题需要从不同细节问,毕竟一个细节,就能卡住我好久。

同事的 Flutter 经验很足,而且搭建了页面框架,所以我只是画几个 widget,以及链接一下数据,搞一下 ListView 就把第一个页面做好了,看起来也还行。再说当时产品催了一下 UI 了吧,给的我是详细设计稿,所以我可闲不下来。

其实这周开始是有点困难的,因为一开始想着不如先搞个导航模块,当时对 Flutter 还不熟悉,就开始吭呲吭呲地搞,我之前用 Swift,习惯了导航模式带参数,用的 enum,比如

enum ModuleExamplNav {
    case pageIndex(param Int, param String)
}

类似这样,但是 dart 语言的 enum 只能带同一个类型的参数,与 swift 差异很大,如果用 @freezed 库是可以的,但是后面还遇到了 build_runner 生成的映射文件居然编译通不过的问题,这就相当坑了;还有同事调研了一下 freezed,觉得目前项目并不很需要这个依赖库,并未打算采用,那这条路估计就断了。

还有 iOS 的导航,基本上是页面的 push、pop、present、dismiss,也就是 navigation controller 在哪一个层级的问题,有多少级的 navigation controller,比如 tab controller 第一级就是 navigation controller,之后 present 了一个页面,为了 present 之后还能继续 push,其实 present 的是一个 navigation controller,其内容才是 present 展示的。

navigation controller 包含了页面栈的层级,本来就包含了需要展示的内容,只是动画过程中,layer 分成了 model layer、presenter layer、以及 render layer(这个我没用过,查了一下是私有的,知道概念就行),前两者我们是能够代码操作的,只是时机不同。

但是在 flutter 中,所有展示相关的都是 widget,虽然他们功能大不相同,以及不一定在最终展示上显示,只是在逻辑概念上、layout 上需要用到,毕竟 Flutter 是描述性的 UI,类似 SwiftUI。

先说导航,对比 iOS 导航,flutter 的导航分成两部分,一部分是 Navigator,用到的比如 Navigator.of(context) 就是它,是管理页面层级的,另外一部分,是 push 的内容,包含 PageRoute,比如 MaterialPageRoute,是管理转场动画的,然后对比 push、pop、present、dismiss 其实都还好理解,但是有时候,如果需要 present 后再 push 盖住 present,其实是用之前页面的 context 来做的,目前我还不知道有没有其他的方案,这个造成 pop 到某一个页面的过程中,貌似需要用到一些技巧,但不确定是好的解法,毕竟我的 flutter 经验还不足。

还有就是页面具体细节相关的了,先是 layout,Expanded 的 parent 必须是 row、column 之类的,然后如果 layout 过程中,某些 widget 需要用到 parent 的宽高,不像 UIKit 中,在 layoutSubviews 里面操作就行了,即便是 build context 也没有带下来信息,毕竟 build context 的时候,widget 并未挂接到渲染树上,此时需要用 LayoutBuilder,所以在 build 里面 LayoutBuilder 虽然占用了了一个缩进,但是只是为了拿一个 parent widget 的宽高之类的。

同样的还有 align、padding,所以我搞了一个 extention WidgetExt on Widget,用来放链式配置,有些不重要的 layout,没必要占用一个缩进。

工作的第一个页面,还涉及到了 gestue,flutter 的 gesture 还好,相对 iOS 是挺好理解的,但是也跟 iOS 有一些平台特殊的逻辑,比如 iOS 的 attributedString,参数就有 link 的,flutter 这边则使用 RichText,带的 TextSpan 就有 recognizer 参数,但是呢,如果 TextSpan 有 children 带了一堆 TextSpan,那不好意思,这个容器类似的 TextSpan recognizer 就接收不到响应了,都被其 children 响应了,即便我在 children 的 TextSpan 没有一个填了 recognizer。

还有居然区分了 StatelessWidget StatefullWidget,这个我还不是很理解,虽然也有用 State 搞了几个,以及页面状态管理用的是 riverpod,用 WidgetRef 来 watch、read 和 write 数据并更新页面。

之前是习惯了 RxSwift 或者 Combine,现在又刷新了,前端 3 年一小变、5 年一大变,哭。。。

25年10月19日 周日 20:31

喀什六天行

有空再写吧,这里留个引子。。。

25年9月20日 周六 23:20

机器人表情 - 情感沟通

在 2025 年,真的难以想象,机器人对于表情的控制管理已经到了这种程度:你好,很高兴在物理世界与你重逢

一方面是微电机控制,脸部框架骨骼等,另外就是程序算法部分。据介绍,这部分是依赖了大语言模型,机器人可以对着镜子来关联具体哪个位置,什么程度的表情控制,对于视觉的表现是如何的,另外一部分,则是根据视频来学习,模拟人的表情,跟刚建立的控制关联对应起来。

大语言模型还有语音的模仿,语义情感的理解,模型构建了这一整套的对应关系。

机器人的观察如此细微,对于任何一个微表情都不会错过,立即反应。

它是么得感情,但是有了大语言模型,它最懂你。

25年8月30日 周六 21:56

iOS GCD (Grand Central Dispatch)的一些探索

面试遇到了相关问题,答得很烂,才发现自己之前只是粗浅地使用,并未理解。

代码是类似这样的

- (void)exampleFunction {
    dispatch_queue_t queue = dispatch_queue_create(@"test_queue", NULL);
    NSLog(@"1");
    dispatch_queue_async(queue, ^{
        NSLog(@"2");
    });
    NSLog(@"3");
    dispatch_queue_sync(queue, ^{
        NSLog(@"4");
    });
    NSLog(@"5");
}

那么其输出顺序将是:

1
3
2
4
5

更进一步,dispatch_queue_create 如果不指定绑定的 queue,那么其运行就是当前发起 queue 的空间。

看了一下 GCD 的源码,不甚懂,太多条件编译了,以及宏代码。

问了一下 AI,pthread、NSThread、GCD、NSOperationQueue 的区别,大概如下

在 iOS 上用的最多的是 GCD,GCD 创建的时候,可以设定是串行、并行,优先级等,但不能指定其底层 thread,或者关联到自己创建的 NSThread 上。

若提交的任务闭包是一个死循环,那么运行该闭包的 dispatch_queue 会卡住,关联的 thread 会吃满能够分配到的 CPU 资源,才被调度出去。

以上串行 queue 的调度逻辑,体现在上面例子代码“2”、“4”的输出顺序上,async、sync 闭包运行的 queue 空间,也是该 queue 创建时候的空间(有另外的 create 接口,提供关联的管理queue,比如不同层级的优先级queue)。

但如果是并行 queue,提交的任务闭包,就有可能被同时调度运行,具体跟该体系的 CPU 核心数量、线程调度资源有关系,但被重入是无法避免的。为了保证数据的一致性,提交到并行 queue 的任务闭包,得自己保证数据一致性。

NSOperationQueue 由于底层仍然是关联的 GCD dispatch_queue,所以逻辑是一样的。

以上,可以看到 GCD 是关注闭包,即逻辑关联的数据,而不需要关注线程空间的创建、销毁、挂起、恢复、CPU 的调度和分配逻辑。

GCD 内建了比如主队列(UI相关)、默认队列、后台队列等等共 5 种 QOS 分配策略的队列。

如果是自己创建的 pthread、NSThread 得通过相关接口,才能设置线程的优先级,但另一方面,如果有些业务逻辑包含其依赖的数据,在一个死循环里面更好处理,那不如就直接用 NSThread,或许比 GCD 的分配更好吧。

25年8月30日 周六 21:56

交管12123——学法减分

之前老家的车被我开到了深圳,住的离东莞很近,就经常到东莞的超市买东西,反正车一周总要热一次,补电瓶的电的。

经常是下班回来后过去,都 20:30 之后了,那次是看到了红灯,但没管那么多,跟着人家粤S的车后面右转,这回是记 6 分,200 块。

好在 交管12123 app 上,可以学法减分,上面会红点提示,但是如果一次被扣 12 分,就得妥妥的重新考试拿驾照了吧,还有,一年内考试最多可以补 6 分,所以,一年内是有 18 分可以扣的?

所谓的学法减分,首先你得被扣分了,然后才能看减分入口,有网上学习,以及线下公益活动可选。这么热的天,还是学法减分好,首先得申请,通过之后才能看警示视频,之后是考试,20 道题错题少于3道才能过,只能补考一次,时间限制没那么严。

看警示视频每次至少 30 分钟,间隔 5 分钟上报时间,中间有人脸识别流程,时间到了会提示,可以进入下一个环节,进行考试了。

我裸考发现很难考,还是得多学习才能考试通过的。

25年8月30日 周六 21:36

凡人修仙传(3)

电子书凡人修仙传我已经看到慕兰人从对抗到联合了,但只可惜动画进度很慢,才到了慕沛灵自爆贴身侍女这里,韩立从闭关中出来。

谁想到,电视剧都有了,不过我没有会员,都是从夸克下载的,也是断断续续地下载,不是 4k 的不看,要不 27 寸屏幕看着就太粗糙了,蓝瘦。

电视剧的人物南宫婉的嘴是歪的,有仙女之气,但金晨来演,有点太冷了,我先看的是动画,原著里面没有那么多吃醋的场景,动画最多,哈哈哈,电视剧里面人物太冷了,从动画过来的,有点不习惯。

其实看过原著就知道,电视剧很多是借鉴动画的。

比如韩立的师傅李化元,原著里面对于其陨落,是在慕兰人入侵之后,当时韩立已经是落云宗长老了,见到了黄枫谷的弟子们,才知道的;动画里面,李还因为红佛跟云露老祖对抗,戏份还是很多的;电视剧因为篇幅问题吧,这段比较少。

电视剧篇幅的原因,只有 30 集,以韩立从古传送阵离开天南为结束;结束时,也跟动画剧情一样,被王婵追杀,最后王婵看着韩立从古传送阵离开。

电视剧的动画效果,跟动画版比起来,大部分是不如的,不过也有出彩的部分,毕竟真人演,如果有不习惯动画的,真人可以过渡一下。

另外,从凡人的篇幅看,真人版应该也会拍外海的吧,然后看情况也会有其他篇的。

总的来说,还行吧,能看,但看过动画的,就没必要看电视剧了,动画才是最雕的。

25年7月28日 周日 13:53

泥潭

朋友给介绍的小说,这本小说的特点在于故事线,分三个部分,分别从同一事件的三个不同人物(角色)来围绕故事展开。

故事的背景是辛亥革命,主角 1 是其中的保皇派,主角 2 是其中的光复会成员(刚开始留日的时候是带着小辫子,之后逐渐转向了革命),主角 3 是神父,三个不同角色用不同的叙述、表现方式,展开了故事。

故事还可以,最特别的是三个角色,三种不同的叙述方式,以及三条时间线,值得一看吧。

不过,故事叙述比较宏大,像我,得花不少时间才能理清其中的故事线了。

25年7月19日 周六 20:32

目录文件索引思考

比如录像文件循环覆盖的逻辑,按照日分割建立文件夹,之后需要考虑如果存储空间不够,需要删除文件或目录,以及按照时间戳可以快速定位文件。

目前了解到的是都只有纯目录、文件的遍历操作,以及没有空间之后,删除日目录下,某个二级目录,作为存储空间的释放(获取)逻辑。

纯目录、文件的遍历操作,因为是嵌入式,nand 访问 IO 口速率比较低,以及 cpu 较差,所以时间上很慢,接口上为了适应这个限制,限制为 255 个文件后一次返回,之后客户端再次请求,传递进去的参数,是上一次返回的最后一个节点的结束时间。

如果类似 windows thumbs 或者 mac os DS_Store 的缓存文件索引策略,我觉得可以改善这个问题。

创建资源文件前,先创建(更新)索引文件,标记为待完成,资源文件完成后 ,再标记为已完成(open 接口,bit 反转),删除文件目录释放存储空间的时候,也同样更新相关标记就行。

若索引文件损坏或者不可用,逻辑上可以回退到纯目录、文件的遍历操作。

特别是文件查找,如果定义索引文件在日目录一级,日目录里面的文件可以根据二分查找,是相当快的,应该比目录遍历快得多,毕竟只需要读取一个索引文件。

索引文件的更新可能导致损坏,因此我才建议用 open 接口,bit 翻转,用先、后标记来保证,如果没有后标记,更精细的做法是校验相关资源文件,若文件是完整的,则可以更新后标记。

同样的,删除也可以用先、后标记来做,这个都没什么问题。

我觉得这样的操作是安全的,以及通过索引文件来定位、遍历资源文件内容是高效的。

25年7月13日 周日 23:11

嵌入式系统文件更新的一些思考

目前公司产品(一款嵌入式产品)的固件更新,是使用刷固件(实际上是写固定大小的 jffs2 分区)来做的,大概了解到其更新逻辑是,校验下载后的固件压缩包,校验解压后的固件文件系统包,重启更新(其实还需要教研一次固件文件系统包),刷固件到固定大小的 nand flash 分区(这个时候还需要校验一次),之后再次重启,挂载相关的 jffs2 分区。

实话说,相对于后端 go 这样 static linked 的程序更新(shipping),固件程序的更新实在是太麻烦了,一方面 nand 存储不可靠(虽然 SD 卡等 nand 存储,主控本身保证了数据安全),另外一方面,文件系统挂载为只读处理也是问题,以及刷机,多次重启,很耗费时间,从最开始的 5 分钟慢慢成了现在的 10 分钟。

所以,我想能否改成这样的逻辑:

比如类似 nginx -r 重启加载某个进程(假设都是 linux 的系统),一个 master 进程,下面多个 app 进程(看业务需求),更新逻辑是:

这样,用多进程的方式来进行热更新,其效率会比目前冷启动的效率高得多,当然这里面有不少细节需要确认

这样 HOST 仍能随时更新固件里面的业务进程,这要比单纯仅只读 jffs2 挂载的业务分区,更新的效率高得多。

当然,上述的想法,是基于一些我对固件更新的业务逻辑的了解上,做出的一些改进。

距离我上次在福建做类似的固件开发(linux 嵌入式业务开发),已经超过了 13 年,之前的两个不同产品的更新,一个是完全依赖网络(网卡的 IPXE),一个是 EEPROM,然后再带 nand flash(忘记多大了),跟现在的应该是有差别。

大概思路是这样,如果有可能的话,我觉得改进的效率是很高的,应该要比现在 10 分钟起步的更新快得多得多,但是要求是 linux 的多进程管理,以及同步的能力吧。

25年6月15日 周日 21:15

凡人修仙传(2)

是从 B 站的动画开始看的,一开始的动画有点粗糙,到后来大概八九十集,还是百来集的时候,4K 动画的精致程度,已经无与伦比,故事情节更是扣人心弦,每周一集的更新进度,让人等不及。

于是开始看小说了,之前买的汉王 10.3 寸屏纸质书开始派上用场,看书的效果、效率、手感都很不错,相当好评,只是我看小说的进度还是偏慢,大概只看了 1/10,远远赶不上动画的进度。

这篇网络小说我是大大推荐的,故事情节很不错。

当然动画更是大大推荐,故事情节相比小说,还优化了,动画效果更进一步,我觉得当前同期的动画,灵笼第二季的动画效果也远比不上现在的凡人修仙传,不过凡人修仙传每集的长度大概 20 分钟,灵笼大概有 40 分钟,以及凡人修仙传也不是每个角色都能得到这样精致的动效,只有稍微中心点的人物才会得到这样的资源投入。

25年5月24日 周六 19:55

iOS 下图表的绘制

工作中碰到,没有用之前支持 K 线的第三方库,而是自己摸索了一下,因为需求也没有高的要求。

UIBezierPath 曲线的平滑

用直线绘制其实也是可以的,相交部分用圆角过度还行,但只就美观效果来说,跟曲线的拟合相比差远了。

可惜 iOS 下绘制曲线用的 UIBezierPath 仅支持有限点的控制,后台下发的数据可不止那几个点,这个时候就用到下面说的曲线的平滑了。

网上说到的曲线平滑算法有不少,而我只会使用 UIBezierPath 来绘制,github 上找到的有 2、3 个都是可以用的,比如下面这个:

绘制出来的还是挺好看的,唯一的缺点在于,虽然都经过了数据点,但数据点并不是在区域范围的极值点上,也就是说,数据点在曲线里面不是曲线的最高、最低点,曲线的最高、最低值,在数据集的范围之外,这就挺不好的。

不过就曲线平滑这一点看,上面这个库还是很好用的。

X、Y 值的放大、缩小比例

二维的图表,为了在一张图表上表示所有的数据点,X、Y 轴上是需要刻度单位的,在 app 上虽然各图表的大小都一样,但是可能喂进去的数据各不相同,很多时候,并不需要同样的刻度来表现曲线,否则有的曲线,在刻度间隔较大的情况下,视觉上更像一条直线,表现不出具体的变化了。

这个时候需要将 Y 轴的刻度变小,以上原因,不管是 X 轴、Y 轴,都需要根据数据集的范围,选择一个合理的刻度变化(在 UI 大小相同、而数据集不同的情况下)。

数据集到 UI、UI 到数据集的比例映射

我记得之前看的第三方库,用的是 matrix 来做变换的,我自己操作的过程中,没有懂这个,反正都是线性的,索性单独建立 X、Y 轴的比例映射了。

就是转换的时候麻烦一点,显示的时候是 data -> ui mapping,用户选择图表点的时候,是 ui -> data mapping。

以上,就是手搓 iOS 图表绘制的几个要点了,最困难的点,是使用 UIBezierPath 曲线来平滑,得用到看不懂的代码,可选的方案是绘制直线,转交点用 round 来做,效果吧,勉勉强强,也是能用的。