这是季先生万年在医院疗养期间亲自编选的作品集。文集收录了季先生在人生各个阶段的代表作品。从文体上说,涵盖散文、随笔、邮寄、回忆录、日记、讲演报告等。
在读书的过程中,遇到一些自觉有趣的文字,就随手摘录,并作感想。这里同季先生选编文集一样,将这些摘录和感想选编在一起,形成此文。
时光流逝,一转眼,自己已经到了望九之年,活得远远超过了我的预算。有人认为长寿是福,我看也不尽然。人活得大久了,对人生的种种相、众生的种种相,看得透透彻彻,反而鼓舞时少,叹息时多,远不如早一点离开人世这个是非之地,落一个耳根清净。
……
话说到这里,我想把上面说的意思简短扼要地归纳一下:如果人生真有意义与价值的话,其意义与价值就在于对人类发展的承上启下、承前启后的责任感。
——《人生的意义与价值》 季羡林
季先生的文字还是有趣的。
「活得超过了预算」初看到真的会笑死,然后反思自己好像没有做这样的预算,又继而想是否有必要做这样的预算?乐而又乐。
「被困于名缰,被缚于利索」这一句容易理解,但应该惊叹大师练字的本领。名缰利索算是一般常见的词。单独用,大体上也就感觉是有那么个东西。拆开使用平添不少意味,「作茧自缚」的感觉被加强,终知名缰利索是自困、自缚,而不是他困、他缚。
文末的总结很有中华文人的古典气质:追求传世,追求大同。
若说印度之类的国家修来世(做好事是为了来生转世得好缘法),沙漠三教修今世(做好事是为了今生赎罪得上天堂),那中华文化圈就是修传世。即是说,人总要留下或是血脉传承或是文脉传承的东西,为民族、人类的发展留下自己的印记。而若再进步一点者,可以推动人类向前,那便可以在生命最后一刻坦然地说「我已尽到责任」了。
现在杨女士却对我垂青,要我作“哲学思考”,侈谈“禅趣”,我焉得不诚惶诚恐呢?这就是我把来信搁置不答的真正原因。我的如意算盘是,我稍搁置,杨女士担当编辑重任,时间一久,就会把此事忘掉,我就可以逍遥自在了。
然而事实却大出我意料,她不但没有忘掉,而且打来长途电话,直捣黄龙,令我无所逃于天地之间。我有点惭愧,又有点惶恐。但是,心里想的却是:按既定方针办。我连忙解释,说我写惯了考据文章。关于“禅”,我只写过一篇东西,而且是被赶上了架才写的,当然属于“野狐”一类。我对她说了许多话,实际上却是“居心不良”,想推掉了事,还我一个逍遥自在身。
可是我万万没有想到,正当我颇为得意的时候,杨女士的长途电话又来了,而且还是两次。昔者刘先主三顾茅庐,躬请卧龙先生出山,共图霸业。藐予小子,焉敢望卧龙先生项背!三请而仍拒,岂不是太不识相了吗?我痛自谴责,要下决心认真对待此事了。我拟了一个初步选目。过后自己一看,觉得好笑,选的仍然多是考据的东西。我大概已经病入膏肓,脑袋瓜变成了花岗岩,已经快到不可救药的程度了。于是决心改弦更张,又得我多年的助手李铮先生之助,终于选成了现在这个样子。这里面不能说没有涉及禅趣,也不能说没有涉及人生。但是,把这些文章综合起来看,我自己的印象是一碗京海杂烩。可这种东西为什么竟然敢拿出来给人看呢?自己“藏拙”不是更好吗?我的回答是:我在任何文章中讲的都是真话,我不讲半句谎话。而且我已经到了耄耋之年,一生并不是老走阳光大道,独木小桥我也走过不少。因此,酸、甜、苦、辣,悲、欢、离、合,我都尝了个够。发为文章,也许对读者,特别是青年读者,不无帮助。这就是我斗胆拿出来的原因。倘若读者—不管是老中青年—真正能从我在长达八十多年对生活的感悟中学到一点有益的东西,那我就十分满意了。至于杨女士来信中提到的那一些想法或者要求,我能否满足或者满足到什么程度,那就只好请杨女士自己来下判断了。
——《禅趣人生》 季羡林
如先前所说,季先生的文字属实有趣。今日始知这份有趣来自「不作伪」。
大师也有七情六欲,遇事也会有退缩逃避,然后打起如意算盘。
大师在如意算盘被瞧破之后,也会有惶恐(也许心虚脸红?)。
大师也会有思维定式,然后感叹自己的脑袋瓜变成了花岗岩。
真诚而不作伪是打动读者的「大杀器」,写文十年深以为然。然则想要真诚而不作伪,需得首先与自己握手言和,内照己心而圆融无碍。若不然,为了所谓的「面子」或者时下流行的「人设」,则难免带上偏见,从而在自知或者不自知的情况下作伪。
敌兵压境,应当振作起来,鼓励士兵,同仇敌忾,可是苻坚自己却先泄了气。这样的人不称为孱头,又称之为什么呢?结果留下了两句著名的话:“风声鹤唳,草木皆兵。”至今还流传在人民的口中,也可以说是流什么千古了。
——《论恐惧》季羡林
我快要被「流什么千古」笑死了。
文人笔墨,果然有趣。放着有「遗臭万年」不用,偏生要去用「流什么千古」(显然这里的什么代指臭)。用这种小朋友也能懂的小手法表达「遗臭万年」的含义,有多出俏皮和趣味。
对此我有自己的哲学基础:吃饭是为了自己,而穿衣则是为了别人。道理自明,不用辩证。哪有一个人穿着华丽,珠光宝气,天天坐在菱花镜前,顾影自怜?如果真正有的话,他或她距入疯人院的日期也不会远了。
——《衣着的款式》季羡林
至于衣着,更不在我考虑之列。在这方面,我是一个“利己主义者”。衣足以蔽体而已,何必追求豪华。一个人穿衣服,是给别人看的。如果一个人穿上十分豪华的衣服,打扮得珠光宝气,天天坐在穿衣镜前,自我欣赏,他(她)不是一个疯子,就是一个傻子。如果只是给别人去看,则观看者的审美能力和审美标准,千差万别,你满足了这一帮人,必然开罪于另一帮人,绝不能使人人都高兴,皆大欢喜。反不如我行我素,我就是这一身打扮,你爱看不看,反正我不能让你指挥我,我是个完全自由自主的人。
——《漫谈消费》季羡林
看到前一篇文章部分时候,我还在诧异什么叫做「穿衣是为了别人」。这与我心中所想相差颇大,也与认知中的季老影响相去甚远。
继续读到后一篇文章才明白季老想表达的意思。这个「给别人看的」其实是说,别人看看就好,至于看过之后意见如何则不必在意。
穿衣服嘛,蔽体、得体即可。
不管你意识到还是没有意识到,大自然还是把虚无缥缈的时间用具体的东西暗示给了人们。比如用日出日落标志出一天,用月亮的圆缺标志出一月,用四季(在印度是六季或者两季)标志出一年。
——《时间》季羡林
印度居然有六季的说法。原来蟪蛄不知春秋不只是说说。若在印度,我岂不就是阿成不知雨风吗?
查阅得印度一年分为六个季节,这六个季节分别是春季、夏季、雨季、秋季、风季、冬季。
春季(3月20——5月19) 金星主宰
夏季(5月20——7月19) 太阳与火星主宰
雨季(7月20——9月19) 月亮主宰
秋季(9月20——11月19) 水星主宰
风季(11月20——1月19) 木星主宰
冬季(1月20——3月19) 土星主宰
基本如图。这个应该是表意的,不是完全和实际情况对应的。
重点说明:
DX - 正排库,对前 300 条结果进行精细正排计算,会重新计算 proximity。这个模块我们当前(2016)是缺失的,我们只有前 100 条结果的title。对方描述里这个模块是非常重要的,是百度 12 年相关性提升最大的项目。
百度的基本流程应该是 BS(qsrchd/leaf)返回结果,merge 后对前 300 条在 DX 重新排序,返回给 AC,AC 继续重新排序,然后返回。
DX 和 AC 各有一个 GBRank 模型来负责排序。
特征的线性拟合:$Y = \sum_{i}\omega_if_i$。
效果:和手写规则基本打平。
好处:基本验证机器学习是可行的,同时,可以得到每个特征有多大作用,即每个特征的 weight,这个 weight 的概念对于理解机器学习有非常大的作用,在后面用非线性模型时,他们依然找到一种计算特征weight的方法,这个方法在debug中也起到很大的作用。
这个和我们用的lambdaMart基本类似。
基本思路和我们目前做的是一样的,但是各种细节还是不一样
Q: 百度如何评测
A: 测试集 ndcg + 人工 side by side + 线上小流量实验
Q:DX 的作用
A:DX 提供的正排对于计算 proximity,term 紧密度非常重要,是必不可少的一个模块,并且为后续的一些质量改进可能也提供了基础,对长尾 query 的提升非常大
Q:DX 具体存了什么
A:url 的正排,同时包括 entity/anchor 等,提到分域存储(这个回答貌似不是很全面)
Q:GBRank 如何控制 diff
A:用 bagging 来提升模型稳定性,可以是几个 bagging 的 GBRank,也可以是 GBRank 里面每棵树用 bagging 的方法来生成多个树。Bagging 的 GBDT 已经有人做过了,但是效果和非 bagging 的差不多,但是之前没有人从模型稳定性来看这个问题,从稳定性的角度来看,bagging 就比较重要了。
Q:ltr 的标注规模
A:DX 上升到 7--8w 时效果提升比较大,百度的整体标注规模在几十万 query 级别。每个 query 标注了 20 条。
Q:ltr 标注 query 是怎么选取的
A: 随机,偏向长尾,40%是长尾的。还说看长尾是怎样定义,貌似听见一个搜索次数小于 10 次。
Q:是否采用 active learning 来选取标注集
A: 没有,但是后来在 spam 的机器学习上有采用,因为 spam 的样本少。
Q:百度的标注人力是怎样的
A:全公司的标注是外包的,只需要在平台提交标注任务就可以了
Q:新特征的开发过程是怎样的,是否需要先经过评测,还是直接放到 GBRank 里,通过 debug 来发现特征是否符合预期
A: 新特征一般来讲还是要先经过评测,确保和 label 有一定的相关性
Q:一些需要组合的特征,是否直接加入模型
A:从理论上来讲,直接加入,期望模型学习出来各种组合是可以的,但百度不是这样干的,如果你觉得一些特征需要组合,最好手动组合,把组合后的特征加入到模型
Q:特征的 weight 是如何计算的
A:类似于算导数的方法,上面已经大概说过了。
Q:LTR 的工作方向,模型为主还是特征为主
A:经历不同时期,一开始模型为主,后面加入 DNN 后,特征变的很大,很难做,当然这些特征也是 LTR 团队做。LTR 团队一开始 4 个人,后面发展到接近 20 人。
Q:新特征的上线方式
A:如果另外一个团队升级了一个特征,一般来讲不重新训练,直接上线。如果是新加了特征,ltr 团队会负责重新训练。如果同时加了多个特征,也是 ltr 团队负责重新训练上线
Q:GBRank 上面是否还有 ranksvm
A: 没有
Q:DNN 的并行化方式
A:用的 IDL 的平台,100 多台机器?100 亿的数据规模, 一开始模型比较简单,经过 4--5 个月做出来后,效果非常好。后面也逐渐尝试 cnn,rnn 等
Q:目前我们 1 亿的数据,有啥建议
A:先把数据加到 10 亿,看看效果。他们在 1 亿的时候效果也不明显。可以把 1kw/5kw 数据时的结果拿来观察
Q:ltr 标注量多少比较合适
A:DX在标注到 7--8w 时有比较大的突破。 这个数据提到好几次,可能他们的实际情况确实是数据量提升到 7--8w 时产生了一个质变。
Q:DX 也是 GBRank
A:是,DX 也是用 GBRank 来排序
Q:AC 还有多少基于规则的排序
A:基本没有了,都被替换了,但是 AC 的上游 US 还有一些规则,例如合并 onebox 等
Q:百度结果里经常靠百度知道顶着,大搜索是否对百度知道有特殊处理,例如单字检索等
A:没有对百度知道特殊处理。但是百度知道是一个垂搜, US会请求。同时百度知道有很多站内的权重数据,可能做的比较好
Q:DNN 出来的值是作为特征加入到 GBRank 里的吗?
A:是的
Q:LTR 技术的发展过程
A:先是把 rank 的 LTR 做好,站住脚,然会向外输出技术,例如泛时效性 query 的识别,省略与 termweight、spam 等。
Q:DNN 的关键
A:数据量,必须上大数据量。与 msra 的 gaojianhong 交流是,对于 msra 只用了 1 亿的数据量很鄙视
在 iOS 15 以前,因为没有 SSV,所以用户可以在越狱后的设备上直接替换系统目录下的文件达成效果。实际上,当年的很多「字体插件」就是干的这么个事情。但从 iOS 15 开始,事情发生了变化。Apple 向 iOS 引入了在 macOS 上已经成熟的技术:SSV。用普通人能理解的话说,SSV 是一个在系统启动阶段的密码学校验机制。它会校验整个系统目录是否完整无修改,而如果校验失败则会拒绝启动(表现为反复白苹果)。因此,我们需要新的方案。
对于普通用户,目录映射理解为是一种可以达到「指鹿为马」效果的机制。即是说,当系统或者某个软件想要访问某个路径 A 时,通过建立 A 与 B 的映射关系,让系统或者软件实际访问到的是 B 而不自知。
以字体为例。iOS 系统所用的字体存放于 /System/Library/Fonts
这个目录下。显然,我们无法修改其中的内容,否则 SSV 校验失败,机器就变砖了。但是,我们可以做目录映射,比如将其映射到 /var/jb/System/Library/Fonts
这个目录下。其中 /var/jb 是越狱后我们有权限修改而无需担心破坏 SSV 的路径。这样一来,我们只需要修改 /var/jb/System/Library/Fonts
下的文件内容,就能让系统和软件读取到替换后的字体了。
但要注意,/var/jb/System/Library/Fonts
这个路径下原本是没有内容的。因此,建立这种映射关系的前提是要将原始目录(source)当中的内容拷贝一份到目标目录(target)中去,然后再建立这一映射关系。这是有代价的。尽管 iOS 使用的文件系统 APFS 有 Copy-on-Write 的能力,但它对跨 Volumn 的拷贝并不生效(而 /System
和 /var/jb
就是在不同 Volumn 中的)。因此,这种拷贝会占用系统的存储空间,尽管可用,但不是没有代价。
很遗憾,Fugu15 Max 的官方版本并不支持用户自定义的目录映射,而且大概率在将来也不会添加这一功能。因此,想要自定义目录映射的用户只能选择第三方修改的越狱包。这其中也存着风险——你并不知道第三方有没有往里面「加料」。
目前,已知支持用户自定义目录映射功能的有
普通用户只需安装上述越狱包即可开启自定义的目录映射。默认已开启映射的目录有:
/System/Library/Fonts
/System/Library/PrivateFrameworks/CoverSheet.framework/zh_CN.lproj
/System/Library/PrivateFrameworks/SpringBoardUIServices.framework/zh_CN.lproj
/System/Library/PrivateFrameworks/UserNotificationsUIKit.framework/zh_CN.lproj
这两个版本都已开源。并且,真皮的开源修改版我已完整检查过代码,确认没有加料。我自己的改版当然也是开源不加料的。这里要特别感谢真皮:我维护的版本里目录映射的功能代码最初就是来自真皮的开源改版。(俗称:抄代码)
以 AppleSymbols.ttf
这个字体为例。它是 Apple 官方提供的字体,包含了各种特殊符号;其默认路径位于 /System/Library/Fonts/Core/AppleSymbols.ttf
。
假使我们获得了一个改版的 AppleSymbols.ttf
文件(例如,网上广泛流传的所谓盲文修改版),就可以利用 Filza(可通过 TrollStore 安装)将它复制并粘贴到 /var/jb/System/Library/Fonts/Core/AppleSymbols.ttf
,然后注销(Restart SpringBoard)即可使其生效。
有时,你也可能获得一组字体。此时,若你想要替换,则需要逐个目录地对字体文件进行替换(可以多选文件,但是不要替换目录本身)。
我的修改版和真皮的修改版都支持用户自己添加更多映射目录。但这需要用户通过 Filza 自己修改相应的 plist 配置文件。
/var/mobile/prefixers.plist
,在 Root/source
下添加你想要映射的目录,然后重启再越狱即可。/var/mobile/newFakePath.plist
,在 Root/path
下添加你想要映射的目录,然后重启再越狱即可。进阶用户(需要会使用命令行)也可以做到无需重启越狱即可实现新增映射目录。
/var/mobile/update.prefixers.plist
,在 Root/source
下添加你想要映射的目录,然后在命令行执行 /var/jb/basebin/jbctl update_bindmount
即可。/var/mobile/otaFakePath.plist
,删除 Root/pat
h 下的已有目录,再添加你想要映射的目录,然后在命令行执行 /var/jb/basebin/jbctl OTAFake
即可。我也将修改并维护中文版的 Dopamine(多巴胺)。迁移到多巴胺后,用户需要在设置中打开「启用目录映射」再进行越狱。
若想要增加自定义的目录映射,则可按照同样的方法修改 /var/mobile/Library/Preferences/page.liam.prefixers.plist
(再次越狱生效),或是在越狱状态下修改 /var/mobile/Library/Preferences/update.page.liam.prefixers.plist
后执行命令 /var/jb/basebin/jbctl update_bindmount
以立即生效。
这里提供一个利用 Apple Configurator 安装的办法。
这种方法因为 GTA Car Tracker 的权限不够,所以不能被选为 Presistence Helper,只能选择一个平常不用的系统 App。之后,如果 TrollStore 及其安装的 App 闪退/无法打开,则可以打开 Home/家庭 App,刷新 App。
]]>内存对于程序的运行是必不可少的。程序需要将自身的可执行代码以及运行过程中处理的数据结构存放在内存当中。操作系统内核也是一种程序,因此也和普通的用户应用程序一样需要内存。
为了安全起见,防止普通的用户应用程序不小心地(当然,也防止恶意地)访问并修改内核保存在内存中的内容,现代计算机的操作系统(iDevice 等手持设备也属于广义的计算机)会将整个内存寻址空间(这里的内存指得是虚拟内存)切出一块来单独给内核使用。这块空间就是所谓的内核空间。普通用户程序的权限较低,无权访问内核空间的内容,只能通过系统调用(System Call)来与内核进行交互。
与此相对应地,内核之外的所有其他用户应用程序都运行在内核空间之外的部分,即用户空间。
launchd
在 macOS 和 iOS (包括衍生的 iPad OS 和 TVOS) 中,进程 ID(PID)为 1 的进程即是 launchd
。这是操作系统内核启动后启动的第一个程序。它负责按需启动守护进程(不运行在前台的进程)和应用程序,并负责监控守护进程。操作系统启动后,所有后续启动的进程都是它的子子孙孙(直接或间接 fork
自 launchd
)。
launchd
还有一个配套的命令行应用程序 launchctl
。它可以与 launchd
通讯,以便管理系统的各个守护进程。
SpringBoard 是 iDevice 上管理主屏幕界面的标准应用程序。它也负责启动 WindowServer、启动 App 等工作。因此它也成为越狱后众多 Tweak 的注入目标。
有了这些前置知识,我们就能对这些概念进行辨析了。
reboot
可使设备重启。launchd
,然后重启。这种情况下,因为内核未受影响,所以重启用户空间后仍处于越狱状态。在 iDevice 命令行中运行 launchctl reboot userspace
可使设备重启用户空间。ldrestart
):这是 Apple 官方提供的能力。和重启用户空间类似,但 launchd
本身并不会杀死然后重启。同样,为内核未受影响,所以重启用户空间后仍处于越狱状态。在 iDevice 命令行中运行 ldrestart
可使设备软重启。killall -9 backboardd
可使设备注销。uicache
可使设备重建图标。注意,在重启用户空间和软重启的过程中,你可能有一定概率遇见内核错误(Kernel Panic),从而导致 iDevice 从重启用户空间/软重启 fallback 到直接重启。这种情况,是会丢掉越狱状态的。
Netskao 开发的 PowerSelector 插件可在控制中心(Control Center)中添加一个插件。点按该插件的图标后会弹出一个窗口。在窗口中可以按需选择关机、重启、重启用户空间、软重启、注销、重建图标等操作。可谓十分方便,在此推荐。
]]>经过一番检索,在 @SongXiaoXi 的这篇帖子中找到了问题的原因。具体来说,Fugu14 在越狱过程中,将用户名 _analyticsd
改为了 _nanalyticsd
,但其 ID 和 $HOME
则保持不变。但随后其他的某个守护进程将 /private/var/db/analyticsd
及其子目录的所有者改为了 _analyticsd
(ID 变更为 264
)。这造成 _analyticsd.back
以 263
启动时无法读取 /private/var/db/analyticsd
下的数据库信息,导致电池用量信息无法渲染。
解决起来也很简单。我写了一个插件来完成所有需要的工作。你需要添加以下两个源,然后搜索安装 FixBetteryUsageFugu14
:
https://liam.page/apt/
https://liam.page/apt-beta/
插件会在每次 SpringBoard 启动时执行一些 Shell 命令。
]]>你也可以手工执行这些命令观察其效果。但请确认你知道每一步在做什么之后再操作。
首先,你需要在 Cydia/Zebra 等包管理器中安装
file-cmds
这一软件包。它提供了chflags
命令。(或者,对于高级用户,也可以考虑安装binpack64
)。而后,你需要在 iOS 命令行(或者 SSH 过去)中执行下列命令。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cd /var/mobile/Containers/Data/Fugu14Untether/Library/Caches/com.apple.dyld/
ls -l
sudo chflags -v noschg,nouchg *.closure
sudo chown 263:263 *.closure
sudo chflags -v schg,uchg *.closure
ls -l
cd /var/mobile/Containers/Data/Fugu14Untether
ls -l
sudo chown -h 263:263 /var/mobile/Containers/Data/Fugu14Untether/Library # 只修改 Library 这一 symlink 的所有者
ls -l
cd /var/db
ls -l
sudo chown -R 263:263 /var/db/analyticsd/
ls -l
sudo launchctl stop com.apple.analyticsd
sleep 3
sudo launchctl start com.apple.analyticsd
TrollStore 由社区著名开发者 opa334 开发,安装方法及完整的支持列表可见其官方 GitHub 页面。
现代软件通常会加上一层加密的壳,必须经过脱壳才能分析。脱壳也常被戏称为砸壳。此篇我们介绍可用于 iOS 平台的脱壳工具。
Frida 是跨平台的脱壳工具。首先我们需要在 iOS 上安装其服务端。
https://build.frida.re/
目前最新版本的 Frida (16.0.8) 存在 bug 会导致无法安装。蜗牛源(
https://repo.snailovet.com/
)提供了其早先版本(15.2.2)。经测试可用。
Terminal 中执行:
1 | pip3 install frida-tools |
为与 15.2.2 版本的 frida-server 匹配,你可能需要安装指定版本的 frida-tools。
1 pip3 install frida-tools==11.0.0
而后,我们可以下载 dump 脚本。
1 | git clone https://github.com/AloneMonkey/frida-ios-dump |
使用 USB 线缆,连接手机和电脑。以不加载任何插件的方式启动你要砸壳的 App。(你可能需要 Choicy 这一插件来实现)
在 macOS 的 Terminal 中执行
1 | cd /path/to/frida-ios-dump |
其中
-H
指定你手机的 IP 地址,-p
指定 ssh 的端口,-P
指定root
账户的密码(默认是alpine
)。
最后的程序名字,可以是显示在你手机上的名字,也可以是它的 Package ID。所有可用的 name/ID 可以通过下列命令查看。
1 python3 dump.py -H 192.168.1.3 -p 22 -u root -P 'alpine' -l
如此,即可在当前目录下生成 健康.ipa
可供后续分析。
检查发现,这是因为在 4.10 版本开始,思科的 VPN 加入了 Socket 过滤功能。从现象来看,它几乎无时无刻不在 buzy loop(哪怕把所有网络连接都断掉),这应当是个 bug。从功能来看,这种全方位的过滤功能,实在令人担忧其安全性。因此决定干掉他。以下是步骤:
Cisco...
开头的网络配置-
,删除他们/Applications/Cisco/Cisco AnyConnect Socket Filter.app
以免将来之虞这里假定你对 C-like 语言有一个较为全面的了解。若你是 C/C++ 的熟练使用者则更好。
Objective-C 是 C 语言的严格超集。即是说,在 C 编译器下能够编译的代码,应当可以不加修改地使用 Objective-C 的编译器来编译。(尽管可能行为不完全相同)另一方面,在 Objective-C 当中,可以混合使用 C 风格的代码。
头文件 | 实现文件 | |
---|---|---|
C | .h | .c |
C++ | .h /.hpp | .cc /.cpp /.cxx |
Objective-C | .h | .m |
Objective-C++ | .h | .mm |
为了兼容 C,我们依然可以使用预处理器指令 #include
来包含头文件。但是 Objective-C 提供了另一选项 #import
。它与 #include
的作用几乎完全相同,但可以保证在一个编译单元中每个头文件都只被引入一次。即是说,它起到了传统 C/C++ 变成中 #pragma once
或是 Guard Macro 的作用。
Objective-C/C++ 中的基本类型和 C/C++ 中的差不多。几种基本类型在 Objective-C/C++ 中的长度分别是:
char
: 1Bint
: 4Bfloat
: 4Bdouble
: 8B此外,Objective-C/C++ 中也有 short
/long
/long long
/signed
/unsigned
之类的修饰。含义也和 C/C++ 中的相同。
Objective-C 支持 C-style 字符串,并且也遵循 C 语言当中对引号使用的约定。亦即,使用单引号表示字符(例 'c'
),使用双引号表示字符串(null termination)。但在 Objective-C 中也有实现 NSString
类(类似 C++ 中的 std::string
但更强大)。它更常用。
1 | 'c'; // 字符类型字面量 |
此外,NSString
也支持 printf
风格的字符串构造方法,以及支持从 C-style 字符串中构造。
1 | // construct a NSString object from literal |
if
Objective-C 中的 if
语句和 C/C++ 中的基本一致。唯独,在 Objective-C 中以 0
表示 false
,而以其他值表示 true
。例如说,其他任何数值,或是任何字符串,在 Objective-C
中都会被认为是 true
。
for
/while
Objective-C 中的 for
/while
和 C 中的完全一致。
名称 | 代码风格 | |
---|---|---|
C/C++ | 对象成员函数调用 | obj.method(args) |
Objective-C/C++ | 向对象传递消息 | [obj method: args] |
在 C++/Java 中,类中定义有成员函数/成员方法。我们可以通过类似 obj.method(args)
的方式调用 obj
对象的 method
成员函数。如果 method
在 obj
所属的类中没有定义,则在编译期就会报错。
Objective-C 则继承了 Smalltalk 的消息传递模型。在这一模型中,调用成员函数被视作是向对象发送一个消息。例如,obj.method(args)
式的调用会被写作是 [obj method: args]
。这种写法的意思是,向 obj
这个对象发送名为 method
的消息,args
则是消息附带的参数。与 C++/Java 风格的调用不同,obj
所属的类即便没有定义名为 method
的成员函数,我们在代码中依旧可以向 obj
发送这一消息。Objective-C 的编译器不会为此报错,但在程序执行时则会抛出一个异常。
对比下来,消息传递模型中类和成员函数的关系较为松散,这种调用方式总是在运行期动态绑定。于是,它不需要 C++ 当中的 virtual
/override
关键字。当然,这种做法也存在一定额外开销。(显然)
空对象(
nil
)接受消息后默认不做任何事情。因此向nil
传递消息是安全的。
在 C++ 中,我们称之为「声明一个类」。在 Objective-C/C++ 中,我们说「定义类的接口(interface)」。
在 C++ 中,定义一个空的类形如
1 | class Foo {}; |
注意,它不需要继承自一个作为占位符的父类。在 Objective-C/C++ 中,定义一个空类形如
1 | @interface Foo : NSObject |
注意,和 Python 中所有类都继承自 object
类似,Objective-C 中所有类都继承自 NSObject
。
在 C++ 中,定义一个包含有数据成员的类形如
1 | class Foo : public Bar { |
类比在 Objective-C/C++ 中则是
1 | @interface Foo : Bar { |
Objective-C 的类分为接口(interface)和实现(implementation)。接口部分通常包含了类声明以及其中数据成员的定义,以及相关成员函数的声明。实现部分通常包含了成员函数的实现代码。
注意,C++ 中,class
中的数据成员默认是 private
的;在 Objective-C/C++ 中,@interface
段定义的数据成员默认是 protected
的,@implementation
段定义的数据成员默认是 private
的。为了保持访问控制一致,额外在 C++ 代码中加上了 protected
关键字来指定 data
的访问控制类型。
在 C++ 中,成员函数的声明形如
1 | class Foo : public Bar { |
类比在 Objective-C 中,则是如下形式
1 | @interface Foo: Bar |
首先关注 (1)。在 C++ 中,class
内的访问控制默认是 private
。因此,要使声明的成员函数可用,我们需要显式地指明 public
。在 Objective-C 中,@interface
段的方法默认是 @public
的。
接下来关注 (2)。在 C++ 中有所谓的 static
-成员函数。此类成员函数是属于整个类的,不能修改类的对象内部的数据成员。Objective-C 中也有类似设定,即所谓的类方法(class method)。具体形式是在方法前加上一个 +
记号。
现在关注 (3)。这是典型的成员函数的声明方式。这样的成员函数是与具体的类的对象绑定的,必须要有一个构造好的对象才能执行这些成员函数。在 Objective-C 中,这是所谓的对象方法(instance method),也称为一般方法。
(4) 处也声明了一般意义上的成员函数,但在 Objective-C 这里稍有不同。对 Objective-C 的版本,它的函数全名(签名)是 instance_method3:and:
。即是说,在声明时,函数的名称和参数列表交织在一起;每个冒号后面都带有一次参数传递。调用它的时候则类似:[obj instance_method3: 0 and: 1]
。这是 Objective-C/C++ 特有的。
尽管我们也可以在 Objective-C 中定义数据成员,但实际上更好的方式是使用属性。例如
1 | @interface Foo: NSObject |
它等价于
1 | @interface Foo: NSObject |
这里,(1) 声明了类 Foo
的一个属性。它的类型是 int
,名字是 age
。如果没有显式地如 (2) 这样将属性和变量关联起来,则编译器会自动产生一个变量,并做这样的关联。注意,属性的声明应当位于 @interface
段,属性与变量的关联则应放在 @implementation
段。
你也可以使用别的变量与属性进行关联。例如 @synthesize age = internal_age;
。这样会将 age
这个属性与 internal_age
这个数据成员进行关联。
声明属性,则编译器会为我们自动生成相应的 setter/getter 方法。例如说,上面的代码,大致相当于会生成这样的代码:
1 | @implementation Foo |
也就是说,通过属性,我们将类的数据成员封装了起来。外部不能直接操作类的数据成员,而要通过 setter/getter 来操作。此外,Objective-C 还为此提供了类似 C++ 中成员访问运算符(.
)的语法糖。我们可以写出类似下面的代码
1 | p.age = 10; // 1.a |
其中 (1.a) 和 (1.b) 的含义相同,(2.a) 和 (2.b) 的含义也相同。
]]>PATH
变量很长。仔细一看,发现其中有重复部分。但同期在系统 Terminal 当中,PATH
变量则是正常的。举例来说,VSCode 里的终端有:
1 | $ echo $PATH |
同期 Terminal 里的终端有:
1 | $ echo $PATH |
检查各种 profile
/bashrc
文件,均未发现异常。后查明是在 VSCode 当中启动嵌入的终端时,默认会继承 VSCode 启动时的环境变量。这导致后续在使用 PATH = "/path/to/foo/bar:$PATH"
这样的语法时,会将 PATH
复制一份。只需将 VSCode 中的下列设置改为 false
即可。
1 | "terminal.integrated.inheritEnv": false |
在安装好 Theos 的基础上,可以执行下列命令启动 NIC 程序。
1 | $THEOS/bin/nic.pl |
之后,NIC 会在终端上打印出一个列表,询问你想要创建何种类型的项目。
1 | $ $THEOS/bin/nic.pl |
我们主要关注的是其中的 iphone/tweak
和 iphone/tweak_with_simple_preferences
。后者会构造一个 Preferences.plist
文件,以便我们能在 iOS 的系统设置中配置该插件的行为。
这里我们选择 18。接下来,NIC 会交互式地要求我们键入一些插件的信息。
1 | Choose a Template (required): 18 |
这里,我们给项目起名为 PhantomSteps
,其包名称为 page.liam.phantom_steps
。你可以按需修改。MobileSubstrate Bundle filter 这一项是说,你可能影响的 App 的名字。List of applications to terminate upon installation 这一项则是说,在安装你的插件时,需要杀死的程序的名字。
创建成功后,目录结构应该类似
1 | $ tree . |
这里
PhantomSteps.plist
记录了需要杀死的包的名称。control
包含了先前在 NIC 交互式环境中填写的信息。Makefile
是对应 GNU make 的文件。Tweak.x
是插件自身的代码。Theos 会从模板中创建一个带有各种注释的文件供参考。]]>通过 NIC 模板创建的项目可见:GitHub
你也可以直接参考官方英文文档:https://theos.dev/docs/installation-macos
打开 MAS,搜索 XCode,安装/更新到最新。
打开 Terminal,执行下列命令(来自Homebrew 官网)。
1 | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
在 Terminal 中执行下列命令。
1 | brew install ldid xz |
1 | echo "export THEOS=~/theos" >> ~/.zprofile |
1 | git clone --recursive https://github.com/theos/theos.git $THEOS |
当然,你也可以将
$THEOS/bin/
加到环境变量$PATH
中去。
新版的 XCode 不提供插件可能使用到的私有框架,因此我们需要安装 Theos 维护的补丁版本。
1 | curl -LO https://github.com/theos/sdks/archive/master.zip |
将来需要更新 Theos 时,可以这样:
1 | $THEOS/bin/update-theos |
首先是介绍一下我订阅的软件源。
https://getzbra.com/repo/
http://apt.thebigboss.org/repofiles/cydia/
https://apt.bingner.com/
https://limneos.net/repo/
https://repo.co.kr/
https://repo.chariz.io/
https://havoc.app/
https://ios.jjolano.me/
https://opa334.github.io/
http://cydia.ichitaso.com/
https://repo.snailovet.com/
https://repo.autotouch.net/
https://repo.packix.com/
https://repo.ginsu.dev/
https://liam.page/apt/
/https://liam.page/apt-beta/
https://liam.page/oldabi
https://ib-soft.net/repo/
https://subdiox.com/cydia/
https://apt.cydiakk.com/
https://www.ios-repo-updates.com/repository/cokepokes/
https://apt.fouadraheb.com/
https://apt.htv123.com/
https://apt.25mao.com/
https://repo.initnil.com/
https://apt.procursus.us/
https://repo.acreson.cn/
https://lenglengyu.com/
https://sopppra.mooo.com/
https://poomsmart.github.io/repo/
https://alias20.gitlab.io/apt/
https://c1d3r.com/repo/
这里按照大致的安装时间顺序介绍我目前还在使用的插件。其中,插件名字后面带有星号标记的表示已适配 Fugu15 Rootless 越狱。
常见的民用加湿器有几种工作原理。超声振动雾化加湿器又是其中最常见者。其工作原理清晰,结构简单可靠,价格也最便宜。但北方城市水质硬度往往较大,加湿器长期使用后,在振动片和出风口附近容易形成大量水垢。振动片附近的水垢往往导致噪声,对睡眠实不友好。
此篇介绍水垢及其成因,以及介绍如何方便快捷地取出加湿器振动片附近的水垢。
自来水中不可避免会溶解一些无机盐。特别地,其中的钙盐和镁盐(以碳酸钙、碳酸镁为主,辅有硫酸钙、硫酸镁、氯化钙、氯化镁等)因溶解度较小,容易沉积形成水垢。
加湿器振动片使水雾化,导致振动片附近的水相对减少、无机盐浓度升高。溶解度较小的钙盐、镁盐便容易在振动片附近沉积。这些沉积的水垢附着在振动片上,导致振动片工作时发出噪声。
既已知水垢的成因和主要成分,想要去除水垢便不难。此处我们只需用到初中化学和物理知识即可。
水垢的主要成分是碳酸、硫酸、盐酸对应的钙盐和镁盐。要软化、去除这些水垢,只需找一合适的阴离子对应的酸性溶液即可。此处我们可以选择生活中常见的醋酸。
醋酸容易获得,且醋酸是弱酸操作安全。最主要的是,醋酸钙和醋酸镁的溶解度较高,算是易溶解的物质。因此,醋酸与水垢反应,可有效软化、溶解水垢。
操作步骤:
从此篇开始,我们讨论羽毛球的步伐。此篇从启动步开始讨论。
羽毛球运动的特点、基本的物理学定律、人的生理结构,三者共同决定了启动步的重要性和必要性。
羽毛球是速度最快的球类运动之一。若是限定在初始速度的概念上,我们还可以将「之一」去掉。因其速度快、场地小,所以从你击球结束到下一次击球之间的间隔十分短暂。即便在较为缓和的对抗中,这一时间间隔通常也不会超过 3s。在这短暂的间隔中,你需要完成收拍、移动回位、移动到位、准备击球的若干步骤,时间紧迫。此外,在对手击球之前,你无法判断对手回球的落点位置。因此,羽毛球的步伐移动同时具有快速和方向不确定两个重要特点。
基本的物理学定律告诉我们:若你想要向前加速运动,则势必要让外界对你有向前的合力;向后同理。若你想要相左加速运动,则势必要让外界对你有相左的合力;向右同理。这一合力越大,加速度就越大。在运动上就体现为移动的爆发力越大、突然性越强。在不打滑的情况下,这一水平反向的合力由地面给脚的静摩擦力提供。而静摩擦力的极限大小可由公式 $f_{\text{静}} = \mu_{\text{静}} \cdot F_N$
给出。这也就是说,
人的生理结构决定了,若要向斜下方蹬地发力,则必须让发力腿的小腿和地面呈现一个 30° 至 60° 的夹角。最典型的例子是 100 米短跑。跑步项目的一个特点是它永远向前跑。因此运动员们在起步阶段会采用蹲立式起跑的方式获得极大的向前的加速度。但羽毛球的启动方向不确定,于是你必须用合理的方式启动,以便能够照顾尽可能多的潜在移动方向。这种合理的方式,即是所谓的「启动步」;具体来说,又有细分:
总结一下。由于羽毛球运动移动的快速和不定向的特性、向斜下方蹬地发力的原理、人的生理结构特点,羽毛球的启动步是重要且必要的。若无合理的启动步,则往往无法击打到球,或只能陷入被动击球。
此节我们讲解启动步的要领。首先我们从最简单的静止启动开始讲起,然后再过度到移动中的连贯启动步。
首先是准备动作。
在对手可能击出攻击性的下压球的情况(你发后场高球、挑高球、击平高球、击高远球等),你应当采取双脚左右分立的防守站姿。具体来说,应当
在对手大概率作出防守的情况,你应当采取双脚前后分立的进攻站姿。具体来说,应当侧身使持拍手在前,面向对手击球方向,双腿前后分立与肩同宽,脚尖朝向斜前方向。其余身体姿态与防守站姿基本相同。
在对手引拍即将击球的瞬间,开始启动。具体来说:
这里做一些额外说明。
从静止启动是较为理想的情况,在实际对抗中较少出现。羽毛球球速极快,对手不可能等你回动到位后、站定后再击球。因此,更多的启动情况发生在回动的过程中的顺势连贯启动。
举例来说,当你在后场击出高远、平高球,你应当迅速回动至球场中部区域。在回动的过程,应密切注意观察对手击球情况,随时准备启动。此时,可能你尚在回动过程中。例如说,右手持拍的你,重心放在左腿上,而右腿正处在委屈膝盖向前迈步的阶段。此时你的启动应当是(其他情况类比即可):
这里着重讲一下顺势的含义。顺势在此处有几层含义:
为了做到这一点,我个人总结了四个字:快回·慢踩。也就是,
启动步本身值得持续练习。以下是从入门到高阶的一些练习方式。
解法:
WeChat.command
即可。1 | 将启动命令写入桌面上的文件 WeChat.command;如果已有重名文件,请注意修改。 |
新入手的球拍(简写 AX100zz)情况:
作为对比,过去六年常用的球拍(简写 Arc11)情况:
挥重方面,实测 AX100zz 和 Arc11 的挥重接近。大约是因为 Arc11 去除底胶的缘故,实际平衡点有所前移。
挥速方面,相同发力的条件下,AX100zz 的挥速较 Arc11 慢。这主要应该是 AX100zz 采用盒式拍框而 Arc11 采用破风拍框的缘故。
中杆方面,AX100zz 的中杆相对较硬,但硬得有限。传统上,我们习惯用软硬来评价中杆。但 AX100zz 的中杆更为显著的特性是回弹快。这一特性是建立在实心中杆 + 钨丝的黑科技结构上的。这一特性要求你能用 AX100zz 做出足够快的挥速。若然,相较 Arc11,击球感明显更加干脆;若不然,AX100zz 的击球会显得有一些「木」。
这即是说,AX100zz 对小臂内旋和屈指发力的技术动作要求更高。而一旦动作到位,AX100zz 的击球球速会比 Arc11 要更快。
高远方面,步伐到位的情况下,AX100zz 的击球感和 Arc11 差别不算很大;被动的情况下,AX100zz 会稍微更废力一些。
重杀方面,第一次杀球用的还是 Arc11 的发力习惯。相较而言,球路稍微有些发飘,落点更长。稍作调整后,能够感受到更明显的下压感,暴击音也不出期待地非常舒爽。按对手的反馈,球速有区别,但不大。
点杀方面。有重杀的经验,点杀时加大了屈指的力量,效果喜人。实战中,头顶突击点杀对角,落点可以比较轻松做到在发球线附近。相同的条件,Arc11 打出同样的落点个人感觉更困难一些。
劈杀方面,这本身不是我可称擅长的技术,平时用得也偏少。实战中,尝试正手区劈杀斜线,许是还没有完全适应发力变化的缘故,总体感觉和 Arc11 区别不算大。
吊球方面,劈吊和 Arc11 区别不大;而滑板吊的动作由于本身发力更难,使用 AX100zz 的失误率比 Arc11 要明显高一些。
反手高远方面,AX100zz 对发力的要求明显更高。前三拍感觉都没能很好发力,打得有点木木的。调整后整体感觉 AX100zz 明显吃力一些。
个人感觉,前场的搓放由于发力小而巧,影响回球质量更多的是拍面大小和拍线(型号和磅数)。实际 AX100zz 的拍面和 Arc11 相比只是稍微小一些,而拍线又完全相同(穿线师傅都是同一个),整体感觉前场搓放差异不大。但是,不知是不是错觉,使用 AX100zz 做假动作搓放时,感觉更得心应手一些。
挑推方面,个人感觉差异不大。
4U 版本的 AX100zz 属于相对容易上手的高端拍。但其对发力的要求,不那么糖水,需要更短粗的爆发发力才能驾驭。
个人意见,可以视作是 3U 版本 Arc11 的进攻加强版。
]]>从自然到刻意,可选项有:
从自然到刻意,可选项有:
经查,类似时间机器备份或是自 MAS 下载和安装软件的行为,苹果将其优先级调低,并设置有限流(throttle)这一设定。这是为了保证这些优先级较低的行为不会占用过多的系统资源,干扰正常的工作。
这个设定的出发点是好的,但存在一个问题。比如说,对我而言,使用时间机器备份通常发生在无需使用电脑工作的时间段。此时,限流的这个设定就十分让人蛋疼了。为此,我们可以在命令行暂时关闭限流,待备份完成后再重新打开。命令如下:
1 | # 关闭限流功能,加速备份 |