Swift入门踩坑记:从刷题到写App的实战之路
每天早上8点,我准时坐在工位上,咖啡还没凉,键盘已经敲得噼里啪啦。最近在准备跳槽,白天应付项目deadline,晚上刷LeetCode,周末还得抽时间折腾新东西。上周五,产品经理突然丢过来一个需求:“能不能做个iOS端的小工具,先跑个原型看看?”——我差点一口老血喷在MacBook上。我们团队明明是做后端和Web的,现在连移动端都要啃了?
但转念一想,这不正好是跳槽前练手的好机会吗?毕竟现在大厂面试,Swift基础题都快成标配了。于是,我翻出尘封已久的Xcode,重新拾起那个曾经让我抓狂的Swift语言。
说实话,以前用过各种AI编程工具,Copilot、CodeWhisperer、Tabnine都试过,但最后还是回到了Cursor。不是因为它多牛,而是它对上下文的理解特别贴合我的编码习惯——尤其是当我边写代码边查GPT-4o时,它能无缝衔接我的思路,甚至帮我补全那些“我明明知道但一时想不起来”的语法细节。这次学Swift,我也全程开着Cursor + GPT-4o组合,效率直接拉满。
为什么是Swift?因为面试题挑战来了
前几天刷LeetCode时,看到一道题:“用Swift实现一个线程安全的单例”。我愣了三秒——这啥?我连Swift的class和struct区别都还没搞清楚呢!那一刻我意识到,光会写算法远远不够,语言生态和平台特性才是面试官真正想考察的。
于是,我决定系统性地过一遍Swift基础。不是为了成为iOS专家,而是为了在面试中不被问住。毕竟,谁也不想在“讲讲ARC机制”这种问题上卡壳,然后被面试官礼貌微笑送出门。
变量、常量和类型推断:别被let和var骗了
Swift最友好的地方之一就是类型推断。比如:
let name = "张三" // 推断为 String
var age = 25 // 推断为 Int
看起来简单,但坑就藏在细节里。let定义的是不可变绑定,不是“常量”那么简单。比如:
let user = User(name: "李四")
user.name = "王五" // ✅ 合法!如果User是class
但如果User是struct,上面这行就会报错。因为struct是值类型,let user意味着整个结构体不可变;而class是引用类型,let只是锁定了引用地址,对象内部依然可改。
这个区别在面试题里经常出现。我第一次写的时候就栽了,还以为Swift和Java一样,结果被GPT-4o一句话点醒:“Swift的let对值类型和引用类型语义不同。”
Optional:Swift的“甜蜜毒药”
如果说Swift有哪个概念让新手崩溃,那一定是Optional。String?、Int??、if let、guard let……一堆问号看得人眼花。
但用熟了你会发现,这其实是Swift最优雅的设计之一。它强制你处理空值,而不是像某些语言那样,等到运行时才给你一个NullPointerException然后崩掉。
举个实际场景:我写一个网络请求返回用户信息,可能成功也可能失败。用Optional就能清晰表达:
func fetchUser(id: Int) -> User? {
// 模拟网络请求
if id > 0 {
return User(name: "赵六")
}
return nil
}
// 调用时必须处理 nil
if let user = fetchUser(id: 123) {
print("用户: \(user.name)")
} else {
print("用户不存在")
}
更高级的用法是map和flatMap:
let optionalName: String? = "钱七"
let greeting = optionalName.map { "Hello, \($0)!" } // String?
这种链式处理在数据流中特别清爽。不过刚开始写的时候,我经常写出if let x = y, let z = x.w, let a = z.b这种地狱嵌套,后来学会了用guard let提前退出,代码立马清爽了。
函数和闭包:别再写冗长的回调了
Swift的函数是一等公民,闭包语法也极其简洁。比如排序:
let numbers = [3, 1, 4, 1, 5]
let sorted = numbers.sorted { $0 > $1 } // 降序
$0、$1这种速记符号一开始看不习惯,但用多了真香。尤其是在处理UI事件或网络回调时,闭包让代码逻辑更紧凑。
不过要注意循环引用!这是iOS开发的老大难问题。比如:
class ViewController {
var callback: (() -> Void)?
func setup() {
callback = {
self.doSomething() // ❌ 强引用self,可能导致内存泄漏
}
}
}
正确做法是用[weak self]:
callback = { [weak self] in
self?.doSomething()
}
我第一次上线就因为没加weak,导致页面退出后还在后台跑定时器,用户反馈“App退了还在耗电”,运维差点把我挂墙上。后来每次写闭包,手指都会自动敲出[weak self],肌肉记忆了。
协议(Protocol)与扩展(Extension):Swift的“组合优于继承”
Swift没有多重继承,但它用协议 + 扩展实现了更灵活的组合能力。
比如,我想让多个VC都支持“显示加载动画”:
protocol LoadingIndicator {
func showLoading()
func hideLoading()
}
extension LoadingIndicator where Self: UIViewController {
func showLoading() {
// 实现通用逻辑,比如添加HUD
}
func hideLoading() {
// 隐藏HUD
}
}
class ProfileVC: UIViewController, LoadingIndicator {
// 自动获得show/hide方法
}
这种设计比写一个BaseViewController要灵活得多——你可以按需混入不同能力,而不必陷入继承链的泥潭。
在准备面试时,我特意用这种方式重构了一个旧项目,结果面试官眼前一亮:“你这用的是面向协议编程(POP)啊!”——其实我只是被GPT-4o安利了而已。
性能优化:别让Swift“糖”变成性能陷阱
作为对性能优化有点执念的人,我特别关注Swift的底层开销。
比如,String vs Substring。很多人不知道,Substring是共享原字符串内存的,不会立即拷贝。但如果你长期持有Substring,反而会拖住整个原字符串无法释放。
let longText = "非常长的字符串..." // 假设10MB
let shortPart = longText.prefix(10) // Substring,仍引用longText
// 如果shortPart被存到全局变量,longText永远无法释放!
解决办法是显式转成String:
let safeShort = String(longText.prefix(10))
另外,struct虽然值语义安全,但频繁拷贝大对象也会有性能损耗。这时候可以用inout参数避免拷贝:
func updateLargeStruct(_ s: inout LargeStruct) {
s.field = "new value"
}
这些细节在日常开发中可能感觉不到,但在高频率调用的路径(比如滚动列表、动画帧)里,积少成多就是卡顿。
实战:用Swift写一个刷题助手原型
回到开头那个需求。我决定做个极简的“面试题挑战”小工具,功能就两个:展示题目、提交答案。
核心代码结构如下:
// 题目模型
struct InterviewQuestion {
let id: Int
let title: String
let description: String
let difficulty: Difficulty
}
enum Difficulty: String, Codable {
case easy, medium, hard
}
// 视图控制器
class QuestionViewController: UIViewController {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var descriptionLabel: UITextView!
@IBOutlet weak var answerTextField: UITextField!
var currentQuestion: InterviewQuestion? {
didSet {
updateUI()
}
}
private func updateUI() {
guard let q = currentQuestion else { return }
titleLabel.text = q.title
descriptionLabel.text = q.description
}
@IBAction func submitTapped(_ sender: UIButton) {
guard let answer = answerTextField.text, !answer.isEmpty else {
showAlert("答案不能为空")
return
}
// 提交逻辑...
}
}
布局用Auto Layout搞定,适配iPhone SE到iPhone 15 Pro Max。这里有个坑:UITextView在小屏设备上容易被键盘遮挡。解决方案是监听键盘通知,动态调整contentInset。
发布到TestFlight时,我还特意加了性能监控:记录每个VC的加载时间、内存峰值。结果发现,首次加载题目列表时,JSON解析占了80%时间。后来改成用Codable配合JSONDecoder,并开启keyDecodingStrategy,速度提升3倍。
跨平台适配?别天真了
虽然Swift现在也能跑在服务器(Vapor)甚至Windows上,但iOS开发的核心还是Apple生态。这意味着:
- 必须考虑刘海屏、灵动岛、状态栏高度
- Dark Mode要适配
- 后台任务有限制(别指望像Android那样随便跑Service)
- App Store审核规则随时可能变
我第一次提交就被拒了,理由是“缺少隐私描述”。原来只要用了网络权限,Info.plist里就必须加NSAppTransportSecurity和Privacy - Network Usage Description。这种“玄学”规则,只能靠经验或者被拒一次才知道。
写在最后:Swift不是终点,而是起点
折腾两周,这个小工具终于能用了。虽然离上线还有距离,但至少让我过了Swift基础这一关。更重要的是,在准备跳槽的过程中,我意识到:语言只是工具,解决问题的能力才是核心。
现在,当面试官问“讲讲Swift的内存管理”,我不再慌张,而是能从ARC、weak/unowned、循环引用到性能优化,串成一条线。这背后,是无数个早起8点的coding时光,是Cursor帮我补全的语法,是GPT-4o解释的底层原理,更是被产品经理“临时需求”逼出来的实战经验。
如果你也在准备iOS相关岗位,别只刷算法题。打开Xcode,写个真·App,哪怕只是个“面试题挑战”小工具。因为面试官真正想问的,从来不是“Swift有几个关键字”,而是:“你能不能用它,把事情搞定。”
共勉。

评论 0