Swift语法精讲:从基础到进阶(一个县城远程仔的实战手记)

Postman使者
2025-12-15 00:29
阅读 421

上周五晚上十一点,窗外下着岭南特有的回南天潮雨,我一边听着《夜曲》(对,就是周杰伦那首,别笑,写代码必须有BGM),一边改着iOS项目里那个又臭又长的Swift文件。产品经理凌晨三点发来消息:“能不能让这个按钮动起来像水波纹?参考微信支付。” 我默默点了杯美式续命,心想:这需求不就是个CAAnimation的事儿吗?结果一翻代码,发现三年前的老项目还是用Objective-C写的,连Swift混编都没配好。

那一刻,我真的想砸电脑——不是因为需求离谱,而是因为我居然还在用[self.view addSubview:button]这种上古语法。


为啥一个在深圳远程的小镇做题家要死磕Swift?

先自我介绍一下:本人,坐标广东某三线小县城(没错,就是那种快递三天到、外卖只有黄焖鸡的地方),但工牌挂的是深圳某腾讯系大厂(你懂的,名字不能说,怕被HR找上门)。每天早上九点准时上线,下午六点假装下班,实际经常熬到深夜——远程办公最大的好处是不用挤地铁,最大的坏处是……你永远在“on call”。

去年双11期间,我们团队接了个新项目:一款主打年轻用户的社交App,要求全平台原生体验,iOS端必须用Swift重写。领导拍板时轻飘飘一句:“Swift现在都5.9了,还写OC?时代变了。” 于是,作为组里唯一一个“会点iOS”的后端转全栈(其实只是大学选修过iOS开发),我被迫捡起尘封已久的Xcode。

更惨的是,前端同事用React Native写了Android版,动不动就拿“JS里一行搞定”来凡尔赛我。比如他说:“你看,我们在JS里直接 array.map(item => <View>{item}</0>) 就渲染列表了,你们Swift是不是还得写个for循环?” 我表面微笑,内心OS:等着,等我搞懂Swift的高阶函数,看谁笑到最后。


别再拿Swift当“高级OC”用了

很多老iOS开发者(包括我)一开始把Swift当成“语法糖版OC”:变量加个let、方法前面加个func,完事儿。结果呢?写出的代码既没享受Swift的安全性,又丢了性能优势,还被SwiftLint疯狂报错。

举个真实案例:我们项目初期有个网络请求模块,用URLSession封装,返回数据直接强转成Dictionary<String, Any>。结果某次测试环境返回了个null字段,App直接Crash。测试妹子甩过来一堆日志,最后一行赫然是:

Fatal error: Unexpectedly found nil while unwrapping an Optional value

熟悉的崩溃,熟悉的配方。那一刻我仿佛回到了大学做课程设计的日子——手动解析JSON,手动判空,手动祈祷后端别乱改接口。

痛定思痛,我决定系统性地重构整个数据层,真正用上Swift的类型安全和函数式特性。


基础篇:Optional、let/var、类型推断——别再!满天飞

先说最基础也最容易踩坑的 Optional

很多人(包括早期的我)为了省事,到处用!强制解包,觉得“反正后端不会返回null”。直到线上事故告诉你:后端真的会。

正确的姿势是什么?

// ❌ 危险!Crash预警
let name = json["name"] as! String

// ✅ 安全做法:用guard或if let
guard let name = json["name"] as? String else {
    print("name字段缺失或类型错误")
    return
}

再来说 let vs var。Swift鼓励不可变性,能用let就别用var。这不仅是语法规范,更是思维转变——一旦你习惯用不可变值,后续用map/filter/reduce时会自然得多。

还有 类型推断。Swift的类型系统强大到几乎不需要显式声明类型(除了public API)。比如:

// 冗余写法
let users: [User] = fetchUsers()

// 简洁写法(推荐)
let users = fetchUsers()

少写点代码,多活两年。


进阶篇:函数式编程不是装X,是真的香

回到那个被JS同事凡尔赛的列表渲染问题。在Swift里,我们完全可以写出类似JS的链式操作:

// 假设我们有一堆用户数据
struct User {
    let id: Int
    let name: String
    let isActive: Bool
}

let allUsers = fetchAllUsers()

// 筛选活跃用户 + 按ID排序 + 提取名字
let activeUserNames = allUsers
    .filter { $0.isActive }
    .sorted { $0.id < $1.id }
    .map { $0.name }

看,是不是有点JS内味了?而且因为Swift是强类型的,编译器会提前告诉你isActive是不是Bool,id是不是Int,根本不会等到运行时才报错。

我们项目里现在大量用这种写法处理数据转换。以前一个方法动不动上百行,现在拆成几个小函数,每个只做一件事,测试覆盖率蹭蹭涨(虽然测试同事说我是在逃避写UT 😅)。


高阶技巧:协议扩展、泛型、Result类型——告别回调地狱

说到回调地狱,不得不提我们早期的网络层:

// 老旧写法:嵌套completion handler
API.fetchProfile { profile in
    API.fetchFriends(of: profile.id) { friends in
        API.fetchMessages(with: friends.first?.id) { messages in
            // ...此处省略800字嵌套
        }
    }
}

看得人头晕。后来我用 Combine(Apple官方响应式框架)重构了一版,但团队里有人抱怨学习成本高。于是折中方案:用 Result类型 + completion handler 模拟Promise。

enum NetworkError: Error {
    case invalidURL
    case noData
    case decodingFailed
}

typealias ResultHandler<T> = (Result<T, NetworkError>) -> Void

func fetchProfile(completion: @escaping ResultHandler<Profile>) {
    // ...网络请求
    if let data = data {
        do {
            let profile = try JSONDecoder().decode(Profile.self, from: data)
            completion(.success(profile))
        } catch {
            completion(.failure(.decodingFailed))
        }
    }
}

调用时:

fetchProfile { result in
    switch result {
    case .success(let profile):
        print("拿到用户信息: \(profile)")
    case .failure(let error):
        print("请求失败: \(error)")
    }
}

虽然不如async/await简洁,但至少扁平化了逻辑。好消息是,Swift 5.5+已经支持async/await,我们新模块已经开始迁移:

// Swift 5.5+
do {
    let profile = try await fetchProfile()
    let friends = try await fetchFriends(of: profile.id)
    let messages = try await fetchMessages(with: friends.first?.id ?? 0)
} catch {
    print("出错了: \(error)")
}

终于,iOS也能告别回调地狱了!(虽然得iOS 15+才支持,但新项目管他呢)


项目实战:如何用Swift特性提升App质量?

光讲语法太干,说说我们项目里的具体实践。

1. 用枚举替代魔法字符串

以前配置API路径,全是这样:

// ❌ 魔法字符串,易错难维护
let url = "https://api.example.com/v1/users/\(userId)/posts"

现在:

enum APIEndpoint {
    case userProfile(Int)
    case userPosts(Int)
    
    var url: URL {
        let baseURL = "https://api.example.com/v1"
        switch self {
        case .userProfile(let id):
            return URL(string: "\(baseURL)/users/\(id)")!
        case .userPosts(let id):
            return URL(string: "\(baseURL)/users/\(id)/posts")!
        }
    }
}

不仅类型安全,还能配合Mock服务做单元测试。

2. 用属性包装器简化状态管理

SwiftUI里常用@State@Binding,但我们发现有些业务状态(比如登录态)需要跨View共享。于是自己撸了个简单的@UserDefault属性包装器:

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T
    
    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
    
    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

// 使用
class UserManager {
    @UserDefault("isLoggedIn", defaultValue: false)
    static var isLoggedIn
}

一行代码搞定本地持久化,比手写UserDefaults清爽多了。

3. 避免隐式解包,拥抱安全初始化

以前为了图快,经常这样写:

class ViewController: UIViewController {
    var viewModel: ViewModel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel = ViewModel() // 假设这里一定会执行
    }
}

结果某次重构时忘了初始化,上线后Crash。现在全部改成:

class ViewController: UIViewController {
    private let viewModel: ViewModel
    
    init(viewModel: ViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("不支持Storyboard")
    }
}

依赖注入虽好,就是写起来啰嗦点。但想想Crash带来的背锅风险,这点啰嗦算什么?


App Store审核那些坑:Swift项目特别注意

最后聊聊上架。我们第一次提交Swift重写的版本,被苹果拒了三次:

  1. 第一次:用了私有API(其实是某个第三方库偷偷调用了UIDevice的私有属性)
  2. 第二次:没适配深色模式(SwiftUI默认支持,但我们混用了UIKit组件)
  3. 第三次:隐私描述缺失(iOS 15+要求所有网络权限都要写NSDescription)

血泪教训:Swift项目≠自动合规。尤其是混编项目,一定要用otool -L检查二进制依赖,用grep扫一遍源码里的敏感词。

另外,Swift编译产物体积比OC大,记得在Build Settings里开启:

  • Strip Linked Product → Yes
  • Dead Code Stripping → Yes
  • Bitcode → No(除非你确定要用)

我们最终包体积从85MB压到62MB,审核一次过。


总结:小镇做题家的Swift成长路

从去年被逼着学Swift,到现在能带队搞新项目架构,我最大的感悟是:Swift不是一门“更好的OC”,而是一种全新的编程范式

它逼你思考类型安全,逼你写不可变代码,逼你用函数式思维解决问题。刚开始会觉得束缚,但一旦习惯,你会发现代码Bug少了,协作顺畅了,连Code Review都变得愉快了(毕竟没人敢随便改你的let变量)。

至于那个JS同事?上周他来找我问怎么在React Native里处理复杂的动画链,我说:“要不你试试原生模块?我们Swift这边有个现成的水波纹组件……”

他沉默了三秒,然后说:“算了,我还是用Lottie吧。”

——你看,技术没有高低贵贱,只有合不合适。但在Apple生态里,Swift就是那个“合适”的答案。

好了,咖啡喝完了,雨也停了。明天还要改那个水波纹按钮,据说产品又加了“点击要有音效”的需求……各位,保重。


附:Swift vs JavaScript 核心特性对比(项目选型参考)

特性 Swift JavaScript
类型系统 静态强类型(编译期检查) 动态弱类型(运行时可能报错)
空安全 Optional 显式处理 null/undefined 隐式,易Crash
并发模型 async/await + GCD Promise/async-await + Event Loop
内存管理 ARC(自动引用计数) GC(垃圾回收)
原生性能 接近C++ JIT优化,但仍有开销
跨平台 Apple生态(iOS/macOS/watchOS) 全平台(浏览器/Node.js/RN)
学习曲线 较陡(需理解值类型、协议等) 平缓(但深入异步/原型链较难)

如果你在做Apple平台项目,别犹豫,上Swift。至于JS?让它继续在它的宇宙里发光发热吧。

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝