人物访谈 | 老司机周报主编 Parsifal 专访

Parsifal 是老司机周报的主编,今年他给摸鱼周报提供了很多帮助和建议,这里对他再次表达一下感谢。这次邀请他来做一期访谈,主题分为两部分,一部分是老司机周报的发展故事和他的行业观察,另一部分是他对开发者的一些建议。如果大家还有其他问题欢迎留言提问~ 简单介绍下自己吧 哈喽,各位摸鱼周报的读者好,我是 Parsifal,是一位 iOS 开发多年的老司机了。17 年底开始加入「老司机技术周报」编辑组,到 18 年底正式接手至今,主要负责其运营工作(其实就是给我们编辑组的大佬们打打杂)。二次元一点说,我算目前的二代目,当然我们也很快会迎来三代目,感谢大家一直以来的支持~ 当初创办老司机周报的目的是什么,能讲一些当时的背景吗?现在回想来看,对比当时的目标,达成度怎么样? 老司机技术周报自 17 年 12 月创刊以来,已经更新了整整 4 年时间。在我的认知里,4 年对于一个社区组织的生命周期来说,是很长很长了,长到足够让一个小萌新成长为优秀的成熟开发者,甚至长到我们读者群可以完成一轮新陈代谢。 现在我们的很多读者,谈到周报编辑组,可能更熟悉的是负责我们社群运营的老王「Damon」,或者时不时会在周报中插播公告的「Parsifal」,但如果要谈到周报创办的往事,就需要请出我们的创始人 - 「没故事的卓同学」。作为整个编辑组的一代目,老卓一直被我们认为是编辑组的灵魂人物,即使他 18 年底已从编辑组“退休”,但他创办周报的初衷我们还是延续了下来。 在经过 14、15 年的野蛮发展后,17 年我觉得是 iOS 开发的一个巅峰尾巴。那会儿很多优秀的开发者依然在社区内活跃着,比如老卓就是其中一位。他是非常擅长和乐于分享的,在周报创办之前也深受大家喜爱。在社区内容输出还很爆炸又良莠不齐的当时,老卓就有了想法要将自己平常看到的优质内容,定期整理出来分享给大家。当然这种事情是人多力量大的,所以老卓又和几个好友聊了下,大家都觉得很有意义,就水到渠成地组成了编辑组的第一批元老 Damon\MM\味精\EF\四娘和 BlackSun 等。随着这几年的不断成长和纳新,周报编辑组已经有 30 多人了,但我们的初心依然没变 - 为读者输送有价值的内容。 这么多年的积累,让我们有了一个非常高质的资源库,我个人是非常喜欢在周报仓库检索内容的;WWDC 内参活动越办越好,质量和数量都有了很大提高;今年编辑组也尝试了线下沙龙的方式,用周报的影响力帮助企业对外输送高质内容;我们一直以来做的事情,也逐渐被官方关注和认可。 现在回想来看,老卓的目标在他“退休”那天就已经实现了,周报每期都能够顺利发布,18 年的时候周报已经有了相当的影响力,当时主要的两个更新渠道 GitHub 和掘金都收获了较大的关注群体。而于我个人而言,18 年底从老卓手里接过来运营,算超预期了吧。按我原计划,接手后我负责 18 年的收尾工作,让周报能够有个相对完整的 ending。 但后面做着做着发现,编辑组的朋友依然还有着热情继续做这个事儿,Damon 和邦本等人也愿意加入平时的运营,并且得益于老卓之前的管理原则:周报的编辑周期不强依赖于某个人,我们还是坚持了下来。 18 至 20 上半年左右,周报的发布其实也是轮值的,而后由于调整了发布渠道和时间,并且开始重视公众号运营,才转为主要由我负责发布,我有事的情况下再找其他人代班。然后就是整个编辑组还是得有个人做下统筹,负责一些人员流转,对外合作,以及内参啊,沙龙啊等等其他的活动。轮值没有具体的多长时间,看上一任自己意愿吧。接着编辑组不断吸收优秀的开发者,团队越来越大,新老编辑交替也在很自然地进行。 我的周报编辑生涯可能也很快会结束,但老司机技术周报应该还没到停刊的时候。关于这些,接下来我们一二三代目会一起在老卓播客细聊。大家可以开始期待周报三代目了~ 到目前为止老司机周报已经出了180多期了,分享和传播了非常多优秀的资源和内容,某种程度上也推进了整个 iOS 行业的发展。作为活跃在这一领域最前沿的一群人,在这4年期间里你感受到的变化是什么? 作为周报这类聚合资讯性质的内容创造者,我们可以说是最了解社区的那批人了。我们的内容源自于社区,所以对社区发展方向和热度是十分敏感的。这几年,从行业上看,如我上面所说的,17 年算是一个尾巴,而 18 年至今整体趋势还是相对稳定去泡沫化。不过这其实就是一个行业正常的生命周期,萌芽到野蛮生长,稀缺到泡沫化,然后会到稳定的成熟期,最终到衰退期。 社区产出的内容,也伴随着行业的稳定,没有了之前百花齐放资讯爆炸的光景。整体感觉就是新的东西越来越少,老的东西还在一遍又一遍地被新人挖出来继续写。而我们的读者群里,0-2 年新人也几乎成了凤毛麟角,早两年虽然也少(我们的内容更偏中高级开发者),但不至于看不太到。 整体社区的内容产出来看,17 还是 OC 内容占了大部分,然后是慢慢国内外出现了分歧,国外 18 年开始 Swift 的内容占比越来越大,这几年几乎都见不到国外有 OC 方面的产出了。18 年开始至今的国内,则对跨端(ReactNative\Flutter\Weex)方案爆发了热情,至于 Swift 依然没看到大范围的使用。 创办老司机周报的过程,都遇到过哪些困难?未来老司机的走向是什么样的,有没有什么规划呢? 17 年底起步到 18 年上半年,都算比较顺利,刚开始大家的热情也比较高,也比较有精力做这些事情,同时那会儿社区的优质内容也多,也有很多新的东西被不断挖掘出来。而后我们就遇到了第一个比较大的困难,每周被推荐的内容不够,从而需要主编来花费更多时间去收集。 未来的走向我现在不太好说,但目前的形势是很好的,今年做的几个尝试反响也很好,我们会争取更大的影响力,并继续尝试与苹果做一些进一步的合作。当然,更具体的未来规划,就等我们下一任主编官宣后再聊了。随着移动互联网红利的消失,对 iOS 开发的要求可能不再仅限于能做出一个 App。如果需要进一步提升,学习的广度和深度,哪一个帮助可能更大? 其实我不太认为说以深度为发展方向的开发者和以广度为方向的开发者存在明显的孰优孰劣。为什么会有这两种特征明显的开发者?还是市场需求决定的。这几年大厂产品越来越成熟,那么自然对专精人才的需求就随之增大,比如各类的极致性能优化、基建建设和效能提高等,团队发展到一定阶段,遇到瓶颈了这些需求就出现了;但同样的,全面手,比如大前端全栈开发者,依然是很多企业喜欢的。既然路有很多条,那么主要还是看自己适合和擅长哪条了。 选择了哪条路,如何提升?我觉得理好知识图谱,按点攻破即可,方法论不难,关键还在于执行力。前几年看过成甲的一本讲关于如何学习的书,如果还没有形成一套自己学习技巧的朋友,建议快速过一遍,取其精华。 面对 Flutter、Uni-App 等跨端技术的崛起,对iOS行业未来的发展你怎么看?对当前的iOS从业者有什么想说的? iOS 环境这几年确实被吐槽得厉害,事实上我也有认识的同事辞职回家种花当老板。未来几年,iPhone 还会是销量前几的产品,苹果还会是那个科技巨头。iOS 开发者接下来会相对平稳了吧,直到下一个颠覆性产品到来。别把自己局限于 iOS 开发者,我们是做最接近用户端这一侧的开发者,现在是 iPhone,Apple Watch,以后还可能是 Apple Glasses。元宇宙革命正在进行~ 开发人员除了掌握本职的技术知识外,现在也越来越多提到「软技能」这个词,在你看来有哪些软技能是在职业发展中比较重要的。 提到软技能,应该有不少人都或多或少看过或听说过《软技能:代码之外的生存之道》一书。这本书也有提到「十步学习法」,同样适用上面那个问题。当然,这本书讲的方面很广,从社交、理财到健身什么的,作者都分享了自己的经历和想法。我这里基于自己的一些职场经验,简略谈谈职场开发者可能需要的一些软技能吧。 需要掌握优秀的检索能力,检索能力往往会被很多人忽视,但在工作中却很重要。虽然我们常常调侃面向搜索引擎编程,可这就是绝大多数人的编码常态。好好提高检索能力,会让你效率大增,并且学习起来也比别人更快一步。检索第一步是去哪里搜?Google 作为程序员第一搜索引擎,自然不是个坏去处,但某些场景,可能有更好的地方。比如,我现在想看某些方面的文章,我更优先检索的就是周报仓库。知道了去哪里搜之后,怎么组织关键词去搜就直接决定了你能搜到什么了。编码相关的问题,我更推荐是直接按英文关键词拼接空格来搜,这样搜到的东西往往更准确,而且资源也会更多。最后就是一些相对进阶点的搜索技巧,引号精确匹配,默认模糊匹配等等。 老生常谈的提问的能力,作为一个某领域的萌新怎么去请教别人也很值得重视。一个好的提问可以帮你更快得到答案,也省了对方很多时间。以寻求帮助去提问的场景下,比如问你的导师,或者平常的一些技术群求教,一个好的提问大体是需要包含这几点内容:谦虚的态度,细致的问题背景,目前你的进度和结论,你期望得到的帮助等。 完成事情的能力,有些人叫「拿结果」的能力。在日常团队管理中,我是比较强调办事能力的,这一个能体现综合水平的点。有些开发者编码能力很强,但他并不一定能把事情做好。在团队内把事情做好,需要足够的责任心,良好的沟通协作能力,过硬的业务水平,有时候还需要有很强的韧性。 开发的职业发展过程中大多数都会经历瓶颈期,你有没有经历过类似的阶段,后来是如何突破的?对于处于这种阶段的同学,你有什么好的建议吗? 可能是自己没有很高的追求,我的职业发展相对比较顺利。所以给不了很有价值的建议。但如果说我自己真的遇到了,我估计会索性放空自己一阵子,任性放纵自由一段时间,都放松下来后,再来思考下一个阶段需要怎么走。生活压力很大,并不是每个人都有这样的机会和勇气这么处理,总之挺过去就好。 人生就是无数习惯的总和。在你工作中遇到过哪些开发人员好的和不好的习惯,能给我们分享一下吗?能再分享一个你自身具备且感觉对自己帮助较大的好习惯吗? 有些小伙伴比较好的习惯是不定期的去归纳总结,将所做过的事情,解决好的问题,新掌握的知识,都去做一个阶段性的总结。为自己的总结再设立几个问题,通过这几个问题去判断总结的效果是否理想。 不好的习惯,有些小伙伴遇事先条件反射性地自我否定算一个吧。碰到一个困难,第一反应就先觉得这个不行,那个不可能,这不是我的问题等等,本能地去推脱,久而久之也就丧失了正确评估一个事情的能力。或许是出于自我保护意识,察觉到威胁先跑路,但这确实是很不好。于己,这样会让自己的上限不断被压缩,仅维持在一个相对安全的小舒适区;于人,不断地被否定,也会影响对你的观感。 我一直认为开发人员不应该扔掉代码,尤其是中低层管理,这很容易会让自己失去竞争力。平常写写代码,保持编码逻辑的触觉,也能让自己跟团队其他伙伴更能走到一块。 另外对于普通开发者也好,技术管理层也好,定期去关注业界动态和社区技术生态发展,也是很重要的。每周看老司机技术周报就是一个很好的途径,哈哈哈~ 很多开发都知道学习的重要性,但又常常陷到工作中无法抽身学习,就学习和工作的平衡性应该如何维持呢? 对于很多人来说,这可能是一个普遍的问题。这里聊下我自己的看法吧。首先还是需要从工作中去学习,毕竟这占据了我们大部分精力,去和自己的直属 Leader 多沟通想法,将一部分想学习的能力与自己的工作内容结合起来,落到工作中去实践。 然后是必须注重学习效率,一个阶段做一个事情,贪多嚼不烂;最后,虽然我很不鼓励内卷,平常自己也不 996,但很多人拉开差距真的就是在工作之外的时间。去培养自己的兴趣,将能够给自己带来乐趣的学习内容安排在非工作时间。 讲一个最近的生活感悟吧? 杭州疫情在这几天又复发了,且有迅速扩张的态势。注意防护,没事少出门,听从指挥,少给别人添乱~

人物访谈 | 一位普通的 iOS 程序员

夏天是摸鱼周报的一位主编,主要负责 Tips 部分的编写和审核。他曾参与掘金翻译计划,翻译过很多篇优质博客内容,也翻译过像是 iOS Crash Dump Analysis Book 这一类的外文图书。今年拉他来聊一聊关于外文翻译和他个人的一些情况。 简单介绍下自己吧 哈喽,各位摸鱼周报的读者好,我是夏天,是一位双非的老 iOS 程序员。目前在摸鱼周报里面摸鱼。 一般优秀的英文文章,不会马上就有人翻译,所以,如果想获取一手英文资讯,有什么有趣或者必看的外国网站推荐吗 一般都是通过Medium,Realm 之类的网站,然后注册一下邮箱,接收推送,没事翻两篇。 看到好文章时,看看他们的参照了谁,参照作者可能也有好的文章。 之前翻译过不少内容,像是:《iOS Crash Dump Analysis Book》,是出于什么理由进行翻译的呢?翻译这个长篇有什么收获? 《iOS Crash Dump Analysis Book》这本书,我是在 19 年看到的,其实并没有仔细阅读下去。后来想仔细阅读的时候,发现网上并没有译文,我就申请翻译一下这本书,作者也是很慷慨,给予了这个机会,让我完成了 1.0 和 2.0 的翻译。 我觉得整体翻译下来的收获,大概就是形成体系的去了解一些关于崩溃的结构和作者在进行奔溃分析时的分析途径和方法。整本书是一个概论,虽然不能让你成为一个 crash 分析高手,但给了一个格式,在碰到崩溃的时候,可以按部就班的去进行分析。 为此我还写了一篇观后感iOS 崩溃分析。 技术文章翻译的过程中那部分是相对困难的,有没有什么经验可以分享的 第一就是语感,翻译完以后多读几遍,没有 deadline 的翻译,你都可以翻译完以后,过几天再去读,再优化一下,至少读到你自己不变扭,能通顺为止。 第二就是实践,虽然里面很多技术你不能去实践,但是你可以去了解。文章的引申阅读和搜索一下相关技术。我在写 字节跳动移动研发工具链 - MBox 一文的时候,也是尽量保留原作者的本意,去了解对应的技术及其实践,进行再创作。 听说你最近一直在写前端需求,前端和iOS有什么区别?iOS开发想往前端过渡的话,有哪些注意事项? 写的都比较简单,都是偏业务方面的。思想上和逻辑上没有大的差异,唯一需要注意的是,如果你接手别人的项目时,一定要心平气和,因为前端很坑爹的是怎么写都对。 学点CSS,学点JS,就可以开始过渡了,期间体系化的学习一下。 期待你在一家好的公司,什么东西都有,然后你顺着经验就可以开始夯实之路。 利用公司的项目锻炼自己,可以根据现有的模板,从零开始,从第一个组件,到第一个业务模块等等。 对于“大前端”这个词,你怎么看?随着跨平台、Web WebAssembly等技术发展,会不会统一成一个工种? 我理解是为用户体验负责,各种与用户有直接接触的都算是大前端,不过这可能更偏向于程序员这一职业。 前端程序员几乎都做着类似的事情,差不多的开发流程,页面埋点,发布流程,项目持续集成,页面性能监控 APM ,工程化,组件化... 技术永远都有差别,都是往前也不一定是同向。 也许未来这些为页面服务的人员可能会慢慢的变成一类,但是其中的某些人难以忍受这种越来越同化的工作又会开始异化出新的种类。 技术不停步,工种就永远有差异。 在这几年的iOS生涯里有什么比较大的收获?你怎么看待程序员这个行业? 没看到有啥收获,连个只能被铭记的功能都没有。 程序员这个行业,是一个注重基础和思考的行业,用数据结构和设计模式规范具体的行为,注重实践动手能力。 这个行业需要你一直学习,终身学习。 不应过分功利,需要一定的职业操守和道德。 自己在工作中有没有什么好习惯或者高效的工作方法可以分享的 解决问题的方式不要困于一种途径,交流能让你成为更好的人,多出去分享去交流,可以使人进步。 学到什么一定要做出来, 写下来,讲出去。 最近的一个生活感悟是什么 努力,生活总有好事发生。准备迎接好事发生。 偶尔咸鱼也没关系,躺着是很舒服,但是还是要动一动的,不然真的会腰酸背痛 你的公众号感觉最近更新少了,后续还会恢复吗? 不更新的原因有很多:创作热情:在更新一段时间后,读者的增长量没有达到自己的预期,心理有些懈怠创作质量:期望与创造出真正有价值的东西,但是囿于能力等因素,既不想水,也写不出好文章创作方向:平常的工作更多是业务相关的,作为一名老 iOSer 并没有自己固定擅长的地方,想像书籍一样写点东西,但是不太清楚具体些什么。...最最主要的原因就是懒后续还会恢复更新的。目前在进行 Swift 的学习,之前也开了一个Swift 100 Days的专题,还在想怎么在里面添砖加瓦,丰富成一个系列 有什么想借助摸鱼周报宣传的 希望大家支持我的公众号:iOS成长指北,也希望大家与我交流。 承接各种技术类文章写作,一起交流学习。

利用 Automator 快速符号化 Crash 文件

背景 起因是最近有接到一个临时协助任务,其中有几个重要的流程:QA 方导出 .crash 文件(必要的) 我方要根据测试提供的 crash 文件的build number,去下载对应的 xx.app.dSYM 把下载的dSYM给合作方 合作方解析crash文件从上的步骤可以看出第一步不可省略。第二、三步完全可以干掉,流程越多越浪费时间。 第四步也可以我们自己做,就可以优化成 QA 直接解析好 crash 文件然后给合作方。 那么就提效了提效 50% 是不是,两个人的事情一个人搞定 (那么就可以卷点别的)初版方案 小插曲一开始第一周我写了个Shell,调试通过之后就没继续,就干其他大活了(这里有个有悲剧) ...... 第二周的时候,不知怎的崩溃出奇的多(应该是合作方更新SDK之后导致的) 当时我正Coding热火朝天,QA和合作方夺命的Call 我就去找那个当时写好的shell脚本,一通翻箱倒柜之后,我悟了,悲剧来了,找不到了 呵呵,被自己强迫症日常清理垃行为给清理了(自己有个日常清理的垃圾的行为,无奈Mac配置就这样)打工人不得不含着泪重新写了一份 (源码在下面),快速应付下那边的夺命Call 然后我就在想这个事,为啥要我来做,也没啥技术含量,为啥不可自动化? Bingo~ 说来就来Shell 源码 crash_txt=$1 crash_log=${crash_txt%%.*}.log # find /Applications/Xcode.app -name symbolicatecrash -type f # cp /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash symbolicatecrash export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer ./symbolicatecrash $1 $2 > $crash_log open $crash_log -a sublime具体如何符号化解析这里就不再唠叨了,网上一大堆:附一个参考链接 使用 Automator 的自动化方案 要使用 Automator 还需要编写 AppleScript 代码。 工具 & 语言工具:Automator Service, 脚本编辑器 语言:AppleScript,Shell脚本编辑器,AppleScript调试用。 好玩的 AppleScript 下面是一些好玩的 AppleScript 代码,唤起你的好奇心: display dialog "你说假如地球没有了空气,我们会怎样... 那么没有工程目录,后面该怎么办?" default answer "会死" buttons {"我知道了"} ¬ default button "我知道了" with title "Handsome ERROR" set theInput to text returned of the result --display dialog text returned of the result if theInput is equal to "会死" then display dialog "没救了" with title "ERROR" buttons {"我知道了"} ¬ default button "我知道了" end if --忽略下面部分say "Hello world"display dialog "Hello World" with title "Alert"display notification "Hello World" with title "Notification"或者直接在终端里面跑 osascript -e "display notification \"Hello World\" with title \"Notification\""-- single comment, # single comment 是单行注释 (* this mutli comment *) 是多行注释 Markdown问题AppleScript脚本里意外出现<p data-line这种代码忽略AppleScript 需要注意的问题 主要还是路径问题 ApeleScript 获取的路径如下: Macintosh HD:Users:xxxxxxx:Documents:xxxxx.app_副本_2.dSYM:这种冒号的路径在shell命令行根本没法用,所以下面代码成了常客: 冒号字符串 打包成数组 set my_array to split(input as string, ":") on split(the_string, the_delimiter) set old_delimiters to AppleScript's text item delimiters set AppleScript's text item delimiters to the_delimiter set the_array to every text item of the_string set AppleScript's text item delimiters to old_delimiters return the_array end split字符串 set target_path to join(my_array, "/"),这里要注意拼接文件与文件夹用的index下标不同: on join(the_array, the_delimiter) set split_str to the_delimiter set target to " " set list_length to the length of the_array set list_length to list_length - 1 set short_list to items 2 through list_length of the_array repeat with dir in short_list set target to target & split_str & dir end repeat return target end join实现过程 思路分析 1、定位dSYM路径 2、定位xx.crash件路径 3、唤起终端,切入指定路径 4、symbolicatecrash解析并重定向输入结果 5、自动打开展示结果 其实这前两步有个大坑:重复下载 dSYM 文件以及导出的 xxx.crash 文件路径会存在空格。在AppleScript调用Shell的时候路径有空格,会报错找不到对应的文件。 解决办法利用 AppleScript 给文件重命名 借助Automator 现有的快捷操作修改期间有周报群群主指点使用 AppleScript 借助 quoted 这个 API 来转义引用空格。 结果是终端识别了,但是symbolicatecrash还是不识别,虽然结果不尽人意,但是学到了新技能。 如果你看过AppleScript API,除了想哭就没别的,上面说的很清楚干啥用,但是不知道语法该咋写。因为没写过这种自然语法,每次都是不停的尝试、失败,尝试、失败,尝试、失败。 AppleScript小众到谷歌都没有,大部分都是查阅Stack Overflow。 我这边选择是第二种 xxx.crash 文件名有空格的解决办法是直接重命名,查找之后直接把空格替换成下划线。dSYM 父目录路径空格,这边多次导出之后会导致父目录存在空格,这个相对上面就比较复杂。 这里有几点思考:在事物本身很难解决问题时,我们就需要放开视野,跳出事物本身,提升更高的角度去思考 当你这么想了,你思考问题的维度和角度就变了 在我们这个问题上,既然它的路径上存在空格,我给它换个不存在的路径不就好了 是不是一个很简单的解决办法,所以有时候不要太局限一点一面一点瞎扯淡 其实日常编码或者修复 BUG 的过程中也会遇到类似情况,我们在一个问题上纠结好久好久到快死了吧!但是问题还没能解决,这个时候就可以尝试:冷静下来 刻意放慢节奏 全身心放松下来 想点别的换换脑子或者睡一觉(我通常就是睡觉) 冥想(这个相对高级 需要练习)不去想这个问题一段时间之后,慢慢就会发现脑子开始活络起来,之前的问题解决办法好像一下子思路如泉涌,睡一觉精神也恢复了,思路也有了,简直两全其美是不是,比死磕一天啥都没有强千百倍吧,最后还得被喷延误工期,拉胯身体,最后无奈身不由己加入996.icu 这个 Big Party。 工程创建 1、选中dSYM文件 -> 右键 -> 服务 -> 创建服务 2、弹出一个快捷操作的模板空工程,可以配置参数入口(因为第一步选中了,参数就不需要配置了) 3、然后就可以拖拽你要的操作(类似于storyboard,xib操作) 4、保存 -> 命名,就会自动存储到本机的~/Libray/Services目录 所有的快捷操作,工作流都会在这个目录,就是说你想用别人写好的最后安装的也是这个目录 示例图:完整的操作步骤脚本交互Shell 调用 AppleScript可以用osascript -e AppleScript调用Shell可以用do shell script & do script do script需要配合终端示例: tell application "Terminal" activate --set new_tab to do script "echo fire" delay 1 do script "pwd" in front window do script "ls" in front window end tell演示模糊了点,为了加载快,压缩的有点狠,但是也能看大概流程就OK了 有两种使用方式启动 dSYM 自动化服务:首先选中 dSYM 文件,然后右键 -> 快捷操作 -> dSYM 首先选中 dSYM文件,快捷键即可(这里需要到 Finder -> Service 偏好设置里面配置好按键)执行流程如下: 1、启动之后就自动去/Users/$(whoami)/Downloads/目录文件下搜索.carsh文件 这里写死Downloads目录的原因是想提高搜索速度,所有导出的时候选择的就是Downloads目录。如果你想要全局搜索也不是不是可以, 但是你得等等Spotlight 2、搜索完毕之后会列出该目录下所有的 .crash 文件。 3、选择对应的文件(build number 一致),就会打开一个终端进入解析流程。 4、解析完毕之后会通过 sublime 打开。没有sublime会怎样? 就去掉 -a sublime ApplesScript 代码负责的部分:冒号:转斜杠/ 调用了剪切板做缓存 display dialog 显示.crash文件的搜索结果 唤起终端,执行解析总结 这里本来想在Automator里面加一个调用Shell脚本的的服务,这样就可以静默解析不用唤起终端,调试过程中解析一直失败,因为运行解析的 symbolicatecrash 需要的环境变量报错,也在对应的目录进行了export,但是最终还是不行,最后还是选择唤起终端来执行操作,或许看起来更酷一点吧 哈哈哈。 其他文件读取/拷贝/搜索/重命名都是Automator提供现成服务。Automator真的很强大,但是你要发现它的美,学会使用它。 最后就是要告诫自己:该做的事还得及时做出来, 不然就是午饭没吃 午休没睡。

人物访谈 | 一位 iOS 程序媛

Jojo 是我的前同事,做事非常细心,负责过团队多项重要功能的开发与建设,是我接触过为数不多的比较优秀的程序媛之一。这次邀请到她,来聊一聊她眼中的 iOS 开发。 简单做个自我介绍吧 大家好,我是 Jojo ,90后,一直从事 iOS 开发的一枚湖南妹子,喜欢交朋友,容易与人相处。现在是一名娃 3 岁➕的宝妈,如果有北京的宝妈,一起遛娃呀,哈哈哈~ 当初学计算机专业出于什么考虑?后来为什么选择了 iOS 开发? 从小农村长大,初中才开始接触电脑,感觉计算机特别神奇,出于好奇,就走了编程这条路。当时 iOS 比较火热,苹果手机高端,能在高端手机上开发 App,会是一件愉快的事情。目前程序员群体的男女比例大致在 9:1,女生的比例还是比较小的。作为女生,你怎么看待这种状况,有质疑自己选择做程序员这件事吗? 我工作时遇到开发的女生还是挺多的,也许现在比例较低,个人认为以后会越来越多。目前为止,我认为自己走上编程这条路没错,也没后悔过。自己是比较简单的人,开发圈相对来讲也比较简单,挺喜欢的。 你目前的工作状态是什么样的,有没有困惑或迷惘? 目前工作状态还不错,知道自己想要什么,需要哪方面充电,也正在往自己希望的方向走,工作三年左右那会有过迷惘,感觉大部分东西能实现,工作中需求也能完成,遇到了程序生涯第一个天花板,却不知道怎么突破,那会感觉这辈子只能这样了。 后来遇到了一群可爱又优秀的人,也不排除年纪增长带来的觉悟,能发现自己的不足,目标越来越清晰,了解目标应具备什么能力,寻求突破。不知道大家有没有过相同感受,我曾经不太喜欢看书,逼着自己都看不下去那种,近期却非常想看书,发现认真读完一本书真的可以学到不少东西。 最近在看《非暴力沟通》,读这本书一开始是因为家里有个调皮小孩,天天跟我唱反调,心累得很。收获,让我懂得分析矛盾产生原因,以及如何去解决矛盾,书中会提供几种方法,总而言之,冲突产生时,学会倾听内心的声音,然后站在对方角度去感受,这样很大可能能够建立积极关系,收到良好反馈。不仅育儿有用,工作中同样受益匪浅。如果大家有看过的好书,感谢推荐给我看看。如果有跟我一样困惑的人,还没遇见影响着自己变的更好的人,那就多看些好的有意义的书,说不定能找到点思路或者多交交朋友。遇见优秀的人多了,自己也会变得更加优秀。 作为程序媛,有没有什么与程序猿不同的工作方式?也可以介绍一下自己的高效工作法。 个人感觉程序媛会更细心,可能这是女性特质。 仅代表个人的一个有效的工作方式是,习惯写 ToDo List 。按照重要和紧急两个维度把待办事项列出来,然后按照紧急重要排优先级一个个完成,这样做会有这些好处: 1、做事不会遗漏。工作中随时都有可能被一些事情打断,有插入事项时,能及时记录进去,我比较健忘,所以对我帮助还挺大的。 2、有助于回顾。写周报时看 To Do List,秒完成。 3、建立目标感。有目标的每天,时间过得快而充实。 关于“程序员是青春饭、35岁失业”等,作为程序媛,你怎么看这个问题? 青春饭 35 岁就失业这个我以前也有过这样的想法,现在则有不同的看法。淘汰的永远是年纪跟能力不匹配的人,所以与其为未来焦虑,不如为现在努力,让自己保持进步和成长。 你觉得工作中最重要的一项技能是什么? 自学能力。曾经就职的公司,开发能用 Swift 的得用 Swift 开发,而我在这之前一直只用 OC ,如果自学能力不够,很难跟得上。不知道大家有没有这样一种错觉,干过编程的,再去干别的,只要给足够多时间,没有学不会的自信😁,我认为这是自学能力带来的信心。 你现在还要带娃,个人时间应该更少了吧,你是如何平衡工作和学习的呢? 相对来讲会减少一点,但也不是没有个人时间,以下方面起到不少作用: 1、提高效率,像上面记录 ToDo List 的方式,自己认为是提高效率方法之一。 2、寻找合作伙伴,平时我婆婆在这边帮我,我老公也会一起带娃。 3、娃上幼儿园后,大部分时间都在学校度过了,目前工作学习带娃没有失衡。 不过追剧逛街的时间确实没以前多了,要买啥,记录下来,申请一下逛次狠的😁。平常还会再抽出一些时间看看书,学习一下。 让你坚持做程序员的动力或目标是什么?有没有什么心得或经验分享给其它程序媛? 在任何行业做好一件事都是有难度的,感觉计算机行业自己也挺喜欢,好像没有理由不继续下去。 对所有女程序员说:如果不讨厌编程,Just do IT。 有没有什么想借助于摸鱼周报进行宣传的? 大家如果对百度感兴趣,可以随时把简历发我邮箱:jojocaonet@163.com,找我内推。马上过年了,买不买新衣回家过年就靠大家了,哈哈哈。 大家如果不嫌弃认识我的话,可通过飞哥加我微信,也可以通过邮件跟我交流哦。

人物访谈 | 一位游戏行业的 iOSer

iHTCboy 是摸鱼周报里负责校对的成员之一。记得他第一次参与校验时,我把一期整理好的周报发给他看,他后来给我发了 1600 字的修改意见,我都震惊了。修改意见里即有关于技术点的,也有格式和语言表述的,关键读过之后,感觉他说的还非常有道理,由此可见他做事认真的态度。下面是访谈内容。 简单介绍下自己吧 大家好,我是 iHTCboy,从事游戏 iOS SDK 开发快五年了。目前在 37手游,以 SDK 开发为主栈,自动化技术实践为副栈。目前我们海外版 SDK 中除了仅剩 1% hook 的 OC 代码,全部是 Swift 代码;在游戏行业有多年经验积累沉淀后,我们团队希望打造和分享一些 SDK 相关的自动化方案或框架,未来会逐渐开源,敬请期待,跟大家一起学习交流&进步! 当初为什么选择计算机这个行业呢? 应该是小时候对黑客的幻想,《电脑报》90后应该都有看过吧?而《黑客防线》、《黑客x档案》、《黑客手册》这些杂志,不知道大家有没有看过,初中时很着迷,初中时用破解软件逃过网吧收费系统,高中帮亲戚修电脑重装系统,有非常大的好奇心和成就感。在对计算机编程有全面了解前,总是幻想自己当上黑客后xxx,神秘又强大是每个男孩都有过的梦想吧!所以不加思索的就选择了计算机。 游戏公司的 SDK 开发日常是什么样的,它跟应用开发有什么区别呢? 每一位不熟悉游戏 SDK 开发的朋友都会问这个问题。应用开发大家可能比较熟悉,我们在开发中,也会引用第三方的 SDK,比如友盟 SDK、微信 SDK,这些 SDK 提供推送、分享或者支付的能力。而游戏 SDK 开发也是这个道理,为游戏提供特定平台的原生 API 功能。举例来说,目前主流游戏都是 Unity 引擎开发,游戏开发好后,可以导出 Android 项目或者 iOS 项目(Xcode 项目),然后在接入原生平台的 API。 游戏发展到今天,已经高度标准化..... 游戏研发专注于游戏内容的设计和开发(跨平台),而 iOS 和安卓平台原生的能力(如支付 IAP)就由我们封装成 SDK,提供接口给游戏调用。 1、SDK 开发包括:帐号体系(包括封装第三方账号体系:WeChat、Facebook、Google、Line、Twitter 等) 支付体系 分享社交 Web页面(游戏活动、客服、攻略等) 数据埋点(广告、留存、数据分析等) 性能监控 ...将以上功能模块封装成 API 给游戏调用,一般就是登录、用户中心(有界面),其它都是业务逻辑处理。 2、SDK 是提供给游戏使用,所以不像 App 快速迭代 UI,而是迭代业务逻辑。详细的开发细节,以后有时间专门写篇文章吧。关注游戏行业的发展,可以参考之前写的文章:游戏出海本地化概述 。 看你发多篇调研苹果审核和内购相关的文章,针对最近苹果在美国败诉,撬开苹果 IAP 的限制,你怎么看?这个改动可能带来的影响有哪些? 我们先来梳理一下,苹果 AppStore 垄断案的几个新闻关键点:时间 概述 引用来源2021-08-26 Apple 与美国开发者就 App Store 更新达成一致,开发者可以使用电子邮件等通信方式与用户共享 iOS App 之外的支付方式信息。 来源2021-08-31 韩国国会立法和司法委员会通过《电信业务法》修正案,禁止占据市场主导地位的应用商店强迫移动内容提供者使用特定支付方式。 来源2021-09-01 日本公平贸易委员会结束对 App Store 的调查,允许“阅读器”类 App 的开发者在 App 内提供访问其网站的链接,开发者可将用户引导向外部网站进行购买。(阅读器 App:提供数字杂志、报纸、书籍、音频、音乐和视频的预付费内容或内容订阅。) 来源2021-09-10 Epic诉苹果垄断案裁决:美国联邦法院命令苹果不得禁止应用发商引导用户通过第三方支付平台付费。法院并不认为苹果公司是手游交易市场垄断者,但认为苹果公司禁止将用户导向其他渠道是反竞争行为。要求苹果必须在12月9日之前执行。 来源1、来源22021-11-10 Yvonne Gonzalez Rogers 法官拒绝了苹果公司关于推迟执行永久禁令的请求,苹果打算根据这些情况要求第九巡回法院暂缓执行。 来源关于 IAP 内购问题,随着 AppStore 体量越发庞大,大多数开发者认为 30% 税不再合理,其实很多事本身就没有公平合理可言,省略一万字~ 你懂的。 大家可能忽视了一个重要的问题:苹果 IAP 收入占比最大的是什么类型的 App?答案是游戏!所以,对非游戏类 App 允许使用 IAP ,目前对于苹果的损失还不是很大,当然,如果没有损失是最好,但是目前的大环境和反垄断,一定会让苹果做出调整。(注:游戏一直以来都是 App Store 中的重头戏,从 2017 年 iOS 11 开始,苹果在 App Store 增加了 Games 游戏标签入口。)对于苹果败诉带来的影响: (1)苹果在美国一定会上诉,一般垄断案可能长达几年时间,所以苹果愿意继续打官司,尽量拖延时间(参考微软 IE 浏览器反垄断案,从1997年到2002年)。 (2)苹果降低 IAP 税率或允许所有 App 使用第三方支付是时间问题,但没有了 IAP,苹果可能会推出其它的服务费。就在 11 月 19 号,谷歌宣布韩国地区的 Google Play 商品允许应用接入第三方支付系统。谷歌目前每年前100百美元抽15%,超过后30%。按谷歌的新规,韩国地区接入第三方支付系统,谷歌依然从中收取11%(26%),也就是说抽成只是被降低 4%。 简单来说,就是谷歌允许开发者使用第三方支付系统,但没有说不能抽成啊!抽成和允许第三方支付,不冲突! 为了应对韩国的新法规,Google 已更新 Google Play 付款政策 。(3)对于开发者来说,肯定是好事。但这件事背后,是大资本之间博弈的结果,一定要明白为什么可以赢,比结果重要。看过你的博客:https://ihtcboy.com/ ,从2008年就开始写了, 这是一个非常长的时间跨度。关于博客部分也有几个问题想问下你。培养一个良好的习惯并持之以恒是一件不那么容易的事情,能讲下你是如何培养写作的兴趣以及对培养一个有益的习惯有什么建议吗? 高中语文课最讨厌的就是写 500 字作文,但没有想到自已大学后会喜欢写作。写作可能是我小时候比较孤僻,很少与人交流,总喜欢自言自语道,也许是这个原因,自己会有很多天马行空的想法或者内心独大。而作文是根据要求写作,限制你话题,没有自由的发挥和空间。 当然喜欢是一回事,写的好不好才最重要!培养写作,我认为要了解写作的注意事项,怎么样的文字修饰有多妙,多模仿多练习。培养习惯,更多是从一个个可行的小目标做起,积累量多了,慢慢就是习惯。切忌急功近利! 写作一定也有通用的规律规则,比如 中文技术文档的写作规范。大家多写写,多总结,一定会有自己的收获。 你写过如何建立自己的开发知识体系的文章,程序员也是一个需要不断学习的行业,关于如何让学习这件事变得有趣,再分享一下你的见解吧? (1)说起来见笑了,建立自己的开发知识体系 只是觉得大家可以往一些通用的方向去梳理。现在回想起来,发现自己也没有什么都懂,可能接触和学习更多知识后,发现自己空白面更多,然后就越想学习。所以,也许再过几年,会有更深的理解,之后再跟大家分享啊。 (2)“不断学习”我认为是21世纪,每个人都要学会的。就像父母要学会用智能手机,否则健康码都打不开,在城市里寸步难行。所以,大家一定要正面接受它,每天都要学习和进步。 (3)“让学习变有趣”,其实我一直认为看书,应该是让人感到愉快的事情。但为什么大家都不愿意学习了呢?我觉得最重要的是,大家越来越急功近利,一切都为更快,从而忘记了体验过程。举个例子,去旅游时,可能为了发一个朋友圈,一直拍照修图,刻意的、绞尽脑汁的想,却没有享受风景的心境。所以学习本身如果慢下来,不只关注结果,还关注或者尝试从问题到结果的推演过程,这样的学习会更有趣一些。 你博客有很多思维导图,思维导图除了能够帮助梳理知识外,还有其他什么好处吗? 大家一定知道一个道理,图片比文字更加形象。思维导图比一段文字也更加生动形象,所以,它不仅能够梳理知识,还能理清知识的层次和架构,帮助记忆知识。举个例子,Vim 快捷键,如果你忘记了,打开思维导图,一看就知道:再比如,说到人工智能,可能想法很多,那么大的框架应该是怎么样的呢? 最近有什么感悟想跟大家分享的吗? 关于内卷的话题,网上有很多讨论,大多数是吐槽或者劝退。我想换一个词,“努力”,为什么会有人很努力?现在大家都达到小康生活后,大多数人会选择躺平,然后自嘲自己是打工人。其实自甘打工的还是少部分人,大部分还是想往上跳一跳当老板的。另一方面在一个阶级中,努力的人,往往是想要跳出这个阶级的。就是这个往上跳的愿望,它需要的正是努力。 所以,努力它本身上没有错,这代表一种向上的态度。但需要注意的是努力的结果,是不是可以为你改变什么?如果可以,我觉得努力无可厚非。否则,就是真内卷。 最后,有一句话送给大家一起共勉:Follow your own course, and let people talk. 走自己的路,让别人说去吧! - 但丁 《神曲》有什么想借助摸鱼周报宣传的? 欢迎大家关注我们掘金公众号:37手游iOS技术运营团队,会定期分享一些有趣的文章。 如果大家有兴趣加入三七互娱,可以查找相关岗位 :https://zhaopin.37.com ,或者将简历发给我内推:ihetiancong@gmail.com。不要担心有没有相关岗位,优秀的人,在哪里都是金子!如果有其他问题想跟 iHTCboy 交流,还可以在留言区评论。

人物访谈 | 一位参加过 WWDC 的 iOSer

Mimosa 是摸鱼周报的编辑之一,负责每期的学习资料整理。在一次聊天过程中他晒出了跟 Tim Cook 的合影,给我们馋哭了。于是就有了这期访谈的主题:WWDC 之旅,也算是拉着 Mimosa 一起回忆了一把,哈哈。因为是在 17 年,WWDC 还是线下,活动真还挺多的,不知道还要多久才开发者大会才能再次回归线下,希望疫情赶紧过去吧。 简单介绍下自己吧 哈咯大家好,我是 Mimosa,中文谐音名棉毛衫,小号是 Mimoku(棉毛裤),是一名 iOS 开发者,工作经验一年左右,是 iOS 摸鱼周报的编辑之一。我是在大学的时候接触的 iOS 开发,当时加入了学校的 iOS Club,在大二那年获得了来自 Apple 的 WWDC Scholarship Winner,从此算是与 iOS 开发结缘,参加了许多与 iOS 相关的活动,毕业到如今一直是从事 iOS 开发。 能讲下自己获得 WWDC 奖学金的经历吗? 首先先介绍一下这个奖学金吧,WWDC Scholarship Winner 是 Apple 每年为全球学生提供的一个小福利,每年 4 月份的时候,Apple 会在官网放出当年 Scholarship 的要求,比如你需要是一个学生,你不能年龄太小等等,你还需要做一个有创意、激动人心的小作品,并提交,全球每年将会有大约 350 名学生的作品会被接收。如果你的作品被接收了,那么将免费获得一张当年 6 月份参加 WWDC 的门票,价值 $1599 😎(且提供住宿、来回差旅费也有机会报销)。在 2020 年之后,这个奖学金改名叫做 Swift Student Challenge 了,且由于疫情这两年的 WWDC 改为线上,就无法去现场参加了。 我获得的是 17 年的奖学金,申请 WWDC 奖学金时我正值大二,当时是刚学完学校的 C 语言课程和数据结构,加入了我们学校的 iOS Club,也是第一次接触 iOS 开发。我还清晰的记得当时是在 Swift 3.0 的环境下用 Swift 2.0 的教学视频在自学😂,周围会 Swift 的人也是屈指可数,只能自己上网摸索。由于当时有学长获得过上一年的 WWDC 奖学金(但由于时间问题没去 WWDC 现场),所以当时的刚学我们也想试一试,就熬了几天用自己学过的所有东西做了一个作品提交了。没成想那年我们学校有 4 位同学获得了奖学金!但最后由于签证问题(我是之前已经有申根和美签了,但我的朋友们没有😢),所以只有我一个人去圣何塞 WWDC 的现场。你当时开发的是什么作品? 那一年是第一次 Apple 要求参赛者制作一个 playground(之前都是要求做一个介绍自己的 App 这种类型的题目),我制作了一个简单又可爱的可视化冒泡排序教程💂‍♀️,运行在 Swift Playgrounds 这个 App 中(可以在 iPad App Store 和 Mac App Store 中找到这个 App),利用这个 App 一侧可以写代码,一侧可以查看运行结果这种所见即所得的特性,来教使用者冒泡排序算法的原理。参加 WWDC 期间有什么好玩的事情,这个过程有什么收获? 可以说是收获颇丰。首先一个是,作为 Scholarship Winner 可以额外参加一个 WWDC 正式开幕之前的 WWDC Scholarship Meeting,由 Apple 组织在一个小剧场和我们讲了一点 Apple 在各个方面做出的一些努力等等,还会给我们发一个 dev 版本的 Apple TV。重要的是,在所有演讲结束后,Tim Cook 会突然走出来和所有 Scholarship Winner 合影,然后接下来就是激动人心的和 Tim Cook 合影环节,我当时的情况是你只需挤到他旁边他就会和你合照一张!🤩据我观察现场大约有 2/3 的人都在成功合影了。(剩下没合影成功的只能落寞地望着 Tim Cook 和他保镖的背影😇对于大多数开发者来说,可能 WWDC 里面重要的是各种各样的 session,但其实也会有一些无关技术但与生活相关的演讲,给我印象深刻的有两个。 一个是在 WWDC 的第二天,由于我时差没倒过来睡不着,参加的当天最早的一个演讲,是奥巴马夫人米歇尔的一个演讲,(虽说我排队时排在前几个但是仍没坐到前排,气死了),她讲了许多科技与生活的事情,谈论了他的孩子,还讲了去长城的经历,吐槽长城人多等等。另有一个让我印象深刻的演讲是来自 NASA 的一个女数学家奶奶,她谈论了她当年是怎么加入 NASA 以及工作至退休期间的经历,讨论了那个年代女性在 NASA 的情况,讨论了那个年代女科学家的境地,让我大受震撼。 作为 Scholarship Winner 还有一大好处就是,除了能接触到说中国话的学生开发者之外,还可以接触到来自世界各地的 Scholarship Winner,由于所有 Winner 住在圣何塞州立大学的宿舍里,我去的那年 350 个 Winner 中大概只有15个是来自大陆,同时黄皮肤的面孔不超过30个,意味着活动中有大量的时间我需要与母语不是中文的人交流。 比如我的室友就来自俄罗斯🇷🇺,他大四在读,我们用散装英语交流了很多东西,他的作品是用 playgrounds 模拟 Metal(?我到现在也没懂这怎么做的😅),他的计算机基础踏实的一塌糊涂,聊天的时候我就像被面试一样,绞尽脑汁地想他到底在说啥。还有一位意大利小哥🇮🇹一看我这是亚洲人面孔,就凑上来问我们是哪里人,问我是不是中国人,问我来自上海嘛,在得到一系列肯定的答复后他说他去同济大学交流过一段时间,然后他突然很想告诉我他是哪个校区,然后我们开始一起苦思冥想同济大学除了四平路还有在哪的校区,想了半天没想出来,意大利小哥也不尴尬,和我面面相觑地走了一路,最后挥手道别😂。在 WWDC 期间的会场外,也会有很多的 tech talk 在附近举办(蹭 WWDC 热度),我误打误撞参加了一个 Realm 举办的 tech talk。一开始先是吹了一波 Realm,然后是邀请了一些从 Apple 离职的人来做对话,结果发现请到了 Chris Lattner(当时就职于特斯拉)。大家伙那叫一个激动啊, 连下面的好多爷爷辈的听众都提了好几个问题(真的爷爷辈👨‍🦳,拄拐杖的,不开玩笑),只能感叹一下计算机技术这该死的魅力。另外 WWDC 的倒数第二天有个 Bash,算是一场露天音乐会,气氛很热烈!来的是 Fall Out Boy(代表作有"The Phoenix","Immortals"等,现场太顶了),大家在现场把酒言欢,给 WWDC 收了个尾。在 Bash 现场还和来自国内各大互联网公司的工程师们合影了,不知道各位看官有没有在照片里面的。在 WWDC 玩的这几天拓宽了我看待技术的视野,把我从学校里教的那些知识里面拉了出来,在会场的内外,看到了更多与业界发展相关的东西,看到了计算机科学的广度和深度。我本科就读于魔都某不知名二本,且不是工科强校,所以我的学校计算机专业水平比起其他获得奖学金的同学的学校来说(我记得有中大、同济、新国大、中科大、港大等🙂)差的不是一点半点,无意贬低我的母校。但确实这次的经历让我觉得我能努力的地方原来还有这么多,我在学校所认识到的那些知识似乎只是计算机科学的冰山一角,突然在心中对一些东西产生了渴望,让我突然对未来感到期待和兴奋,在这些经历和感受一起冲击我心灵之后,我知道了一件事:我掉进代码这个陷阱里了🥰。 听说你还在上海科技馆和 Apple 供应链工厂等场合进行过一些线下演讲,这是一种什么体验? 由于在学校参加的 iOS Club 与 Apple 有一些方面的交流和合作,所以也参加了一些特别的活动,例如在夏天的时候去上海科技馆免费给小朋友上编程课,就是用上文提到过的 Playgrounds 这个 App,也给科技馆的小朋友分享参加 WWDC 的经历(同时我也发现,现在的10岁左右小朋友有编程经历的真不少)。除小朋友外,我还作为助教参加过给 Apple 供应链工厂的 Swift 教学活动,用一个月的时间让一点代码都不懂的工厂员工通过学习实践之后,能够自行开发一个简单的 iOS App。另外我还受邀参加香港教与学博览会分享 playgroundbook 的制作经验,向参观者介绍 Playgrounds 这个 app 作为教学工具的一些优点等等。这些交流的经验带给了我从别的角度看待 Swift 以及 iOS 开发的机会,在准备教学教案这些东西的时候,不可避免地会去研究和比较别的语言、别的技术、别的生态是怎么样的😵,让我了解了一些跨平台技术,对客户端开发多了一点理解。 除了这些技术方面的感悟,这些经历带给我了更多情感上的震撼,比如有位10岁小朋友学过了python、c++等等语言,对编程思想也理解的很到位,而且为人很谦虚,还会帮助别的小朋友理解代码;还比如在工厂中我们作为老师教那些20多岁、30多岁、甚至40多岁的员工写代码,而他们就像是科技馆的小朋友一样,对代码一无所知然后带着好奇和热切的眼神来上课,并且当做不出作业时会很羞愧,这些经历会让我感觉到:诶好像这个世界不是我以为的那个世界这种感觉,我没想到有这么多人在推动编程教育、我没想到这么多人对编程这么大的兴趣、我也没想到学编程对一些小孩子来说是一种消遣、我更没想到多会那么一点点编程能改变一个人的发展。根据给我们的反馈,有一小部分上过我们课的工厂员工由于在编程课的表现很好,被“挖”到了工厂的技术部门,从此工作的地点从流水线换到了办公室🤑。 能分享下你的学习历程以及保持学习热情的一些方法吗? 我想我的答案可能和大家想象的很不一样。在校的时候,我是在我社团的推动下成长的,我参加了很多活动、比赛,几乎都是和 iOS 有关的,这些都是是我的社团带给我的资源和推动,比如去参加移动应用创新赛(Apple 赞助)的期间,我学了 ARKit、CoreML、SceneKit、SpriteKit 等等,了解了很多较冷门的 iOS 技术,了解了隐私政策、了解了怎么上架一个 app、以及审核的诸多事项,我的比赛或者活动需要什么我就去学什么,当然这没有什么不好的,没有这些推动我的话我肯定窝在寝室打 dota(嘿嘿👍)。 但至于其他方面的知识?额我懂得很少,记得有次有个评委发现我的 app 里内存泄漏导致 crash 了,他问我为什么 crash 了,我答不出来;记得大四我去面试的第一家实习是英x流x说✍️,面试官和我说他们以前也有一个奖学金的 winner 实习生,表达的意思就是对我蛮期待的,但是后面的面试让他大跌眼镜,我对这次面试印象很深刻,我从第一个问题开始,没有一个问题是答对的(不夸张),答得面试官都有点怀疑人生,他的眼神像是在问我是不是来砸场子的,我对面试的这些内容算是一点都不懂,当时很难受,也很后悔,后悔第一次面试就去了最想去的公司。再之后经过了准备之后换了一家公司得到了 offer,所以在那段时间,促使我学习的是那种落差感,是那种想要再证明自己的不甘。再往后我去考研了然后技不如人没考上,那段时间更焦虑了,会一直督促自己去学习,所以其实失败才是我源源不断学习动力的源泉,也希望在今后的成长道路上,失败不会成我唯一的学习动力👨‍💻。 有什么想借助摸鱼周报宣传的? 明年计划换工作了,想去大厂被资本家摧残🤡,坐标上海,邮箱:mim0sa@qq.com,欢迎各位老板骚扰。 也希望大家多关注 iOS 摸鱼周报,如果有好的建议和意见快来告诉我们。

人物访谈 | 一位研究生的iOS之路

本期访谈对象是摸鱼周报的主编之一:反向抽烟。他还在读研,因为名字里带个尧字,我们都叫他尧兄。尧兄有一个博客:https://blog.csdn.net/opooc ,分享自己的学习记录,大家有兴趣可以看下。 zhangferry:简单介绍下自己,再讲一下最近的状态吧。大家好,首先感谢飞哥的采访。网名叫反向抽烟,是一名研二在读的大学生,也是 iOS成长之路群的早期用户。目前在做计算机视觉方向学习,一方面对这个领域比较感兴趣,另一方面也是为了毕业设计。在学校的状态一般,强度不大,还是比较随意的。zhangferry:你之前在网易实习,后来又回到了校园,为啥结束了实习?对比校园和职场这两种不同的环境,能分享下你的感受吗?导师给我安排了一些任务,要结束实习工作,所以才回来的。其实我自己不想结束实习,还是挺想上班的。大家一起共事,一起努力完成工作,那种氛围,我还是挺喜欢的。 时间和精力上的分配不太一样,实习更多的是输出吧,校园自由一些,输入会多一些。zhangferry:你接触过的技术方向挺多的,算法、Java、iOS、计算机视觉等,你在选择或者更换技术方向时一般如何做权衡呢?这些方向的本质是没有什么区别,都是为了解决问题,后端也好,前端也好,我觉得都是值得学一下的,毕竟打工嘛,还是得提高自身价值,。zhangferry:尧兄啃过很多本书,能推荐一个对自己影响最大的一本书,简单介绍下吗书的话没有看很多,飞哥既然给面了,那我就推荐一本课外读物《狼的智慧》,强者恒强,适者生存。备注:尧兄真的是在啃书,有一张之前群里发的图片为证:再附一张他推荐过的书目名单:zhangferry:对于学习这件事,你是如何保持热情的?感觉你对学习是沉迷的。可以推荐一些你的学习方法吗?对我而言应该就是兴趣吧,如果对一样技术的态度是为了完成任务而学习,我大概率完成不好。但如果是自主的学习过程中,我有了一些自己的想法,就会竭尽全力的去弄懂。所以主动而非被动,会让学习这件事更有趣味性。 学习方法的话,分两个角度吧,一是学习一定得是刻意的,要拿出时间来经常练习的,这个没有捷径;第二是对于新东西要先用起来,再去研究原理,边用边学,这样效率会高一些。zhangferry:你经历过不少大厂的 iOS 面试,实习面试时考察侧重点是什么样的?对正在找实习工作的同学有什么建议吗?实习面试重点还是在考察基础,有四个部分:算法基础 + 计算机基础 + iOS基础 + 项目/实习。算法是最基本的一项,一般每一面都会问到;计算机基础一般是在一面进行考察;项目/实习一般是在二面/三面进行考察。 准备的话: 1.算法最好有个 300 的题量,一定要在面试前保持题感。 2.计算机基础中考察有计算机网络、编译原理、操作系统、数据结构,网络和操作系统基本是必考,这一部分还是需要花时间理解的,建议平时多花时间钻研下,只背面经是经不住问的。 3.iOS基础准备起来还是有章可循的,推荐看 mj 老师的底层视频和慕课网的 iOS 大牛面试视频,最后把不理解的点做好笔记,及时请教。 4.项目/实习有一样就可以,要是准备项目的话,可以把自己的以前写过的项目进行一些优化,做好总结,把写了简历上的点,都一定要弄明白。 实习面试看公司或者部门,有的公司要实习生去干活,就考 iOS 多一些,有的是为了培养转正,就要求基础牢实一些。主要还是凡事提前准备吧,多往前看几步,早做准备。zhangferry:最近有什么新的感想或心得跟我们分享吗?程序员这个行业其实是与社会关系脱节的,它不是那种社会性质的工作,会经常和人打交道。技术肯定要有的,毕竟是门手艺活,得靠它吃饭,但技术只是一个最最基本的层面,政治书上说了,人的本质是一切社会关系的总和,所以大家还是要多多搞好社会关系,这样人生路才会更宽广,走的更顺畅。(仅为个人观点)

人物访谈 | 微软Offer之路

本期访谈人物是张安宇,他是 #Swift社区 公众号的负责人,最近换工作接了微软(苏州)的 Offer,11月底入职,目前正在爽爽的休假。咱们拉他过来聊聊关于面试微软的经历。 zhangferry:简单介绍下自己吧。大家好,我是 #Swift社区 公众号的负责人,我叫张安宇,很高兴被摸鱼周报邀请来参加这期访谈,我是摸鱼周报的读者,经常看摸鱼周报的文章。 我是一个换工作非常频繁的人,平均每家公司工作 1-2 年甚至更短,比如搜狐和掌阅科技,兄弟们常常因为这个取笑我。关于这个问题我没有特别多的看法,找工作的过程中也没有因为这些受到伤害或者阻挠,相反每次换工作都还挺顺利,大部分情况下是能够受到用人单位的尊重的。 在饿了么工作的时间稍长一些,我在饿了么的蜂鸟团队和北京的前端组主导完成过饿了么入淘的第一个项目叫做蜂鸟商城,同时还负责给我们物流团队的几个 App 统一接入淘宝的浏览器框架 Windvane。 我喜欢玩游戏、看书、看电影,偶尔运动,很喜欢交朋友。平时我跟展菲一起维护我们的这个公众号,还有一些技术群。我们会定期分享一些 Swift 社区动态和相关的资讯,欢迎大家的关注。zhangferry:能简单说下你是如何准备微软的面试的吗,实际面试过程中有哪些比较重要的记忆点?跟国内其他大厂的 iOS 面试有什么区别?微软的面试流程非常长,一共有 6 轮,每轮 1 个小时左右,都是技术面。我之前也没有接触过这种面试形式,所以主要依靠 HR 提供的面试考察点,以及自己去了解来的一些信息来准备这场面试。 这里有个小插曲,正常来说每轮面试间隔应该都是一周左右。因为当时还有另外几家公司在同时面试,跟 HR 说想加快进度,结果后面 2 天就安排完了剩余的 5 轮面试。 微软的 HR 会特别详细地向大家说明每一轮面试的主要步骤,我觉得最重要的是进行针对性地准备。微软面试的核心考察点就是 coding 环节,算法在每轮面试都有,如果代码功底不足,建议继续刷题再参加应聘。整体来说,是在能白板编程 + bug free 的前提下,再根据自己多年的工作经验,与面试官交流一些 iOS 方面的通用性技术。 实际面试中,我能够比较从容应对的主要原因是熟练 + 自信。我有 NOIP 与 ACM 的参赛经验,加上面试前认真刷了半个月 Leetcode,基本上遇到题目不会发慌。因为备战仓促的原因,我刷的题目数量也有限,实际面试过程中,也没有遇到最近刷过的原题。好在微软的题目不算太难,毕竟面试时间只有一小时左右,基本上是 mid 难度的题目。在现场编写时我都基本做到了编译通过、结果正确,并且能分析出算法的时间复杂度和空间复杂度。我觉得我这个表现对于微软来说,是最低要求了,如果 coding 不过关,应该也是会得到面试官的 no hire 的。 我想声明一下,微软的面试要求其实不低,并不是说我就凭刷了两周题目(60 题左右)就通过了面试,整个面试过程的考察还是很全面的,算法只是其中一部分。希望大家不要误解这一点,不要黑微软,谢谢大家。zhangferry:你面过很多国内大厂,在你看来微软跟他们比起来有什么区别呢?工作了这么多年,我几乎参加过所有公司的面试,确实微软这样的企业与国内很多互联网企业的面试要求、流程、侧重点都不一样。 据我观察,头部的互联网企业,面试大都是以面试题八股文为主,最后结束以前面试官可能会考一下算法题,也可能不会。像字节的话就比较看重算法题,相信这个大家都比我更了解。 微软对于算法能力更看重一些,整个面试过程对于候选人的考察点聚焦在算法题,coding 能力。coding 对应的是现场写代码,比如手写 GCD 代码,实现一个多线程的需求。还会有一些相对开放的题目,比如问是否关注过最近的 WWDC,苹果的一些最新特性是什么;如果实现一个词云功能有什么思路,中英文如何分词等等。zhangferry:你应该通过了多家互联网公司的面试,为什么最终选择了微软,而且还要从北京搬到苏州?这个并没有特别多,我是收到微软面试的邀请以后决定离职,因为刷题需要付出很多精力与时间,我曾戏谑说,我备战高考时也不曾如此认真,哈哈。 确实除了微软以外,还有其他公司也在接触,基本都聊的很愉快很顺利,但是不多。我没怎么主动投递自己的简历,甚至简历都没来得及更新,从离职到 Offer 大约两周时间,对于这么密集的面试流程来说,时间是非常仓促的,所以也不能说难度不大。 为了这 6 轮面试,我腾不开时间鸽掉了一些公司,有一家公司我鸽了两次……我怀疑我已经被这些公司屏蔽拉黑了,但我也没办法哈哈。 选择微软的原因,我觉得应该是目标职位的匹配程度比较高,还有微软给出的薪资待遇足够慷慨大方。入职的是 Edge 部门,我说过我喜欢前端技术,也喜欢浏览器内核研发相关的领域,我希望这份工作能够让我继续成长突破自己。 在接受 Offer 的过程中,我坦言过自己可以接受落差,因为我没去过微软,所以对微软所有的了解只能来自于想象与道听途说。希望我的苏州之旅,能够真正让我了解到这家公司,也很乐意在朋友圈与朋友们继续分享我的成长路程与心得体会。 为什么愿意去苏州,这个话题比较沉重。我是特别舍不得北京的,最开始我拒绝过一次微软的面试邀请,也是因为微软在北京没有 iOS 职位的原因。最后愿意去苏州,说实话也是对北京这个城市很多地方感到不太满意。 北京是好,但在北京的这些年,真的没有太多归属感,永远在内心里把自己当做低人一等的外地人。所以搬家去苏州对我来说也没那么难受,毕竟我在北京没有根,我自嘲我这种没有根的人,也是很洒脱的,我想去哪里就去哪里。值得一提的是,微软因为这个给了我搬家费,还不少,令我挺感动的。 我很庆幸微软的 HR 都特别大度,没有把我之前拒绝面试这件事放心里,后来很快就给我重新安排了面试,并且也很热心的提供了很多资料给我。zhangferry:能否简单介绍下已知的微软工作环境、入职的部门?对于英语是否有特殊要求,有没有英文的面试?在你看来微软更喜欢招什么样的人?据我了解,微软的工作环境非常的好,不管是苏州的研发中心,还是北京的总部。我入职的部门是 Edge 团队,我听说 Bing App 即将与 Edge App 合并,希望这件事不会对我的 Offer 有什么影响,哈哈哈。 Edge 是微软自研的浏览器,我相信很多人都知道,Edge 除了 Windows 客户端以外还有 Mac 和 iOS 端,当然也有 Android 端。据我了解,我应该可以去这个团队学习一些比较深入的浏览器内核技术,以及一些 C++ 技术。我希望我以后能成为这方面的大牛,哈哈。 英语的话,当然是要能够日常交流,起码阅读、发 email 能做到没有障碍吧我觉得。面试过程中,我没有遇到说英文的面试官,但是不排除别人不会遇到哈,毕竟微软是一家跨国企业。 我觉得微软喜欢招什么样的人?哈哈这个问题太大了,不过了解我的知道我这个人很狂我也没啥不敢讲的话,我觉得微软应该喜欢招天才进来,真正的天才。不是我自诩天才,是我觉得微软公司配吸引天才们加入这里,一起去实现改变世界的想法,真的。zhangferry:对于正在面试和即将面试的人有什么好的建议?据我了解我有个师弟也在面试微软,他比我优秀多了,我认为他没有问题,他应该刚从中科院读完硕士,在我眼里属于学霸,我认为他进入微软没有太大问题,加油。 对于即将面试的人有什么建议的话,我觉得真的比较简单,那就是调整好心态,认真刻苦复习算法与数据结构,这是一块特别有针对性的领域。 你面试其他公司可能还需要复习一下编译原理之类的,但是对于微软的面试流程,对于 coding 环节来说,我认为就可以不用再额外付出时间精力去复习这些。目标性非常强,所以很容易成功。祝愿和希望更多优秀的人才加入微软和我做同事,哈哈。zhangferry:有什么想借助于摸鱼周报宣传的。当然是替微软招人啦。虽然我目前还没入职,还是想借此机会,替微软的 HR 宣传一下,微软苏州研发中心,大量 HC 期待大家加入。前端、后端、全栈、算法、Data Scientist 都需要,感兴趣的可以投递简历到:a-huili@Microsoft.com。 也希望没有关注过 #Swift社区 公众号的朋友,能点个关注,支持一下我的事业,谢谢大家。zhangferry:你经常在朋友圈发不少长文,最近肯定也有不少生活或者工作的感悟吧,可以给我们分享一下。当然有,最近有很多兄弟说被我的精神感动了,说我怎么说到做到,说刷题就刷题,说换工作就换工作呢,每天还刷到后半夜,太励志了。 我想了一下,可能也是有那么点励志吧,哈哈。人生需要目标,需要理想,需要榜样,看着别人奋斗你也能被感染与鼓舞的,我觉得这就是力量,让生命变得更加精彩的力量。 我在朋友圈编过一句鸡汤,我说“我喜欢光,我追寻光,同时我也想成为光”。我觉得大概就是这个意思吧,好的气质,互相吸引互相鼓舞,这是很难能可贵的事。不过鸡汤喝多了会腻,在身边找一个真正的榜样,向他看齐,脚踏实地的前进,时间久了你可能会发现已经与他并肩而行了。 我就有很多技术偶像和技术榜样,我很荣幸他们大部分都通过了我的好友申请,特别开心。希望大家都能找到自己的榜样。 如果大家不嫌弃想认识我的话,可以通过飞哥找到我,我这个人真的很爱交朋友,哈哈,兄弟们都说我有社交牛逼症…… 再次感谢飞哥的邀请,参加这期摸鱼周报的访谈,谢谢。

MachO 代码签名剖析

验证代码的正确性是计算机科学中最难的问题之一,因为不存在普遍意义的正确的算法,所以这一验证通常使用数字签名处理。数字签名主要做两部分工作:验证代码的来源是否合法。 代码是否被修改过。代码签名并非苹果独有技术,Java 和 Android 的 Dalvik 都在使用,但苹果公司是最早开始使用的。大家可以通过阅读下文思考代码来源是否合法和代码是否被修改过的验证是如何实现的。 本篇文章主要参考自 Jonathan Levin 的《最强 iOS 和 macOS 安全宝典》代码签名一章。测试环境:macOS 11.2.3。 测试项目:/bin/ls 在 x86_64 架构下的 MachO 文件。iOS 下的文件与之相差不大。代码签名格式 在了解代码签名机制前,非常有必要了解代码签名的包含的内容。代码签名附着在 MachO 的尾部。加载命令为LC_CODE_SIGNATURE,它指向一个超级二进制块Code Signature,该二进制块又包含了多个其他的子二进制块。之前写过一篇文章,有讲解如何手动解析这个签名二进制块:深入理解MachO数据解析规则。 下面是该二进制块的层级结构:超级二进制块是一个目录性质的结构,用于指定子二进制块的位置,各个子二进制块才是代码签名的主要角色。 子二进制块类型 子二进制块类型通过不同值进行表示:值 二进制块类型0x0000 代码目录0x0002 需求0x0005 授权0x10000 CMS 二进制块0x10001 身份证明(未使用)本篇主要就是对这几个子二进制块的功能和部分实现进行分析。 二进制块的提取 jtool 是 Jonathan Levin 开发的一款主要用于 MachO 分析的高效工具,可以使用 homebrew 进行安装。 $ brew install jtool我们可以使用 jtool 单独提取代码签名部分: $ jtool -arch x86_64 -e signature /bin/ls Extracting Code Signature (5728 bytes) into ls.signature $ od -t x1 -A x ls.signature #原始字节内容 0000000 fa de 0c c0 00 00 14 86 00 00 00 03 00 00 00 00 0000010 00 00 00 24 00 00 00 02 00 00 02 61 00 01 00 00 0000020 00 00 02 9d fa de 0c 02 00 00 02 3d 00 02 01 00 0000030 00 00 00 00 00 00 00 7d 00 00 00 30 00 00 00 02 0000040 00 00 00 0e 00 00 d2 30 20 02 0b 0c 00 00 00 00 0000050 00 00 00 00 63 6f 6d 2e 61 70 70 6c 65 2e 6c 73 # ...也可以使用 MachO 找到 Code Signature 块进行查看。 代码签名的子二进制块 我们可以使用 jtool 查看代码签名内容的分析: $ jtool -arch x86_64 --sig -v /bin/ls Blob at offset: 53808 (5728 bytes) is an embedded signature of 5254 bytes, and 3 blobs Blob 0: Type: 0 @36: Code Directory (573 bytes) Version: 20100 Flags: none (0x0) Platform Binary CodeLimit: 0xd230 Identifier: com.apple.ls (0x30) CDHash: 46cc1da7c874a5853984a286ffecb48daf2f65f023d10258a31118acfc8a3697 (computed) # of Hashes: 14 code + 2 special Hashes @125 size: 32 Type: SHA-256 Requirements blob: a8ccc60c2a5bff15805beb8687c6a899db386d964a5eb3cf3c895753f6879cea (OK) Bound Info.plist: Not Bound Slot 0 (File page @0x0000): e4a537939e00f4974e02b03d36e4dab75f7dc095d2214ba66bc53c73c145ceff (OK) Slot 1 (File page @0x1000): ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7 (OK) Slot 2 (File page @0x2000): ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7 (OK) Slot 3 (File page @0x3000): 4a7cb3e6c1b3a6ac82e3575239ee53d4f0d3bed260fed63438fd21ce0d00392e (OK) Slot 4 (File page @0x4000): 9ec9e4e02292dfda34ef3caa8317e8bfbcc41a46b18d994dba45febe31b8c660 (OK) Slot 5 (File page @0x5000): 037285f744f366210cde48821261d4a5f5b739dcf0b82f94144613e92c4b7c07 (OK) Slot 6 (File page @0x6000): be89c764e52382702918f2db62ff24d9df40410fe894b11d505a4abc1f854340 (OK) Slot 7 (File page @0x7000): a6b322014743965656e796155c1e0bf22e19a3e8770a43f1111cfbc961037d26 (OK) Slot 8 (File page @0x8000): a643fc9485d941019cbdeead1d5c47add9382417ebe4d15768221f3763553b84 (OK) Slot 9 (File page @0x9000): ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7 (OK) Slot 10 (File page @0xa000): ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7 (OK) Slot 11 (File page @0xb000): ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7 (OK) Slot 12 (File page @0xc000): 23304ae11c1ade4411cb63a0955eb644574b8af416e4e3818e382421272ae1b4 (OK) Slot 13 (File page @0xd000): e0ca7b7000d04057e71c49365b1937711b3557f6b91e0fa144791c66de2a7a4d (OK) Blob 1: Type: 2 @609: Requirement Set (60 bytes) with 1 requirement: 0: Designated Requirement (@20, 28 bytes): SIZE: 28 Ident: (com.apple.ls) AND Apple Anchor Blob 2: Type: 10000 @669: Blob Wrapper (4585 bytes) (0x10000 is CMS (RFC3852) signature) CA: Apple Certification Authority CN: Apple Root CA CA: Apple Certification Authority CN: Apple Code Signing Certification Authority CA: Apple Certification Authority CN: Apple Root CA CA: Apple Certification Authority CN: Apple Root CA CA: Apple Certification Authority CN: Apple Code Signing Certification Authority CA: Apple Software CN: Software Signing Time: 201222002625Zi它有三个 Blob,即三个子二进制块,Blob 0 是代码签名 Blob 1是需求,Blob 2 是 CMS,下面是对这几个 Blob 的分析。 代码目录(Code Directory) 代码目录是签名块的主体,它提供了签名资源的散列值(哈希值)。代码签名并非对整个文件进行签名,因为有时二进制文件可能很大,计算全部内容占用资源较多;而且二进制的加载是按需加载,不会一开始就都全部映射到内存中。签名时会将整个 MachO 文件划分成多个页,每个页单独签名。 代码目录部分就是对签名信息的描述,其中包含了各个分页的签名值,签名算法和分页大小等内容。代码签名的数据结构如下: /* * C form of a CodeDirectory. */ typedef struct __CodeDirectory { uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ uint32_t length; /* total length of CodeDirectory blob */ uint32_t version; /* compatibility version */ uint32_t flags; /* setup and mode flags */ uint32_t hashOffset; /* offset of hash slot element at index zero */ uint32_t identOffset; /* offset of identifier string */ uint32_t nSpecialSlots; /* number of special hash slots */ uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ uint32_t codeLimit; /* limit to main image signature range */ uint8_t hashSize; /* size of each hash in bytes */ uint8_t hashType; /* type of hash (cdHashType* constants) */ uint8_t platform; /* platform identifier; zero if not platform binary */ uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ uint32_t spare2; /* unused (must be zero) */ /* Version 0x20100 */ uint32_t scatterOffset; /* offset of optional scatter vector */ /* Version 0x20200 */ uint32_t teamOffset; /* offset of optional team identifier */ /* followed by dynamic content as located by offset fields above */ } CS_CodeDirectory;结合 CodeDirectory 的偏移量,可以从 MachOView 里查看到这部分数据的内容:找到对应数据结构中的含义,我们关注其中三个 uint8_t 类型的值:参数 值 含义hashSize 0x20 hash 值大小,为 0x20 字节。hashType 0x02 表示签名算法,0x01 表示 SHA-1,0x02表示SHA-256。从 macOS10.12 和 iOS11开始,苹果转向使用 SHA-256。pageSize 0x0C 这里是一个计算公式:log2(PageSize) = 0x0C根据公式算出分页大小:PageSize = 2 ^ 0x0C = 4096 = 0x1000 = 4K。这跟系统的内存分页大小是一致的。 由此可知整个 MachO 文件会按照 0x1000 字节的大小进行分页,分页使用 SHA-256 算出散列值。这些计算出的散列值会记录在代码插槽(Code Slots)里。 代码插槽验证 上面Slot 从 0 到 13 的标记对应的都是代码插槽。 有了计算规则我们还可以手动验证代码签名的正确性,我们以前三个代码插槽为例,也即前 0x1000 字节的内容,尝试手动计算其散列值。 $ lipo /bin/ls -thin x86_64 -output /tmp/ls_x86_64 $ dd bs=0x1000 skip=0 count=1 if=/tmp/ls_x86_64 2>/dev/null | openssl sha256 SHA256(stdin)= e4a537939e00f4974e02b03d36e4dab75f7dc095d2214ba66bc53c73c145ceff $ dd bs=0x1000 skip=1 count=1 if=/Users/zhangferry/ls_x86_64 2>/dev/null | openssl sha256 SHA256(stdin)= ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7 $ dd bs=0x1000 skip=2 count=1 if=/Users/zhangferry/ls_x86_64 2>/dev/null | openssl sha256 SHA256(stdin)= ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7注意到后面两个插槽计算结果一样,这是因为这两部分数据为补齐位,它们全部为0。跟前三个代码插槽的值进行对比: Slot 0 (File page @0x0000): e4a537939e00f4974e02b03d36e4dab75f7dc095d2214ba66bc53c73c145ceff (OK) Slot 1 (File page @0x1000): ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7 (OK) Slot 2 (File page @0x2000): ad7facb2586fc6e966c004d7d1d16b024f5805ff7cb47c7a85dabd8b48892ca7 (OK)发现两边散列值一样,输出内容后面的 OK 是 jtool 验证的结果。 这里可以看到代码插槽有14个,File page 里的内容表示相对起始地址。另外注意到输出部分有一句注释: # of Hashes: 14 code + 2 special其表示有 14 个代码插槽和 2 个特殊插槽。 特殊插槽 特殊插槽的出现是因为App由多个内容组成,并非只有二进制文件,为了保证这些非二进制文件的完整性,对它们也会进行签名,它们的签名值就是特殊插槽。因为代码插槽的索引是从0开始的,而且其大小不固定,为了把特殊插槽也能排列进去,就选用负数来表示特殊插槽的含义。以下是特殊插槽的定义:# 插槽目的-1 绑定的info.plist-2 需求(requirement):二进制块嵌入代码签名-3 资源目录:CodeSignature/CodeResources文件的散列值-4 具体应用:实际上未被使用-5 授权(entitlement):嵌入在代码签名中的授权我们可以在上方 jtool 的输出内容里找到特殊插槽的内容: Requirements blob: a8ccc60c2a5bff15805beb8687c6a899db386d964a5eb3cf3c895753f6879cea (OK) Bound Info.plist: Not Bound因为特殊插槽作用是固定的,也就没用序号表示。 代码签名需求(Requirements) 目前代码签名只是分块取散列值,保存起来,但好像还不够强大。苹果公司已经为代码签名增加了另外一个机制:需求(requirements)。它可以自定义规则以施加特定限制,比如允许哪些动态库加载。 需求有一套特殊的语法规则,其表达由操作数和操作码组成,丰富的操作码集使得构建任何数量的逻辑条件成为可能。可以在requirements.h 文件里查看都有哪些操作码。 enum ExprOp { opFalse, // unconditionally false opTrue, // unconditionally true opIdent, // match canonical code [string] opAppleAnchor, // signed by Apple as Apple's product opAnchorHash, // match anchor [cert hash] opInfoKeyValue, // *legacy* match Info.plist field [key; value] opAnd, // binary prefix expr AND expr opOr, // binary prefix expr OR expr opCDHash, // match hash of CodeDirectory directly opNot, // logical inverse opInfoKeyField, // Info.plist key field [string; match suffix] opCertField, // Certificate field [cert index; field name; match suffix] opTrustedCert, // require trust settings to approve one particular cert [cert index] opTrustedCerts, // require trust settings to approve the cert chain opCertGeneric, // Certificate component by OID [cert index; oid; match suffix] opAppleGenericAnchor, // signed by Apple in any capacity opEntitlementField, // entitlement dictionary field [string; match suffix] exprOpCount // (total opcode count in use) };对需求的编译是由 csreq 进行的,对需求的验证可以使用 codesign -v。 我们这里来尝试解读下已有的需求内容。 大部分二进制文件的需求只是验证签名身份,即使用证书是否为苹果所颁发。在 App Store 里的应用则使用更严格的规则集。我们可以查看 Xcode 的代码签名需求: $ codesign -d -r- /Applications/Xcode.app/Contents/MacOS/Xcode Executable=/Applications/Xcode.app/Contents/MacOS/Xcode designated => (anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = APPLECOMPUTER) and identifier "com.apple.dt.Xcode"注意这里有不少 1.2.840.113635 开头的标识,它代表的是国际通用标准证书中苹果公司的分支(iso.member-body.us.appleOID)。其中100对应了安全相关的一些定义 appleDataSecurity,详细内容可以看这里oidref.com。对照Xcode的签名需求,我们可以大致推断出这些规则的含义:由苹果签名且证书节点包含 6.1.9 即 Mac App Store App。 或由苹果签名且证书节点包含 6.2.6 即 "dev_program"。(推测是开发版本的应用) 其证书节点包含 6.1.13 即 Developer ID Applications。 证书的团队标识符(OU)为 APPLECOMPUTER 且 BundleId 为 com.apple.dt.Xcode。注意其中最后一项的内容,限定了团队标识符和BundleId,这样就能够解决应用被重签名的问题了。 CMS CMS 是Cryptographic Message Syntax的缩写,是一种标准的签名格式,由RFC3852定义。书中并没有提这部分内容,但我认为这部分恰恰是代码签名最关键的步骤。 CMS 格式的签名中,除了包含证书之外,还承载了一些其他的信息,比如签名属性 signedAttrs。 上面说了 CodeDirectory 里保存了 MachO 分页的 Hash 值,只要保证这个 CodeDirectory 不被修改就可以了。所以对代码目录进行 Hash 计算,获得 CDHash,然后对这个 CDHash 进行签名就可以了。 注意这一步才是真正的签名,其开始涉及加密,前面的代码插槽只是提供摘要信息。 注意到 jtool 的签名输出里有这样一句: CDHash: 46cc1da7c874a5853984a286ffecb48daf2f65f023d10258a31118acfc8a3697 (computed)这就是外部计算的 CDHash 值,用于跟 signedAttrs 里的内容进行对比。而更关键的是对 signedAttrs 的加密验证,实际验证流程比较复杂,感兴趣的小伙伴可以阅读这篇细说iOS代码签名(三)。 我结合文中签名校验内容和上面的代码插槽,画出了表示签名校验的整个流程:这里有两处 Hash 对比,一个是对 signedAttrs 的解密,确保其是可信任的。另一处是 CDHash 的对比,确保代码未被修改。 signerInfo 里包含了 signedAttrs 、签名使用的 Hash 算法、加密算法、签名数据等信息。再结合证书里的公钥,我们就可以验证,signedAttrs 的有效性。 授权 除了确保代码的真实性和完整性,代码签名还为苹果公司及其强大的安全机制提供了授权(entitlement)功能。授权文件也被包含在签名里,其散列值放在索引为-5的插槽中。授权文件是一个 XML 格式的文件,我们可以使用 jtool --ent 查看其内容,因为 ls 没有授权文件,我们以 Mac 端微信为例进行查看: $ jtool -arch x86_64 --ent /Applications/WeChat.app/Contents/MacOS/WeChat <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.app-sandbox</key> <true/> <key>com.apple.security.application-groups</key> <array> <string>5A4RE8SF68.com.tencent.xinWeChat</string> </array> <key>com.apple.security.device.audio-input</key> <true/> <key>com.apple.security.device.camera</key> <true/> <key>com.apple.security.device.microphone</key> <true/> <key>com.apple.security.files.downloads.read-write</key> <true/> <key>com.apple.security.files.user-selected.read-write</key> <true/> <key>com.apple.security.network.client</key> <true/> <key>com.apple.security.network.server</key> <true/> <key>com.apple.security.personal-information.location</key> <true/> </dict> </plist>这里我们可以看到其中包含了沙盒、application-groups、声音输入、摄像头等一系列权限。在应用访问特定 API 的时候苹果可以根据这些授权判定该行为是否合法。因为苹果公司是应用的终极签名者,所以签名过程中也可以很容易的修改授权,比如 com.apple.security.sandbox.container-required 这一表示沙盒权限的值就会被强制安置到授权文件中。 强制验证代码签名 为了使代码签名真正有效,非常重要的一步就是要保证验证过程顺利执行,没有遗漏。当前签名验证是发生在内核模式下,而非用户模式下。签名的验证发生在两个阶段:加载可执行文件时、实际访问二进制代码时(Page Fault)。分成两个阶段也是出于性能方面的考虑,因为二进制文件是动态加载的,对于还没加载的部分仅当其加载如内存时,也即发生 Page Fault 时再进行签名验证。 可执行文件的加载 可执行文件的加载出现在execve()/mac_execve()或posix_spawn()系统调用被触发的时候。对于MachO,exec_mach_imgact()会被调用,在解析文件时它会找到LC_CODE_SIGNATURE的位置。代码签名二进制块会被加载到内核的统一高速缓存缓冲区中。 Page Fault时的处理 可以查看 osfmk/vm/vm_fault.c 的代码: /* * CODE SIGNING: * When soft faulting a page, we have to validate the page if: * 1. the page is being mapped in user space * 2. the page hasn't already been found to be "tainted" * 3. the page belongs to a code-signed object * 4. the page has not been validated yet or has been mapped for write. */ #define VM_FAULT_NEED_CS_VALIDATION(pmap, page, page_obj) \ ((pmap) != kernel_pmap /*1*/ && \ !(page)->cs_tainted /*2*/ && \ (page_obj)->code_signed /*3*/ && \ (!(page)->cs_validated || (page)->wpmapped /*4*/))当 Page Fault 满足以上条件时将触发签名验证过程: 1、该页面正在用户空间中映射 2、这个页面还没有被发现为 tainted 3、该页属于一个代码签名对象 4、页面还没有被验证,或者还没有被映射为可写状态 代码签名的漏洞 代码签名机制虽然强大,保护着应用的安全,但依然被攻破过,以下讲解几例曾经出现的漏洞。 JIT(即时生成代码) 该情况发生在 Page Fault 过程,如果该页内容是用于 JIT,将会被特殊标记,可以创建和执行任意代码,而无需代码签名。 从 iOS 10 开始,苹果公司开始在64位的设备上加固 JIT。采用专门的 memcpy() 将JIT映射到可执行但不可读的内存上,然后可执行的 JIT 映射为不可写,可写的 JIT 映射为不可执行状态。 Jekyll 应用 Jekyll 应用的含义是应用在提交至 App Store 时表现为无害,但其实它包含恶意功能,只不过这些功能处于休眠状态。过审之后和本地服务器进行合作,自愿公开其地址空间和符号,通过代码注入或者返回导向编程(Return Oriented Programming,ROP),触发预置的恶意程序。 目前还没有可靠的打击 ROP 的方法,但因为沙盒机制的缘故,恶意代码的影响范围是可控的。 苹果公司使用 LLVM BitCode 向 App Store 提交应用的方案,也会使恶意应用难以事先知晓其地址空间。 内存锁定 从上面我们知道发生Page Fault会触发签名验证的流程,那如果没有Page Fault就不会存在签名验证了。按照mmap -> mlock -> memcpy -> mprotect 的调用顺序,应用可以修改可执行内存,以任何看起来合适的方式修补内存。虽然XNU通常会阻止将曾经可写的内存设置为r-x,但当内存锁定时,会绕过该检测。 苹果在iOS 9.3中修复了这个漏洞。 总结 我们再来尝试回答开头上面遗留的问题: 1、如何验证代码的来源是否合法? 主要通过证书来验证来源是否合法,所有的开发者证书都由苹果颁发,且被 Root CA 认证。另外依托于需求(requirements),还可以再扩展一些其他验证方式。 2、如何确认代码是否被修改过。 主要通过代码插槽和 CDHash,再对 CDHash 进行签名,就可确认其是否被修改过。注意实际验证流程有两处关键的 Hash 比对,可以再结合上面的流程图加深理解。

Category无法覆写系统方法?

这是一次非常有趣的解决问题经历,以至于我认为解决方式可能比问题本身更有意思,另一点就是人多力量大,多人讨论就会获得多种思路。 首次提出这个问题的是反向抽烟,他遇到了不能用 Category 覆写系统方法的现象。问题抛到我这,我验证了这个有点奇怪的现象,并决定好好探究一下,重看了 Category 那部分源码仍没有找到合理解释,于是将这个问题抛到开发群里,最后由皮拉夫大王在此给出了最为合理的解释。之后我又顺着他的思路找到了一些更有力的证据。以下是这一过程的经历。问题提出 以下内容出自反向抽烟: 背景:想为 UITextField 提供单独的属性 placeholderColor ,用来直接设置占位符的颜色,这个时候使用分类设置属性,重写 setter 和 getter,set中直接使用 KVC 的方式对属性的颜色赋值;这个时候就有个bug,如果在其他类中使用 UITextField 这个控件的时候,先设置颜色,再设置文字,会发现占位符的颜色没有发生改变。 解决思路:首先想到 UITextField 中的 Label 是使用的懒加载,当有文字设置的时候,就会初始化这个label,这时候就考虑先设置颜色根本就没起到作用; 解决办法:在分类中 placeholderColor 的 setter 方法中,使用runtime的objc_setAssociatedObject先把颜色保存起来,这样就能保证先设置的颜色不会丢掉,然后需要重写 placeholder的setter方法,让在设置完文字的时候,拿到先前保存的颜色,故要在placeholderColor 的getter中用objc_getAssociatedObject取,这里有个问题点,在分类中重写 placeholder 的setter方法的话,在外面设置 placeholder 的时候,根本不走自己重写的这个 setPlaceholder方法,而走系统自带的,这里我还没研究。然后为了解决这个问题,我自己写了个setDsyPlaceholder方法,在setDsyPlaceholder里面对标签赋值,同时添加已经保存好的颜色,然后与setPlaceholder做交换,bug修复。 这里大家先不要关注解决 placeholderColor 的方式是否正确,以免思路走偏。我们应该避免使用Category 覆写系统方法的,但这里引出了一个问题:如果就是要覆写系统的方法,为啥没被执行? 问题探索 我测试发现自定义类是可以通过 Category 覆写的,只有系统方法不可以。当时选的是 UIViewController 的viewDidLoad 方法,其他几个 UIViewController 方法也试了都不可以。 测试代码如下: #import "UIViewController+Test.h"@implementation UIViewController (Test)- (void)viewDidLoad { NSLog(@"viewDidLoad"); }@end所以猜测:系统方法被做了特殊处理都不能覆写,只有自定义类可以覆写。 有一个解释是:系统方法是会被缓存的,方法查找走了缓存,没有查完整的方法表。 这个说法好像能说得通,但是系统缓存是库的层面,方法列表的缓存又是另一个维度了。方法列表的缓存应该是应用间独立进行的,这样才能保证不同应用对系统库的修改不会相互影响,所以这个解释站不住脚。 这时有朋友提出他们之前使用Category 覆写过 UIScreen 的 mainScreen,是可以成功的。我试了下确实可以,观察之后发现该属性是一个类属性。又试了其他几个系统库的类属性,也都是可以的。 所以猜测变成了:只有系统实例方法不能被覆写,类属性,类方法可以覆写。 这时已经感觉奇怪了,这个规律也说不通。后来又有朋友测试通过 Xcode10.3 能够覆写系统方法,好嘛。。。 这时的猜测又变成了:苹果在某个特定版本开始才做了系统方法覆写的拦截。 可靠的证据 皮拉夫大王在此提出了很关键的信息,他验证了iOS12系统可以覆写系统方法(后来验证iOS13状况相同),iOS14不能覆写。 但iOS14的情况并不是所有的系统方法都覆盖不了,能否覆盖与类方法还是实例方法无关。 例如:UIResponder的分类,重写init 和 isFirstResponder,init可以覆盖,isFirstResponder不能覆盖。在iOS14的系统上NS的类,很多都可以被分类覆盖,但是UIKit的类,在涉及到UI的方法时,很多都无法覆盖。 这里猜测:系统做了白名单,命中白名单的函数会被系统拦截和处理。 以下是对 iOS14 状况的验证,覆写isFirstResponder,打印method_list: unsigned int count; Method *list = class_copyMethodList(UIResponder.class, &count); for (int i = 0; i < count; i++) { Method m = list[i]; if ([NSStringFromSelector(method_getName(m)) isEqualToString:@"isFirstResponder"]) { IMP imp = method_getImplementation(m); } }isFirstResponder会命中两次,两次po imp的结果是: //第一次 (libMainThreadChecker.dylib`__trampolines + 67272) //第二次 (UIKitCore`-[UIResponder isFirstResponder])同样的代码,在iOS12的设备也会命中两次,结果为: //第一次 (SwiftDemo`-[UIResponder(xx) isFirstResponder] at WBOCTest.m:38) //第二次 (UIKitCore`-[UIResponder isFirstResponder])所以可以确认的是,分类方法是可以正常添加到系统类的,但在iOS14的系统中,覆写的方法却被libMainThreadChecker.dylib里的方法接管了,导致没有执行。 那么问题来了,这个libMainThreadChecker.dylib库是干嘛的,它做了什么? 这个库对应了Main Thread Checker这个功能,它是在Xcode9新增的,因为开销比较小,只占用1-2%的CPU,启动时间占用时间不到0.1s,所以被默认置为开的状态。它在调试期的作用是帮助我们定位那些应该在主线程执行,却没有放到主线程的代码执行情况。另外官方文档还有一个解释:The Main Thread Checker tool dynamically replaces system methods that must execute on the main thread with variants that check the current thread. The tool replaces only system APIs with well-known thread requirements, and doesn’t replace all system APIs. Because the replacements occur in system frameworks, Main Thread Checker doesn’t require you to recompile your app.这个家伙会动态的替换尝试重写需要在主线程执行的系统方法,但也不是所有的系统方法。 终于找到了!这很好的解释了为什么本应被覆盖的系统方法却指向了libMainTreadChecker.dylib这个库,同时也解释了为什么有些方法可以覆写,有些却不可以。 测试发现当我们关闭了这个开关,iOS14的设备就可以正常执行覆写的方法了。 到此基本完事了,但还留有一个小疑问,那就是为什么iOS14之前的设备,不受这个开关的影响?目前没有找到实质的证据表明苹果是如何处理的,但可以肯定的是跟 Main Thread Checker 这个功能有关。 总结 稍微抽象下一开始处理问题的方式:遇到问题 -> 猜想 -> 佐证 -> 推翻猜想 -> 重新猜想 -> 再佐证。 这其实是错误的流程,猜想和佐证可以,但他们一般只会成为一个验证的样例,而不能带给我们答案。所以正确的处理方式是,不要把太多时间浪费在猜想和佐证猜想上,而应该去深挖问题本身。新的解题思路可以是这样的:遇到问题 -> 猜想 -> 深挖 -> 根据挖到的点佐证结果。