Swift入门踩坑记:从刷题到写App的实战之路

App后端
2026-03-03 22:28
阅读 1680

每天早上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机制”这种问题上卡壳,然后被面试官礼貌微笑送出门。


变量、常量和类型推断:别被letvar骗了

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 letguard 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("用户不存在")
}

更高级的用法是mapflatMap

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里就必须加NSAppTransportSecurityPrivacy - Network Usage Description。这种“玄学”规则,只能靠经验或者被拒一次才知道。


写在最后:Swift不是终点,而是起点

折腾两周,这个小工具终于能用了。虽然离上线还有距离,但至少让我过了Swift基础这一关。更重要的是,在准备跳槽的过程中,我意识到:语言只是工具,解决问题的能力才是核心

现在,当面试官问“讲讲Swift的内存管理”,我不再慌张,而是能从ARC、weak/unowned、循环引用到性能优化,串成一条线。这背后,是无数个早起8点的coding时光,是Cursor帮我补全的语法,是GPT-4o解释的底层原理,更是被产品经理“临时需求”逼出来的实战经验。

如果你也在准备iOS相关岗位,别只刷算法题。打开Xcode,写个真·App,哪怕只是个“面试题挑战”小工具。因为面试官真正想问的,从来不是“Swift有几个关键字”,而是:“你能不能用它,把事情搞定。”

共勉。

评论 0

最热最新
暂无评论
App后端Lv.1
0
影响力
0
文章
0
粉丝