人物访谈 | 一位 iOS 程序媛

人物访谈 | 一位 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,找我内推。马上过年了,买不买新衣回家过年就靠大家了,哈哈哈。

大家如果不嫌弃认识我的话,可通过飞哥加我微信,也可以通过邮件跟我交流哦。

iOS摸鱼周报 第三十五期

iOS摸鱼周报 第三十五期

本期概要

  • Tips:count vs isEmpty
  • 面试模块:Swift 中 struct 和 class 的区别,值类型和引用类型的区别。
  • 优秀博客:本期继续介绍几个优秀的 Swift 开源库。
  • 学习资料:戴铭的 Swift 小册子。
  • 开发工具:PhotoSweeper,一款快速而强大的重复照片清理器。

开发Tips

整理编辑:zhangferry

count vs isEmpty

通常在判断一个字符串或者数组是否为空的时候有两种方式:count == 0 或者 isEmpty。我们可能会认为两者是一样的,isEmpty 内部实现就是 count == 0。但在 SwiftLint 的检验下,会强制要求我们使用使用 isEmpty 判空。由此可以判断出两者肯定还是存在不同的,今天就来分析下两者的区别。

count 和 isEmpty 这两个属性来源于 Collection,count 表示数量,这个没啥特别的,需要注意的是isEmpty的实现。在标准库中,它的定义是这样的:

1
2
3
extension Collection {
var isEmpty: Bool { startIndex == endIndex }
}

集合的首索引和尾索引相等,则表示为空,这里只有一个比较,复杂度为 O(1)。

大部分集合类型都是走的该默认实现,但也有一些特定的集合类型会重写该方法,比如 Set

1
2
3
extension Set {
var isEmpty: Bool { count == 0 }
}

那为啥会出现两种不同的情况呢,我们再看 Collection 里对 count 的说明。

Complexity: O(1) if the collection conforms to RandomAccessCollection; otherwise, O(n), where n is the length of the collection.

所以对于遵循了RandomAccessCollection 协议的类型,其count获取是 O(1) 复杂度,像是 Array;对于未遵循的类型,比如 String,其 count 复杂度就是 O(n),但是isEmpty 却还是 O(1)。

这里的 Set 还要再特殊一些,因为其没有实现 RandomAccessCollection 却还是用 count 的方式判定是否为空,这是因为 Set 的实现方式不同,其 count 的获取就是 O(1) 复杂度。

当然为了简化记忆,我们可以总是使用 isEmpty 进行判空处理。

因为涉及多个协议和具体类型,这里放一张表示这几个协议和类型之间的关系图。

图片来源

面试解析

整理编辑:师大小海腾

Swift 中 struct 和 class 的区别,值类型和引用类型的区别

struct & class

在 Swift 中,其实 classstruct 之间的核心区别不是很多,有很多区别是值类型与引用类型这个区别隐形带来的天然的区别。

  • class 可以继承,struct 不能继承(当然 struct 可以利用 protocol 来实现类似继承的效果。);受此影响的区别有:

    • struct 中方法的派发方式全都是直接派发,而 class 中根据实际情况有多种派发方式,详情可看 CoderStar|Swift 派发机制
  • class 需要自己定义构造函数,struct 默认生成;

  • class 是引用类型,struct 是值类型;受此影响的区别有:

    • struct 改变其属性受修饰符 let 影响,不可改变,class 不受影响;
    • struct 方法中需要修改自身属性时 (非 init 方法),方法需要前缀修饰符 mutating
    • struct 因为是值类型的原因,所以自动线程安全,而且也不存在循环引用导致内存泄漏的风险;

值类型 & 引用类型

  • 存储方式及位置:大部分值类型存储在栈上,大部分引用类型存储在堆上;
  • 内存:值类型没有引用计数,也不会存在循环引用以及内存泄漏等问题;
  • 线程安全:值类型天然线程安全,而引用类型需要开发者通过加锁等方式来保证;
  • 拷贝方式:值类型拷贝的是内容,而引用类型拷贝的是指针,从一定意义上讲就是所谓的深拷贝及浅拷贝

你可以在 CoderStar|从 SIL 角度看 Swift 中的值类型与引用类型 中查看详细内容。

优秀博客

整理编辑:皮拉夫大王在此我是熊大东坡肘子

1、第三方日期处理库SwiftDate使用详解 – 来自航歌:hangge

@东坡肘子:SwiftDate 是在所有苹果平台,甚至在 Linux 和 Swift 服务器端框架(如 Vapor 或 Kitura )上操作和显示日期和时区的权威工具链。在 CocoaPods 上有超过 300 万的下载量。SwiftDate 功能强大,无论是简单的日期操作,还是复杂的业务逻辑都能满足。本文将对 SwiftDate 的使用方法做详尽说明。

2、搞事情之 Vapor 初探 – 来自掘金:PJHubs

@东坡肘子:Vapor 是一个基于 Swift 语言的开源 Web 框架,可用于创建 RESTful API、网站和使用 WebSockets 的实时应用。在核心框架之外,Vapor 还提供了 ORM 、模板语言,以及用户身份验证和授权模块。本文主要记录了第一次上手 Vapor 所遇到的问题、思考和总结。

3、用 Publish 创建博客 – 来自肘子的Swift记事本:东坡肘子

@东坡肘子:Publish 是一款专门为 Swift 开发者打造的静态网站生成器。它使用 Swift 语言构建整个网站,并支持主题、插件和其他大量的定制选项。本系列文章将通过三个篇幅分别介绍 Publish 的基本用法、主题定制以及插件开发。

4、Using Realm and Charts with Swift 3 in iOS 10 – 来自:Sami Korpela

@我是熊大:一个十分强大并且是纯 Swift 的图表相关的开源框架 – Charts。本文作者利用 Swift 中的轻量级数据库 Realm 和 Charts,构建了一个 Demo。篇幅较长,适合新手,但美中不足的是:Demo 基于 Swift 3。此外我早期写过一篇关于 Realm 的实践代码的文章:如何降低Realm数据库的崩溃,有兴趣可以看一下。

5、今天我们来聊一聊WebSocket(iOS/Golang) – 来自掘金:齐舞647

@我是熊大:Starscream swift 中的 7k+ star 的 socket 开源库,本文作者利用 GO 和 Starscream,模拟了客户端和服务端 websocket 的交互过程,建议对 Socket 感兴趣的阅读。

6、Hero Usage Guide – 来自:Hero官方

@我是熊大:Hero 是我用过的最好的转场动画,没有之一,20k+ star 的成绩也表明了它在转场动画的地位;Hero 应该能满足绝大部分需求,这是它的官方使用手册。

学习资料

整理编辑:Mimosa

戴铭的 Swift 小册子

地址:https://github.com/ming1016/SwiftPamphletApp

戴老师用 Swift 写的、按照声明式 UI、响应式编程范式开发的 Swift 小册子。你可以在这本小册子中查询到 Swift 的语法指南,同时还有 Combine、 Concurrency 和 SwiftUI 这些库的使用指南,你还可以在这追踪到一些知名仓库、开发者的 Github 动态和本仓库的 Issues。由于是开源的,你也可以自己调试学习或者是为项目作出贡献。

工具推荐

整理编辑:CoderStar

PhotoSweeper

地址https://overmacs.com/

软件状态:$9.99,可以试用

软件介绍

PhotoSweeper 是一款快速而强大的重复照片清理器,旨在帮助您在 Mac 上查找和删除重复和相似的照片。

我们可以考虑用在给应用瘦身时扫描相似图片资源场景下。

PhotoSweeper

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第三十四期

iOS摸鱼周报 第三十三期

iOS摸鱼周报 第三十二期

iOS摸鱼周报 第三十一期

任务访谈 | 一位游戏行业的 iOSer

任务访谈 | 一位游戏行业的 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来源2
2021-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 交流,还可以在留言区评论。

iOS摸鱼周报 第三十四期

iOS摸鱼周报 第三十四期

本期概要

  • 话题:Apple 宣布推出自助维修计划。
  • Tips:混编|NS_SWIFT_NAME。
  • 面试模块:二叉树的三序遍历。
  • 优秀博客:几个 Swift 三方库的解析文章。
  • 学习资料:棒棒彬·技术参考周刊。
  • 开发工具:Mounty:一个在 macOS 下以读写模式重新挂载写保护的 NTFS 卷的免费的小工具。

本期话题

@zhangferry:Apple 宣布推出自助维修计划。Apple 将面向个人消费者提供零件、工具与维修手册,从 iPhone 12 与 iPhone 13 开始。搭载 M1 芯片的 Mac 电脑也将很快加入。自助维修计划将从明年初开始在美国率先启动,并在 2022 年或更晚推广到其他国家。

这跟苹果的环保政策相符,可以预料到本来就很能打的 iPhone 更换原装部件之后再多用个 1-2 年也不是啥难事。虽然惠及的用户不会特别多,但是苹果的做法还是值得点赞的。

同时这也带来一个问题,自助维修算是一件有一些技术操作的事情,如何保证操作的正确性?

想起之前我自己给 iPhone 换电池的经历,前两次换 iPhone6SP 电池成功。对自己有了一定的信心,结果,第三次给 iPhone7P 换电池就失手了,不仅因为电池排线插的错位导致一块电路板烧掉,还扯断了连接 Home 键的一根线。有一部分原因是手机屏和后盖的链接方式从之前的前后变成了左右,操作空间变小了,但更主要的还是相应知识的缺乏。这还只是简单的电池,像是摄像头的更换将会更复杂。所以这个自助维修计划比较重要的是应该允许哪些人能够自助维修,如何评估这些人。

题外话:如果自己修坏了,肯定还是算到自己头上的吧🤔。

信息来源:Apple 宣布将推出自助维修计划

开发Tips

混编|NS_SWIFT_NAME

整理编辑:师大小海腾

NS_SWIFT_NAME 宏用于在混编时为 Swift 重命名 Objective-C API,它可用在类、协议、枚举、属性、方法或函数、类型别名等等之中。通过 Apple 举的一些例子,我们可以学习到它的一些应用场景:

  • 重命名与 Swift 风格不符的 API,使其在 Swift 中有合适的名称;

  • 将与类 A 相关联的类/枚举作为内部类/枚举附属于类 A;

  • 重命名 “命名去掉完整前缀后以数字开头的” 枚举的 case,改善所有 case 导入到 Swift 中的命名;

  • 重命名 “命名不满足自动转换为构造器导入到 Swift 中的约定的” 工厂方法,使其作为构造器导入到 Swift 中(不能用于协议中);

  • 在处理全局常量、变量,特别是在处理全局函数时,它的能力更加强大,能够极大程度地改变 API。比如可以将 全局函数 转变为 静态方法,或是 实例⽅法,甚至是 实例属性。如果你在 Objective-C 和 Swift 里都用过 Core Graphics 的话,你会深有体会。Apple 称其把 NS_SWIFT_NAME 用在了数百个全局函数上,将它们转换为方法、属性和构造器,以更加方便地在 Swift 中使用。

你可以在 iOS 混编|为 Swift 重命名 Objective-C API 中查看上述示例。

面试解析

整理编辑:夏天

作为最常见的数据结构之一,在算法中有举足轻重的地位。

理解树有助于我们理解很多其他的数据结构,例如等。也有助于我们理解一些算法类型,例如,回溯算法动态规划。当然在练习关于树的解题过程中,也能够加深我们对深度优先广度优先算法的理解。

今天我们以二叉树的三序遍历为题,来开启我们二叉树的学习。

题目

给定一个二叉树,返回他的 前序 中序 后序 三种遍历

输入: [4,2,6,1,3,5,7]
4
/
2 6
/ \ / \
1 3 5 7

输出

前序遍历:首先访问根结点,然后遍历左子树,最后遍历右子树(根->左->右)

顺序:访问根节点->前序遍历左子树->前序遍历右子树

前序遍历: [4, 2, 1, 3, 6, 5, 7]

中序遍历:首先遍历左子树,然后访问根节点,最后遍历右子树(左->根->右)

顺序:中序遍历左子树->访问根节点->中序遍历右子树

中序遍历: [1, 2, 3, 4, 5, 6, 7]

后序遍历:首先遍历左子树,然后遍历右子树,最后访问根节点(左->右->根)

顺序:后序遍历左子树->后序遍历右子树->访问根节点
后续遍历: [1, 3, 2, 5, 7, 6, 4]

二叉树的遍历方法一般有三种

  • 递归
  • 迭代(常规迭代加颜色标记法
  • 莫里斯遍历(今天暂时不涉及)

递归

在树的深度优先遍历中(包括前序、中序、后序遍历),递归方法最为直观易懂,但考虑到效率,我们通常不推荐使用递归。

递归步骤一般需要遵循以下三种:

  1. 确定递归的参数以及返回值
  2. 确定递归的终止条件,递归算法一定有终止条件,避免死循环。
  3. 确定单次递归的逻辑
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// traversals 为输出的数组
func preorder(_ node: TreeNode?) {
guard let node = node else {
return
}
/// 前序遍历
traversals.append(node.val)
preorder(node.left)
preorder(node.right)
/// 中序遍历
preorder(node.left)
traversals.append(node.val)
preorder(node.right)
/// 后序遍历
preorder(node.left)
preorder(node.right)
traversals.append(node.val)
}

迭代

二叉树的迭代步骤一般是将节点加入到一个 中,然后通过访问栈头/栈尾,根据遍历顺序访问所有的符合的节点。

前序遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
func preorderIteration(_ root: TreeNode?) {
var st:[TreeNode?] = [root]
while !st.isEmpty {
let node = st.removeFirst()
if node != nil {
traversals.append(node!.val)
} else {
continue
}
st.insert(node?.right, at: 0)
st.insert(node?.left, at: 0)
}
}
中序遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func inorderIteration(_ root: TreeNode?) {
var st:[TreeNode?] = []
var cur:TreeNode? = root
while cur != nil || !st.isEmpty {
if cur != nil {
st.insert(cur, at: 0)
cur = cur?.left
} else {
cur = st.removeFirst()
traversals.append(cur!.val)
cur = cur?.right
}
}
}
后序遍历

后序遍历其遍历步骤是 左→右→中,但是这个代码实现起来不简单。 所以我们可以先访问依次访问 中→右→左 的节点,最后将得到结果进行 reversed,其结果最终变成 左→右→中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func postorderIteration(_ root: TreeNode?) {
var st:[TreeNode?] = [root]
while !st.isEmpty {
let node = st.removeFirst()
if node != nil {
print(node!.val)
traversals.append(node!.val)
} else {
continue
}
st.insert(node?.left, at: 0)
st.insert(node?.right, at: 0)
}
traversals = traversals.reversed()
}

颜色标记法

传统的迭代由上述代码可知,比较繁琐,而且迭代过程中易错。参照 颜色标记法-一种通用且简明的树遍历方法 ,利用一个兼具栈迭代方法的高效,又像递归方法一样简洁易懂的方法,更重要的是,这种方法对于前序、中序、后序遍历,能够写出完全一致的代码

其核心方法如下:

  • 标记节点的状态,已访问的节点标记为 1,未访问的节点标记为 0

  • 遇到未访问的节点,将节点标记为 0,然后根据三序排序的要求,按照特定的顺序入栈

    // 前序 中→左→右 按照 右→左→中
    // 中序 左→中→右 按照 右→中→左
    // 后序 左→右→中 按照 中→右→左

  • 结果数组中加入标记为 1 的节点的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    func tuple(_ root: TreeNode?) -> [Int] {
var traversals = [Int]()
var statck = [(0, root)]
while !statck.isEmpty {
let (isVisted, node) = statck.removeLast()
if node == nil {
continue
}
if isVisted == 0 {
// ///前序遍历
// statck.append((0, node?.right))
// statck.append((0, node?.left))
// statck.append((1, node))
// ///中序遍历
// statck.append((0, node?.right))
// statck.append((1, node))
// statck.append((0, node?.left))
// ///后序遍历
statck.append((1, node))
statck.append((0, node?.right))
statck.append((0, node?.left))
} else {
traversals.append(node!.val)
}
}
return traversals
}

利用颜色标记法可以简单的理解迭代的方法,并写出模板代码。

莫里斯遍历

作为兼具性能及低空间复杂度的莫里斯遍历,可以在线下讨论。

优秀博客

整理编辑:我是熊大东坡肘子

1、Alamofire的基本用法 – 来自掘金:Lebron

@我是熊大:Alamofire 是 AFNetWorking 原作者写的,Swift 版本相比 AFN 更加完善,本文介绍了 Alamofire 基本用法,很全面,适合精读;作者还有一篇高级用法,推荐阅读。

2、Kingfisher源码解析 – 来自掘金:李坤

@我是熊大:Kingfisher 对标 OC 中的 SDWebImage,作者是大名鼎鼎的王巍,本文是 Kingfisher 源码解析系列的总结,推荐阅读。

3、iOS SnapKit架构之道 – 来自博客:Rimson

@我是熊大:SnapKit 作为在 Swift 中的页面布局的地位,相当于 OC 中的 Masonry,使用起来几乎一模一样,本文作者详细梳理了 Snapkit 布局的过程和原理。

4、第三方图表库Charts使用详解 – 来自航歌:hangge

@东坡肘子:Charts 是一个功能强大的图表框架,使用 Swift 编写。是对 Android 上大名鼎鼎的图表库 MPAndroidChart 在苹果生态上的移植。hangge 通过大量的范例代码对 Charts 的使用进行了相近地说明。

5、访问 SwiftUI 内部的 UIKit 组件 – 来自 Swift花园:猫克杯

@东坡肘子:抛开 SwiftUI 尚不完备的工具不说,SwiftUI 的确因其构建 UI 的便捷性给开发者带来了兴奋。有一个令人欣慰的事实是,许多 SwiftUI 组件实际上是基于 UIKit 构建的。本文将带你探索一个令人惊讶的 SwiftUI 库,它叫 Introspect 。利用它,开发者能够访问 SwiftUI 组件底层的 UIKit 视图。

学习资料

整理编辑:Mimosa

棒棒彬·技术参考周刊

地址:https://www.yuque.com/binboy/increment-magzine

这是一份由 Binboy👻棒棒彬 在语雀上梳理总结的技术参考周刊。这份周刊是作者学习与生活的沉淀和思考,既有广度,也有深度,还有态度。就像其发刊词的标题:「与技术世界保持链接」,在周刊中你可以看到作者学习技术的过程和经验,也能看到科技与生活的一些新鲜事,这里可能有你正在关注的,亦或者是从来没听说过的技术信息,这些信息既是作者与他自己「第二大脑」的链接,也是作者与读者交流的媒介,同时推动着作者与读者一起前进。这里改编引用一段发刊词中的一段话来抛砖引玉 :

做技术,追求深度无可厚非,只是无需厚此薄彼,我个人而言倾向于「修学储能,先博后渊」的。技术世界的开源、互联网的开放更是给了见多识广一片良好的土壤,我们可以了解了技术、工具现状,将其充分地应用、解决现实世界中的普通问题,并在过程中不断完善。当真正遇到边界时,再结合对已有技术和工具的融会贯通去创造真正的新技术、新工具,也不迟。

朝一个方向看得再远,你也未必能看到新方向

———— 修学储能,先博后渊

工具推荐

整理编辑:CoderStar

Mounty

地址https://mounty.app/

软件状态:免费,开源

软件介绍

Mounty 是一个在 macOS 下以读写模式重新挂载写保护的 NTFS 卷的小工具,功能类似于 NTFS For Mac,最大也是最重要的区别是它是免费的。

mounty

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第三十三期

iOS摸鱼周报 第三十二期

iOS摸鱼周报 第三十一期

iOS摸鱼周报 第三十期

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

人物访谈 | 一位参加过 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摸鱼周报 第三十三期

iOS摸鱼周报 第三十三期

本期概要

  • 话题:感恩节和圣诞节期间 AppStore 将正常接受审核。
  • Tips:使用 os_signpost 标记函数执行和测量函数耗时;混编|将 Objective-C typedef NSString 作为 String 桥接到 Swift 中。
  • 面试模块:LeetCode - #1 Two Sum。
  • 优秀博客:本期为大家整理了一些关于图像识别框架 Vision 的文章。
  • 学习资料:Vue Color Avatar,一个纯前端实现的头像生成网站;一篇全面介绍 WebKit 和 Gecko 内部操作的入门文章。
  • 开发工具:swiftenv。

本期话题

@zhangferry:苹果发布了年末两个重要假期关于 AppStore 审核的声明。往年圣诞节期间一般都是停止审核的,今年则会正常接受提交。但是在 11 月 24 号到 28 号(感恩节),和 12 月 23 号到 27 号(圣诞节)之间的提交审核流程会比较慢。如果可以错开排期的话尽量不要在这个时间段提审。

信息来源:Submissions now accepted through the holidays

开发Tips

使用 os_signpost 标记函数执行和测量函数耗时

整理编辑:zhangferry

os_signpost 是 iOS12 开始支持的一个用于辅助开发调试的轻量工具,它跟 Instruments 的结合使用可以发挥很大作用。os_signpost API 较简单,其主要有两大功能:做标记、测量函数耗时。

首先我们需要引入 os_signpost 并做一些初始化工作:

1
2
3
4
5
6
7
8
import os.signpost

// test function
func bindModel {
let log = OSLog(subsystem: "com.ferry.app", category: "SignLogTest")
let signpostID = OSSignpostID(log: log)
// ...
}

其中 subsystem 用于标记应用,category 用于标记调试分类。

后面试下它标记和测量函数的功能。

做标记

1
2
let functionName: String = #function
os_signpost(.event, log: log, name: "Complex Event", "%{public}s", functionName)

注意这个 API 中的 name 和后面的 format 都是 StaticString 类型(format 是可选参数)。StaticString 与 String 的区别是前者的值是由编译时确认的,其初始化之后无法修改,即使是使用 var 创建。系统的日志库 OSLog 也是选择 StaticString 作为参数类型,这么做的目的一部分在于编译器可采取一定的优化,另一部分则是出于对隐私的考量。

The unified logging system considers dynamic strings and complex dynamic objects to be private, and does not collect them automatically. To ensure the privacy of users, it is recommended that log messages consist strictly of static strings and numbers. In situations where it is necessary to capture a dynamic string, you may explicitly declare the string public using the keyword public. For example, %{public}s.

对于调试期间我们需要使用 String 附加参数的话,可以用 %{public}s 的形式格式化参数,以达到捕获动态字符串的目的。

测量函数耗时

1
2
3
os_signpost(.begin, log: log, name: "Complex calculations", signpostID: signpostID)
/// Complex Event
os_signpost(.end, log: log, name: "Complex calculations", signpostID: signpostID)

将需要测量的函数包裹在 begin 和 end 两个 os_signpost 函数之间即可。

使用

打开 Instruments,选择创建 Blank 模板,点击右上角,添加 “+” 号,双击选择添加 os_signpost 和 Time Profiler 两个模板。运行应用直到触发标记函数时停止,我们展开 os_signpost,找到我们创建的 SignLogTest,将其加到下方。调整 Time Profiler 的 Call Tree 之后就可以看到下图样式。

event 事件被一个减号所标记,鼠标悬停可以看到标记的函数名,begin 和 end 表示那个耗时函数执行的开始和结束用一个区间块表示。

其中 event 事件可以跟项目中的打点结合起来,例如应用内比较重要的几个事件之间发生了什么,他们之间的耗时是多少。

混编|将 Objective-C typedef NSString 作为 String 桥接到 Swift 中

整理编辑:师大小海腾

在 Objective-C 与 Swift 混编的过程中,我遇到了如下问题:

我在 Objective-C Interface 中使用 typedef 为 NSString * 取了一个有意义的类型别名 TimerID,但 Generated Swift Interface 却不尽如人意。在方法参数中 TimerID 类型被转为了 String,而 TimerID 却还是 NSString 的类型别名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Objective-C Interface
typedef NSString * TimerID;

@interface Timer : NSObject
+ (void)cancelTimer:(TimerID)timerID NS_SWIFT_NAME(cancel(timerID:));

@end

// Generated Swift Interface
public typealias TimerID = NSString

open class Timer : NSObject {
open class func cancel(timerID: String)
}

这在 Swift 中使用的时候就遇到了类型冲突问题。由于 TimerID 是 NSString 的类型别名,而 NSString 又不能隐式转换为 String。

1
2
3
// Use it in Swift
let timerID: TimerID = ""
Timer.cancel(timerID: timerID) // Error: 'TimerID' (aka 'NSString') is not implicitly convertible to 'String'; did you mean to use 'as' to explicitly convert? Insert ' as String'

可以通过以下方式解决该问题:

  1. 在 Swift 中放弃使用 TimerID 类型,全部用 String 类型
  2. 在 Swift 中使用到 TimerID 的地方显示转化为 String 类型
1
Timer.cancel(timerID: timerID as String)

但这些处理方式并不好。如果从根源上解决该问题,也就是在 Generate Swift Interface 阶段将 typedef NSString *TimerID 转换为 typealias TimerID = String,那就很棒。宏 NS_SWIFT_BRIDGED_TYPEDEF 就派上用场了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Objective-C Interface
typedef NSString * TimerID NS_SWIFT_BRIDGED_TYPEDEF;

@interface Timer : NSObject
+ (void)cancelTimer:(TimerID)timerID NS_SWIFT_NAME(cancel(timerID:));

@end

// Generated Swift Interface
public typealias TimerID = String // change: NSString -> String

open class Timer : NSObject {
open class func cancel(timerID: TimerID) // change: String -> TimerID
}

现在,我可以在 Swift 中愉快地使用 TimerID 类型啦!

1
2
let timerID: TimerID = ""
Timer.cancel(timerID: timerID)

除了 NSString,NS_SWIFT_BRIDGED_TYPEDEF 还可以用在 NSDate、NSArray 等其它 Objective-C 类型别名中。

面试解析

整理编辑:夏天

现代开发⼯程师在⾯试过程中,算法⾯试往往有⼀定程度的重要性。

算法⾯试作为基本功之⼀,它包含了太多的逻辑思维,可以考察你思考问题的逻辑和解决问题的能⼒。完全类似的业务选手只能靠挖掘,但当⼀个⼈逻辑思维和能⼒不错的情况下,其业务匹配及后期上⼿概率也会很⾼。

⾯试算法题⽬在难度上(尤其是代码难度上)会略低⼀些,倾向于考察⼀些基础数据结构与算法,通过交流暴露更多的⾯试题细节。

这也就是为什么现代算法⾯试中推崇⼀题多解,在实际算法⾯试中出现原题的概率往往不⾼,随着与面试官交流且探讨让已知的面试题出现变化。

下⾯我们以 LeetCode 开篇 TwoSum 来简要说明。

默认读者有关于时间复杂度和空间复杂度的概念。

TwoSum

给定⼀个整数数组 nums 和⼀个整数⽬标值 target ,请你在该数组中找出为⽬标值 target 的那两个整数,并返回它们的数组下标。

你可以假设每种输⼊只会对应⼀个答案。但是,数组中同⼀个元素在答案⾥不能重复出现。

你可以按任意顺序返回答案。

示例 1:

1
2
3
输⼊:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

1
2
输⼊:nums = [3,2,4], target = 6 
输出:[1,2]

示例 3:

1
2
输⼊:nums = [3,3],target = 6 
输出: [0,1]

提示:

  • 2 <= nums.length <= 104
  • -109 <= nums[i] <= 109
  • -109 <= target <= 109
  • 只会存在⼀个有效答案

解析

作为⼏乎⼈⼈ LeetCode ,⼈⼈ Code 过的经典题⽬,本题的最优解就是时间复杂度及空间复杂度皆为 O(n) 的解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
var dict: [Int: Int] = [:]

for (i, n) in nums.enumerated() {
if let index = dict[target - n] {
return [i, index]
}
dict[n] = i
}

return []
}

}

但是这种面试原题,往往不是我们能正好遇到的。

删减版两数之和

给定⼀个整数数组 nums 和⼀个整数⽬标值 target ,请你在该数组中找出和为⽬标值 target 的那两个整数,并返回它们的数组下标。

示例 1:

1
2
3
输⼊:nums = [2,7,11,15], target = 9 
输出:[0,1] 解释:
因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

这就很像我们会遇到的初始问题,现在我们一起跟面试官 来确认一下面试题吧!

确认异常状态即确认出参与⼊参的限制

我们需要确保两数之和不会超过值的最⼤限制 Int.Max 以及会不会超过 Int.min

需要跟⾯试官确认参数的限制,以及超出限制以后返回的结果

确认是否有多个答案及结果顺序,及同⼀位置能否⽤多次

确认是否存在多个答案,确认数组中是否存在相同数据,以及确认是否需要展示所有正确的值即答案是否唯一且两数下标是否是顺序的

例如:

1
输⼊:nums = [2,2,7,11,15], target = 9

这个答案可能是 [0, 2][1, 2] 这也会导致最终代码的编写

确认是否整数数组是否有序

对于已经排序的数组,我们可以利⽤双指针的思想来优化我们的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
func twoSum(_ nums: [Int], _ target: Int) -> [Int] {
guard nums.count > 0 else {
return []
}

var start = 0
var end = nums.count-1
while start < end {
let sum = nums[start] + nums[end]
if sum == target {
return [start, end]
} else if sum < target {
start += 1
} else {
end -= 1
}
}

return []
}
}

当然可能还有其他变种,如果你有什么想法也可以来丰富所有的示例。

总结

算法⾯试题是⼀个与⾯试官交流的好途径,而在⾯试过程中⼀步步与⾯试官交流,可以展现⾯试者逻辑思维能⼒以及沟通交流能⼒。

在实际遇到面试题的时候,我们不着急写出具体的代码,展现你的思维过程,利用交流丰富你的表现,思维能力和沟通交流能力。

优秀博客

本期主题:Vision

Vision 是苹果在 WWDC 2017 推出的图像识别框架。与 Core Image、AV Capture 相比,Vision 在耗电量、耗时、精确度上表现优异。

整理编辑:皮拉夫大王在此我是熊大东坡肘子

1、使用 Vision 框架对图像进行分类 – 来自:Apple

@我是熊大:本文演示了如何使用 Vision 和 Core ML 对图像进行识别并分类,附 Apple 官方 Demo。

2、识别视频流中的对象 – 来自:Apple

@我是熊大:直接识别来自相机中的视频流,实时识别物体,本文附 Apple 官方 Demo。

3、Swift之Vision 图像识别框架 – 来自掘金:RunTitan

@皮拉夫大王:Vision 有很多应用场景,比如人脸检测、图像对比、二维码条形码检测、文字检测、目标跟踪等。每种使用场景文章都列举了代码样例。

5、用苹果官方 API 实现 iOS 备忘录的扫描文稿功能 – 来自:东坡肘子

@东坡肘子:本文将介绍如何通过 VisionKit、Vision、NaturalLanguage、CoreSpotlight 等系统框架实现与备忘录扫描文稿类似的功能。

6、理解 Vision 框架中的图片技术 – 来自掘金:RickeyBoy

@东坡肘子:本文主要介绍了 Vision 框架在图像技术方面的一些酷炫功能,并一定程度上阐述了其原理。

学习资料

整理编辑:Mimosa

Vue Color Avatar

地址:https://github.com/Codennnn/vue-color-avatar

一个纯前端实现的头像生成网站,使用 Vite + Vue3 开发,是一款矢量风格头像的生成器,你可以搭配不同的素材组件,或者通过配置代码,来生成自己的个性化头像。

浏览器的工作原理:新式网络浏览器幕后揭秘

地址:https://www.html5rocks.com/zh/tutorials/internals/howbrowserswork/

这是一篇全面介绍 WebKit 和 Gecko 内部操作的入门文章,是以色列开发人员塔利·加希尔大量研究的成果。在过去的几年中,她查阅了所有公开发布的关于浏览器内部机制的数据,并花了很多时间来研读网络浏览器的源代码。学习浏览器的内部工作原理将有助于你作出更明智的决策,并理解那些最佳开发实践的个中缘由。

工具推荐

整理编辑:CoderStar

swiftenv

地址https://github.com/kylef/swiftenv

软件状态:免费,开源

软件介绍

swiftenv 允许您:

  • 更改每个用户的全局 Swift 版本。
  • 设置每个项目的 Swift 版本。
  • 允许您使用环境变量覆盖 Swift 版本。

swiftenv

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第三十二期

iOS摸鱼周报 第三十一期

iOS摸鱼周报 第三十期

iOS摸鱼周报 第二十九期

iOS 摸鱼周报 第三十二期

iOS 摸鱼周报 第三十二期

本期概要

  • 话题:Mac 版 TestFlight 已经上线。
  • Tips:介绍了关于中间层的一些概念,前后端之间的现状以及需要解决的问题等。
  • 面试模块:能否向编译后的类增加实例变量?能否向运行时动态创建的类增加实例变量?为什么?
  • 博客主题:Swift 关键字。
  • 学习资料:来自字节跳动的 OKR 学习网站;一个关于 SwiftUI 的 Cheat Sheet —— Fucking SwiftUI。
  • 开发工具:一个允许你在 App Store 上搜索 iOS 应用程序并下载应用程序包的命令行工具 —— ipatool。

本期话题

使用 TestFlight 测试 Mac App

现在,你可以邀请人们试用你的 Mac app 的 Beta 版,在发布前为您提供宝贵的反馈。通过 TestFlight ,你可以对多达 10000 名测试人员发出邀请。TestFlight 在 Mac 端的使用与移动端无异。

安装地址:https://apps.apple.com/cn/app/testflight/id899247664。

版本要求:最低 macOS 12.0 (Monterey)

开发 Tips

整理编辑:夏天

关于中间层的一些概念

本文基于经验、个人理解及参考资料整理而出。本文以前端作为接口调用方,服务端称为接口提供者,本文指出的中间层概念是作者理解的,如果有误欢迎指正。

在传统的开发分层中,前端工程师负责前端界面层的研发,数据接口由服务端工程师提供。随着微服务架构的发展,服务端的功能更加细致,而前端与用户贴近,其数据展示是多变繁杂的。

中间层,用户接口或 Web 客户端与底层或数据库之间的逻辑层,早期也被称为粘合层,其作用是粘合前端及后端之间的形象比喻。

前后端交流的困境

由于前端直接与服务端进行对接,会导致以下问题:

前端和服务端考虑的问题不一致

前端在开发过程中需要考虑用户体验等信息,而服务端因为服务下沉及解耦等原因一般其提供独立的方法实现独立的功能。

前端入口多

前端目前有客户端(iOS, Android),PC 端及 H5 端,对待不同的前端类型可能需要不同的接口,如果用同一接口的话,需要服务端进行适配,增大了服务端的工作量。

前端需求变化大

作为面向用户侧的前端开发来说,其需求变化是频繁的,对数据要求也是不同的。服务端接口的返回值一般不是前端想要的,并且会返回远超前端想要的内容。服务端会提供不同样式的返回值,导致前端需要兼容,入口越多,兼容成本越大

安全问题

越多的底层接口的暴露意味着需要保护的内容也越多,越多的底层服务被暴露出来,其底层实现逻辑也越容易被分析然后加以攻击。

客户端更新问题

大部分情况下并不会要求客户去强制更新客户端,如果出现旧接口迁移或改造,且保留原接口的话,这种迁移和改造的必要性将会大打折扣。

这些问题导致前端和服务端之间关于 API 接口颗粒度的争吵,越来越常见。

  • 服务端提供的接口是面向前端业务还是通用服务

  • 前端希望接口更能贴近业务及其他性能问题

中间层能够解决的问题

BFF 理念中最重要的一点是 服务自治,谁用谁处理。通过中间层我们可以处理一些问题。

前后端分离

比起业务,服务端应该更多考虑接口的性能,服务能力。由中间层来实现前后端的交流,前端需求和后端需求分开了,便于后期维护

易于维护和修改的API
  1. 不同的服务端可能提供不同的接口,对于前端来说对接不同接口会导致成本的增加,并且不利于前端对接口进行统一的处理。

  2. 使用中间层能够统一处理接口返回的数据, 前端获取的数据可以经过中间层来进行处理,可以大大减少各端开发者的调用时间。

  3. 便于 Mock 数据, 由中间层定义前端的数据格式,可以在服务端 Mock 数据,后续对接接口时,只需要对中间层进行修改,便于调试。

更安全

接口之间可能存在敏感信息需要传递到下一个接口,使用中间层完成多接口串联可以减少这种敏感数据的暴露。

服务端可以限制只有中间层才能对后台进行访问,降低攻击的可能性。

中间层的限制及滥用

在明确是否使用中间层之前,需要明确这几点:

  1. 中间层不能防止错误的出现。中间层是一个中转服务,服务端还需要与中间层进行沟通,这个过程被简化了,但是并没有消除。中间层更多的是统一和聚合,而不是真正底层逻辑的实现,这些实现还需要服务端实现。
  2. 避免重复逻辑的中间层分割。我们可以有多个中间层,但是这种分割应该基于具体的用户体验而不是特定的设备。例如基于用户的需求来分割中间层是可以接受的,例如订单、产品分类等,但是根据 iOS, Android,PC, H5 来区分就是不明智的。
  3. 不要过分依赖中间层。中间层不是万能的,它可以优化用户体验,提升开发效率,但是它仅仅是一个中转层,过多的中间层逻辑处理仅仅是原来服务端的套壳,对于接口等还需要合理的设计。

问题

当然还有其他问题,例如中间层谁来开发、谁来部署、谁来运维这些问题,留待后续讨论。有两个阿里的关于中间层的引申阅读,可以来看看。

参考资料

Pattern: Backends For Frontends

The BFF Pattern (Backend for Frontend): An Introduction

Developer Experience First —— TWA 的理念与实践(附演讲视频)

Serverless For Frontend 前世今生

面试解析

整理编辑:师大小海腾

Q:能否向编译后的类增加实例变量?能否向运行时动态创建的类增加实例变量?为什么?

A:

  • 不能向编译后的类增加实例变量。类的内存布局在编译时就已经确定,类的实例变量列表存储在 class_ro_t Struct 里,编译时就确定了内存大小无法修改,所以不能向编译后的类增加实例变量。
  • 能向运行时动态创建的类增加实例变量。运行时动态创建的类只是通过 alloc 分配了类的内存空间,没有对类进行内存布局,内存布局是在类初始化过程中完成的,所以能向运行时动态创建的类增加实例变量。
1
2
3
4
Class newClass = objc_allocateClassPair([NSObject class], "Person", 0);
class_addIvar(newClass, "_age", 4, 1, @encode(int));
class_addIvar(newClass, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
objc_registerClassPair(newClass); // 要在类注册之前添加实例变量

优秀博客

整理编辑:皮拉夫大王在此我是熊大东坡肘子

本周博客主题:Swift 关键字

在还没有学习 Swift 时,听过好几次部门内关于 Swift 的分享。依稀记得在分享会上听到了各种各样的新颖的概念,见到一些在 OC 中没有见过的关键字。希望本次主题能为 OC 的同学扫清一些学习 Swift 的障碍。

1、Swift-29个关键字,助力开发(万字长文) – 来自掘金:SunshineBrother

@皮拉夫大王:推荐新手阅读,对 Swift 比较熟悉的同学可以简单浏览校验下是否有不清楚的概念。文中有对应的示例代码,帮助大家理解。

2、Swift 的闭包为什么选用 in 关键字? – 来自知乎

@皮拉夫大王:Swift 闭包为什么用 in 可能很多同学都没有思考过。这个问题比较有意思,信息量也不是很密集,也比较轻松。该话题有人列出了苹果工程师对此的解释。

3、细说 Swift 4.2 新特性:Dynamic Member Lookup – 来自掘金:没故事的卓同学

@东坡肘子@dynamicMemberLookup 中文可以叫动态查找成员。在使用 @dynamicMemberLookup 标记了对象后(对象、结构体、枚举、protocol),实现了 subscript(dynamicMember member: String) 方法后我们就可以访问到对象不存在的属性。如果访问到的属性不存在,就会调用到实现的 subscript(dynamicMember member: String) 方法,key 作为 member 传入这个方法。

4、解析 Swift 中的 @discardableResult – 来自:SwiftMic

@东坡肘子@discardableResult 属性可能很少被人熟知,但是对于想消除方法返回值未被使用的警告来说的话,该属性还是很有用的,只需要在对应方法前添加 @discardableResult 属性即可。但是,还是要考虑是否真的需要忽略该类警告,因为有些情况下及时处理返回结果可能是一种更好的解决方案。

5、“懒”点儿好 – 来自:SwiftGG

@我是熊大:这是一个优化的小技巧–使用 lazy 关键字,可以用于属性、闭包初始化等场景;不仅如此,就连 let 修饰的常量,默认也是 lazy 的,还有其他相关的 lazy 小技巧,推荐阅读。

6、访问控制 – 来自:SwiftGG

@我是熊大:在 Swift 中,类、结构体、协议、属性、方法默认访问级别都是 internal,此外还有更多的访问级别需要我们了解,尤其是在做组件、模块时;学好关键字助你设计更好的代码。

学习资料

整理编辑:Mimosa

OKR.com

地址:https://www.okr.com/

来自字节跳动的 OKR 学习网站。你可能或多或少听说过 OKR 这个概念,但没有深入了解过。OKR 是一套协助组织进行目标管理的工具和方法,它能帮助团队明确目标、聚焦重点。该网站会从 OKR 是什么开始,带你了解 OKR 理论的优缺点,价值在哪,或者适不适合你的个人或你的公司规划,你还能在网站上面找到许多关于 OKR 的资源。

Fucking SwiftUI

地址:https://fuckingswiftui.com/

Fucking SwiftUI 是一个关于 SwiftUI 的 Cheat Sheet。这上面有许多许多(关于技术的和无关技术的都有)关于 SwiftUI 的问答,类似 “我该学 SwiftUI 么”、”SwiftUI 中 List 如何使用?”、”SwiftUI 会取代 UIKit 么?” 等等问题,也有几乎所有 SwiftUI 控件的使用方式,希望能帮到大家。(如果你的浏览器不能打脏话可以使用这个链接:https://goshdarnswiftui.com/)

工具推荐

整理编辑:CoderStar

ipatool

地址https://github.com/majd/ipatool

软件状态:免费,开源

软件介绍

ipatool 是一个允许你在 App Store 上搜索 iOS 应用程序并下载应用程序包的命令行工具。当然,这过程中需要你的账户以及密码,并且也只能下载账户过去已经下载过的应用程序。相对于使用 Apple Configurator 2 操作更加便捷一些。

ipatool

注意 gif 中的 ipa 命令实际使用中可能为 ipatool

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS 成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第三十一期

iOS摸鱼周报 第三十期

iOS摸鱼周报 第二十九期

iOS摸鱼周报 第二十八期

iOS摸鱼周报 第三十一期

iOS摸鱼周报 第三十一期

本期概要

  • 本期话题:关于 In-App Events 的一些介绍。
  • Tips:优化 Xcode 增量编译的几个小技巧。
  • 面试模块:一道 RunLoop 相关题目。
  • 优秀博客:本期博客主题是 Swift 的高级中间语言:SIL。
  • 学习资料:raywenderlich 新出的 Flutter 教程;一份认知者偏差手册。
  • 开发工具:一个终端命令补全工具:fig。

本期话题

本期访谈内容准备有些晚了,暂停一期,下次一定😅。

苹果官网经常会更新一些活动或者开发相关的一些资讯,我们打算定期做一些整理和筛选,以帮助大家了解相关信息。

In-App Events(App 内活动)

整理编辑:iHTCboy

App 内活动是指 App 和游戏内的时效性活动,例如游戏竞赛、电影首映和直播体验等。用户能够直接在 iOS 和 iPadOS 上的 App Store 中探索您的 App 内活动。无论您是想吸引新用户,还是向当前用户提供最新信息,或是与以前的用户重建联系,这项功能都能够助您以全新的方式展示您的活动并扩大其触及的用户群。

苹果在 10 月 22 日开放后台,可以上传活动素材提交审核。需要注意的是,此功能目前是测试阶段,需要开发者接受此协议才生效。

2021 年 10 月 27 日起,在 iOS 15 以上设备的 App Store 上,用户可以看到 App 内活动。

详细,参见:https://developer.apple.com/cn/app-store/in-app-events/

开发Tips

Xcode 增量编译优化

整理编辑:zhangferry

相对于全量编译,增量编译才是平常开发使用最多的场景,所以这方面提升所带来的好处往往更可观。参考苹果文档 Improving the Speed of Incremental Builds ,我们可以从多个方面入手优化增量编译。

在开始优化之前更重要的是对编译时间的测量,有衡量指标才能准确分析出我们的优化效果。时间测量可以通过 Xcode 的 Product > Perform Action > Build With Timing Summary,然后在编译日志的底部查看各阶段耗时统计。

以下为优化建议:

声明脚本构建阶段脚本和构建规则的 Inputs 和 Outputs

New Build System 每次编译准备执行 Build Phase 中的脚本时,会根据 inputs 和 outputs 的状态来确定是否执行该脚本。以下情况会执行脚本:

  • 没有 input 文件
  • 没有 output 文件
  • input 文件发生变化
  • output 丢失

最近遇到一个问题刚好跟这有关,该问题导致增量编译时间很长,耗时主要集中在 CompileAsseetCatalog 阶段。

正常 CocoaPods 在处理资源 Copy 的时候是带有 input 和 output 的,用于减少资源的导入和编译行为,如下图:

我们项目中有很多私有库,里面引用图片使用了 Assets.xcassets 的形式(未封装 Bundle,静态库),这导致一个编译错误:

1
Targets which have multiple asset catalogs that aren't in the same build phase may produce an error regarding a "duplicate output file"

这个错误正是 New Build System 带来的,Build System Release Notes for Xcode 10 里有说明:

Targets which have multiple asset catalogs that aren’t in the same build phase may produce an error regarding a “duplicate output file”. (39810274)

Workaround: Ensure that all asset catalogs are processed by the same build phase in the target.

上面给出了临时的解决方案,就是将所有 asset catalogs 在同一个构建过程处理。对应到 CocoaPods 就是在 Podfile 里添加下面这句:

1
install! 'cocoapods', :disable_input_output_paths => true

该设置会关闭资源 Copy 里的 input 和 output,如上面所说,没有 input 和 output,每次都会执行资源的 Copy。因为 Pod 里的Assets.scassets 最终会和主项目的 Assets.scassets 合到一起编译成 car 文件,所以每次主项目都要等 Pods 的 Copy 完再编译,即使资源文件没有任何变更,这就导致了增量时长的增加。

CocoaPods 仓库里有一个 Issue 在讨论这个问题:Issue #8122 。但该回答下的方案均不适用,后来将私有库中资源引用的方式改为 Bundle,去掉 disable_input_output_paths 的设置,增量编译效果得到大幅提升:

其中主要占用编译耗时的 CompileAssetCatalog 阶段直接没有了。

将自己的模块应用 Module Maps

Module Maps 主要缩短的是头文件的引用问题,未 Module 化的时候,编译器会为每一个源文件预处理 .h 头文件,Module 之后,不会再预处理,而是为对应的库单独建一个缓存,之后编译重用缓存内容。在制作仓库时只要需要确保 DEFINES_MODULE 为 Yes 就可以了,剩余的工作全都可以 Xcode 代劳。

需要注意,要发挥 Module Maps 的功能,还需要确保在头文件引用时增加库的名字,这样编译器才会知道你有 Module Map。

推荐: #import <FrameworkName/Header.h>

不推荐:#import <Header.h> 或者 #import "Header.h"

明确的项目依赖

对于非必要的依赖进行移除,因为过时或多余的依赖关系可能会迫使 Xcode 在并行构建时变成顺序构建。

通常当项目引入新的 Framework 时,Xcode 会自动添加对应依赖,这种是隐式的。比较推荐显示的依赖:在 Build Phases -> Dependencies -> 点加号。

重构 Target 以提高并发能力

分析原有的构建流程,将一些额外的依赖去掉。这个改造成本稍微有点高,但某些情况下应该也能带来较大的提升。

举个例子:当一个 target 依赖多个子 targets 时,Xcode 必须等待所有子 targets 完成才能继续编译当前 target。我们可以考虑分拆依赖关系,最大化利用 Xcode 的并发能力。

面试解析

整理编辑:师大小海腾

Q:执行以下代码,打印结果是什么?

1
2
3
4
5
6
7
8
9
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});

- (void)test {
NSLog(@"2");
}

打印结果为 1、3。原因是:

  1. performSelector:withObject:afterDelay: 的本质是拿到当前线程的 RunLoop 往它里面添加 timer
  2. RunLoop 和线程是一一对应关系,子线程默认没有开启 RunLoop
  3. 当前 performSelector:withObject:afterDelay: 在子线程执行

所以 2 不会打印。

优秀博客

SIL:Swift Intermediate Language,SIL 是高级别的中间语言,SIL 由 SILGen 生成并由 IRGen 转为 LLVM IR ,SIL 会对 Swift 进行较高级别的语义分析和优化。我们看到的 @ 开头修饰的代码基本都属于 SIL 范畴。

整理编辑:皮拉夫大王在此我是熊大东坡肘子

1、Swift的高级中间语言:SIL – 来自简书:sea_biscute

@东坡肘子:在 LLVM 的官方文档中对 Swift 的编译器设计描述如下: Swift 编程语言是在 LLVM 上构建,并且使用 LLVM IR 和 LLVM 的后端去生成代码。但是 Swift 编译器还包含新的高级别的中间语言,称为 SIL。SIL 会对 Swift 进行较高级别的语义分析和优化。 本文将分析一下 SIL 设计的动机和 SIL 的应用,包括高级别的语义分析,诊断转换,去虚拟化,特化,引用计数优化,TBAA(Type Based Alias Analysis)等。并且会在某些流程中加入对 SIL 和 LLVM IR 对比。

2、一文看破Swift枚举本质 – 来自:狐友技术团队

@东坡肘子:SIL 在实际工作中的应用举例。通过分析内存布局、查看 SIL 源码等方式来探索一下枚举的底层到底是什么样子的。在 Swift 中枚举不仅仅只是一个用来区分类型的常量了,枚举的功能被大大的加强。枚举可以设置原始值,添加关联值,甚至可以添加计算属性(不能添加存储属性),定义方法,实现协议,其功能仅次于一个 Class 对象了,那么 Swift 的枚举到底是怎样实现这些功能的呢?

3、Swift Intermediate Language 初探 – 来自简书:sea_biscute

@皮拉夫大王:文章简单介绍了 SIL 以及 SIL 在 LLVM 架构中的位置。正文部分作者通过 SIL 分析来解释 extension 中 protocol 函数和对象中的 protocol 函数调用选择的问题。

4、Swift编译器中间码SIL – 来自博客:roy’s blog

@皮拉夫大王:作者首先介绍了 SIL 的设计初衷以及与 LLVM IR 的区别。文中还介绍了 SSA( static single-assignment)中“代”的概念以及 SSA 的益处。SIL 是命名函数的集合,SIL 源文件为 Module,通过 Module 可以遍历 Module 中的函数。

5、Swift Intermediate Language —— A high level IR to complement LLVM – 来自:Joe Groff 和 Chris Lattner

@我是熊大:在 LLVM 开发人员会议上 Groff 和 Chris Lattner 通过简报的方式对 SIL 进行了详细的介绍。内容包括:为什么要使用 SIL、SIL 的设计逻辑、Swift 对 SIL 的使用等内容。尽管简报为英文,但主要以代码和图表为主,对了解 SIL 的设计动机和设计原理有很大的帮助。

学习资料

整理编辑:Mimosa

Flutter Apprentice from raywenderlich

地址:https://www.raywenderlich.com/books/flutter-apprentice

raywenderlich 新出的 Flutter 教程,该网站的教程一直以简单易懂、清晰明了为特点,如果是想入门 Flutter 的话,这本书将会是不错的选择。另外该网站的 Swift Apprentice 也出了新的版本,有兴趣重温一下的小伙伴也可以阅读一下。

认知偏差知识手册

地址:https://s75w5y7vut.feishu.cn/docs/doccn3BatnScBJe7wD7K3S5poFf

一份心理学知识词条的普及手册,无关乎代码,但是对互联网工作者来说,阅读之后也许可以让你更加了解产品与用户之间的关系,能够提升产品的细节,用户的体验等等。

工具推荐

整理编辑:CoderStar

fig

地址https://fig.io/

软件状态:免费,开源

软件介绍

fig 是一个开源的终端自动补全工具,支持数百个 CLI 工具,如 gitdockernpm等等,并且可以无缝添加到你现有的终端,如 iTermHyperVSCodemacOS 终端,支持我们自己自定义一些补全规则。

fig

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第三十期

iOS摸鱼周报 第二十九期

iOS摸鱼周报 第二十八期

iOS摸鱼周报 第二十七期

人物访谈 | 一位研究生的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:最近有什么新的感想或心得跟我们分享吗?

程序员这个行业其实是与社会关系脱节的,它不是那种社会性质的工作,会经常和人打交道。技术肯定要有的,毕竟是门手艺活,得靠它吃饭,但技术只是一个最最基本的层面,政治书上说了,人的本质是一切社会关系的总和,所以大家还是要多多搞好社会关系,这样人生路才会更宽广,走的更顺畅。(仅为个人观点)

iOS摸鱼周报 第三十期

iOS摸鱼周报 第三十期

本期概要

  • Tips:分享 WKWebView 几个不常用的特性。
  • 面试模块:一道 Tagged Pointer 相关题目。
  • 优秀博客:本期博客整理了 Codable 在一些特殊场景的处理方式,Swift 处理 JSON 解析时的一些技术细节。
  • 学习资料:Xcode Build Settings 的参数说明网站;来自 Microsoft 的 Data Science 基础课程。
  • 开发工具:免费且开源的 Coding 时间追踪工具:wakapi。

开发Tips

WKWebView 几个不常用的特性

整理编辑:FBY展菲

1. 截获 Web URL

通过实现 WKNavigationDelegate 协议的 definePolicyFor 函数,我们可以在导航期间截获 URL。以下代码段显示了如何完成此操作:

1
2
3
4
5
6
7
8
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

let urlString = navigationAction.request.url?.absoluteString ?? ""
let pattern = "interceptSomeUrlPattern"
if urlString.contains(pattern){
var splitPath = urlString.components(separatedBy: pattern)
}
}

2. 使用 WKWebView 进行身份验证

当 WKWebView 中的 URL 需要用户授权时,我们需要实现以下方法:

1
2
3
4
5
6
7
8
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {

let authenticationMethod = challenge.protectionSpace.authenticationMethod
if authenticationMethod == NSURLAuthenticationMethodDefault || authenticationMethod == NSURLAuthenticationMethodHTTPBasic || authenticationMethod == NSURLAuthenticationMethodHTTPDigest {
//Do you stuff
}
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential, credential)
}

收到身份验证质询后,我们可以确定所需的身份验证类型(用户凭据或证书),并相应地使用提示或预定义凭据来处理条件。

3. 多个 WKWebView 共享 Cookie

WKWebView 的每个实例都有其自己的 cookie 存储。为了在 WKWebView 的多个实例之间共享 cookie,我们需要使用 WKHTTPCookieStore,如下所示:

1
2
3
4
let cookies = HTTPCookieStorage.shared.cookies ?? []
for (cookie) in cookies {
webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
}

4. 获取加载进度

WKWebView 的其他功能非常普遍,例如显示正在加载的 URL 的进度更新。

可以通过监听以下方法的 estimatedProgress 的 keyPath 值来更新 ProgressViews:

1
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)

5. 配置 URL 操作

使用 decisionPolicyFor 函数,我们不仅可以通过电话,Facetime 和邮件等操作来控制外部导航,还可以选择限制某些 URL 的打开。以下代码展示了每种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}

if ["tel", "sms", "mailto"].contains(url.scheme) && UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
decisionHandler(.cancel)
} else {
if let host = navigationAction.request.url?.host {
if host == "www.notsafeforwork.com" {
decisionHandler(.cancel)
} else{
decisionHandler(.allow)
}
}
}
}

参考:WKWebView 几个不常用的特性

面试解析

整理编辑:师大小海腾

Q:以下两段代码的执行情况分别如何?

1
2
3
4
5
6
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghij"];
});
}
1
2
3
4
5
6
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcdefghi"];
});
}
  • 第一段代码,self.name 是 __NSCFString 类型,存储在堆,需要维护引用计数,其 setter 方法实现为先 release 旧值,再 retain/copy 新值。这里异步并发执行 setter 就可能会有多条线程同时 release 旧值,过度释放对象,导致 Crash。
  • 第二段代码,由于指针足够存储数据,字符串的值就直接通过 Tagged Pointer 存储在了指针上,self.name 是 NSTaggedPointerString 类型。在 objc_release 函数中会判断指针是不是 Tagged Pointer,是的话就不对对象进行 release 操作,更不会过度释放而导致 Crash 了。

这里是 release 的实现:

1
2
3
4
5
6
7
8
__attribute__((aligned(16), flatten, noinline))
void
objc_release(id obj)
{
if (!obj) return;
if (obj->isTaggedPointer()) return;
return obj->release();
}

优秀博客

整理编辑:皮拉夫大王在此我是熊大东坡肘子

1、或许你并不需要重写 init(from:) 方法 – 来自:kemchenj

@东坡肘子:Codable 作为 Swift 的特性之一是很注重安全,也很严谨的,这就导致了它在实际使用时总会有这样那样的磕磕绊绊,我们不得不重写 init 方法去让它跟外部环境融洽地共存。本文介绍了一种通过重载 decodeIfPresent 方法以实现应对特殊类型的思路。从某种程度上来说,作者认为这甚至是比 Objective-C 的消息机制更加灵活的一种函数声明机制,而且它的影响范围是有限的,不容易对外部模块造成破坏(别声明为 open 或者 public 就没问题)。

2、使用 Property Wrapper 为 Codable 解码设定默认值 – 来自:onevcat

@东坡肘子:本文介绍了一个使用 Swift Codable 解码时难以设置默认值问题,并利用 Property Wrapper 给出了一种相对优雅的解决方式,来在 key 不存在时或者解码失败时,为某个属性设置默认值。这为编解码系统提供了更好的稳定性和可扩展性。最后,对 enum 类型在某些情况下是否胜任进行了简单讨论。

3、2021 年了,Swift 的 JSON-Model 转换还能有什么新花样 – 来自知乎:非著名程序员,作者 明林清

@皮拉夫大王:本文主要介绍 ExCodable 的特性和使用方法。在文章开头先介绍了常见的 JSON 转模型的几种方式,并对这些方式各自的优缺点进行了总结,随后引出 ExCodable 的特性及使用方法。

4、json 解析有什么可说道的 – 来自公众号:码农哈皮

@皮拉夫大王:文章开头先介绍了什么是 JSON。正文主要篇幅在介绍 SwiftyJSON 和 YYModel 的实现方案。文章最后引出了 HandyJSON,HandyJSON 是基于借助 metadata 结构来实现 JSON 转 Model 的。在这里额外提一句,如何推断 metadata 的结构,可以参考 GenMeta.cpp 中每个结构的 layout 函数。

5、Swift中Json转Model的便捷方式 – 来自掘金:我是熊大

@我是熊大:本文介绍 JSON、Model、Data、Dict 相互转换的小技巧和代码段,适合在实际工作中使用。

6、Swift 码了个 JSON 解析器(一) – 来自知乎:OldBirds

@我是熊大:正如作者所言,码了个 JSON 解析器,感兴趣的可以看一下。

学习资料

整理编辑:Mimosa

Xcode Build Settings

地址:https://xcodebuildsettings.com/

顾名思义,这个网站的作用是展示 Xcode 所有的 Build Settings。你可以在这里按分类查看所有的设置项,搜索你想要的设置项,或查询某个设置项的值类型及其默认值。对于常常要和 Build Settings 打交道的开发者来说,这个网站很实用。

Data-Science-For-Beginners

地址:https://github.com/microsoft/Data-Science-For-Beginners

来自 Microsoft 的 Data Science 基础课程,为期 10 周,有 20 节课。这是一个基于项目的课程,配套 40 多个小测试,通过该课程你可以学习到关于数据科学的基础知识。每节课程还有精美的插画配图,有兴趣学习 Data Science 的朋友可以尝试一下。

工具推荐

整理编辑:CoderStar

wakapi

地址https://wakapi.dev/

软件状态:免费,开源

软件介绍

Wakapi 是一个开源工具,可帮助我们跟踪使用不同编程语言等在不同项目上编码所花费的时间,并使用图表等形式展现出来,支付 Xcode,值得一玩。

wakapi

关于我们

iOS 摸鱼周报,主要分享开发过程中遇到的经验教训、优质的博客、高质量的学习资料、实用的开发工具等。周报仓库在这里:https://github.com/zhangferry/iOSWeeklyLearning ,如果你有好的的内容推荐可以通过 issue 的方式进行提交。另外也可以申请成为我们的常驻编辑,一起维护这份周报。另可关注公众号:iOS成长之路,后台点击进群交流,联系我们,获取更多内容。

往期推荐

iOS摸鱼周报 第二十九期

iOS摸鱼周报 第二十八期

iOS摸鱼周报 第二十七期

iOS摸鱼周报 第二十六期