从 OC 到 Swift,老 iOS 开发者踩过的 10 个语法大坑

从 OC 到 Swift,老 iOS 开发者踩过的 10 个语法大坑

很多老 iOS 工程师写 OC 时靠的是"运行时直觉":nil 能发消息、强转能糊弄、block 和 closure 差不多、NSString 和 String 随便换。

一到 Swift,编译器开始"找茬",运行时反而更乖,但人先崩了。

下面这 10 个坑,基本是每个从 OC 迁移到 Swift 的人都会踩一遍。


1. 以为 nil 还能"发消息不崩"

OC 里:

ini 复制代码
NSString *name = nil;
NSUInteger len = [name length]; // 返回 0,不崩

Swift 里:

javascript 复制代码
let name: String? = nil
let len = name.count // 编译都过不了

Swift 的 nil 不是"空对象",而是"可选值里没有值"。

老 OC 人最容易犯的错是:把 Swift 的 nil 当成 OC 的 nil

正确姿势:

ini 复制代码
let len = name?.count ?? 0

OC:nil 是朋友,悄悄帮你兜底

Swift:nil 是类型系统里的炸弹,必须先拆


2. 疯狂用 ! 强解包,把 Swift 写成"会崩的 OC"

刚学 Swift 的人写:

ini 复制代码
let user = getUser()!
let name = user.name!

这和 OC 里:

ini 复制代码
User *user = [self getUser];
NSString *name = user.name;

看起来一样,但 Swift 里只要有一层是 nil,直接:

lua 复制代码
Fatal error: Unexpectedly found nil while unwrapping an Optional

老 OC 开发者常误以为"我肯定有值",结果网络数据、字典解析、IBOutlet、混编接口全在坑你。

经验法则:

  • 业务数据:用 guard let / if let
  • 配置项默认值:用 ??
  • ! 只留给 @IBOutlet 和你 100% 控制的初始化流程

3. OC 没标 nonnull,Swift 拿到 T! 隐式解包

OC 头文件:

less 复制代码
@interface UserService : NSObject
- (User *)currentUser;
@end

没包 NS_ASSUME_NONNULL_BEGIN,Swift 看到的是:

go 复制代码
func currentUser() -> User!

你一写:

scss 复制代码
let name = UserService().currentUser().name

OC 返回了 nil,Swift 直接崩。

这是混编项目里最阴的坑之一。

解法:

  • OC 侧加 NS_ASSUME_NONNULL_BEGIN / END
  • 返回可能为空的写 nullable
  • Swift 侧永远当可选值处理

4. 把 NSArray / NSDictionary 的"可变性"带进 Swift

OC 里:

ini 复制代码
NSArray *arr = @[@"a"];
NSMutableArray *m = (NSMutableArray *)arr; // 编译过,运行炸

Swift 里更隐蔽:

css 复制代码
let arr = ["a"]
var m = arr
m.append("b")
print(arr) // ["a"]

老 OC 人以为"传数组进去会被改",其实 Swift 的 Array / Dictionary / String值类型,赋值会拷贝(COW)。

坑不在"会改坏",而在:

  • 你以为共享状态会同步
  • 你以为 let 只是不能重新赋值(其实元素也不能改)
  • 你以为 var dict = ocDict as? [String: Any] 还是同一个对象

OC:引用语义,处处是共享

Swift:值语义,默认隔离


5. id 思维残留:什么都写 Any / AnyObject

OC 里:

kotlin 复制代码
- (id)parse:(NSData *)data {
    return @{@"code": @0};
}

Swift 里继续写:

swift 复制代码
func parse(_ data: Data) -> Any {
    return ["code": 0]
}

然后满屏:

javascript 复制代码
if let dict = result as? [String: Any] { ... }

老项目能跑,但类型安全全没了。

Swift 的正确思路:

  • 网络模型用 Codable
  • 配置用具体 struct
  • Any 只出现在桥接边界

6. 以为 BOOLBool 是一回事

OC:

ini 复制代码
BOOL flag = 2; // YES
if (flag) { ... }

Swift:

csharp 复制代码
let flag: Bool = 2 // 编译错误

更坑的是:

arduino 复制代码
if someInt { ... } // ❌

OC 里"非 0 即真",Swift 里 只有 Bool 能进 if

老 OC 人写:

arduino 复制代码
if user.age { } // 想表达 age > 0,结果编译不过

要写:

复制代码
if user.age > 0 { }

7. 初始化顺序:OC 可以先留空,Swift 不行

OC:

less 复制代码
@interface VC : UIViewController
@property (nonatomic, strong) NSMutableArray *items;
@end

@implementation VC
- (void)viewDidLoad {
    self.items = [NSMutableArray array];
}
@end

Swift 不允许"属性没值还活着":

kotlin 复制代码
class VC: UIViewController {
    var items: [String] // ❌ 编译错误
}

你必须:

less 复制代码
var items: [String] = []
// 或
var items: [String]?

还有两段式初始化规则:

  1. 先初始化自己的存储属性
  2. 再调 super.init

老 OC 人常在 init 里调 self.method,Swift 直接骂你:

python 复制代码
'let' property is initialized late / self used before all stored properties are initialized

8. Block 和 Closure 捕获 self 的坑

OC:

ini 复制代码
self.completion = ^{
    [self reload];
};

内存泄漏,但"感觉没事"。

Swift:

ini 复制代码
self.completion = {
    self.reload()
}

编译器直接提醒你循环引用。

老 OC 人第一次写 Swift 会这样"修":

objectivec 复制代码
self.completion = { [weak self] in
    self?.reload()
}

然后又踩新坑:

  • selfSelf?
  • 后面每一句都要解包
  • 多次用 self? 时,可能中途被释放

更稳的写法:

swift 复制代码
self.completion = { [weak self] in
    guard let self else { return }
    self.reload()
}

9. NS_ENUM / NS_OPTIONS 不是同一个东西

OC:

ini 复制代码
typedef NS_OPTIONS(NSUInteger, ViewState) {
    ViewStateIdle = 1 << 0,
    ViewStateLoading = 1 << 1,
};

Swift 里:

swift 复制代码
struct ViewState : OptionSet {
    let rawValue: UInt
    static let idle = ViewState(rawValue: 1 << 0)
    static let loading = ViewState(rawValue: 1 << 1)
}

老 OC 人写:

arduino 复制代码
if state == .idle { }        // ✅
if state & .loading { }      // ❌ Swift 里不能这么写

Swift 里要:

javascript 复制代码
if state.contains(.loading) { }

而普通 NS_ENUM 是真正的 enum,不能按位与。

OC:枚举和位掩码语法很接近

Swift:枚举是枚举,OptionSet 是 OptionSet


10. 以为 Swift 能像 OC 一样"动态调用"

OC:

ini 复制代码
SEL sel = NSSelectorFromString(@"doSomething:");
[self performSelector:sel];

Swift 默认不支持这种玩法。

你写:

scss 复制代码
let sel = Selector("doSomething")
perform(sel) // 很多时候根本不可用

更别说:

  • method_exchangeImplementations
  • NSClassFromString 后直接调方法
  • KVC 随便写 "userName" 拼字段名

Swift 的世界观是:

编译期能定的,就别拖到运行时

所以老 OC 人最容易幻灭的地方不是语法,而是:

原来我以前写的"黑魔法",在 Swift 里既不推荐,也不好使。


一句话总结

OC 习惯 Swift 现实
nil 发消息不崩 可选值必须解包
id 通吃 类型必须明确
引用共享 值类型拷贝
非 0 即真 只能是 Bool
先声明后赋值 初始化必须完整
block 强持 self closure 要 capture list
运行时动态调用 编译期静态检查
强转 NSMutableArray Swift 没有"可变强转"
枚举当位运算 OptionSet 才是对的
后期再修 bug 编译器现在就骂你
相关推荐
我的xiaodoujiao2 小时前
Django 基础知识详细图文教程 9-Django 模板引擎 2
开发语言·数据库·后端·django
烈风逍遥2 小时前
第五篇:通用 LLM 流式对话:前后端联接的完整实现
前端·后端·架构
Ticnix2 小时前
别再手动上线了:一条命令带备份、健康检查和自动回滚
后端·python·ci/cd
ArkPppp3 小时前
如何从零上线一个耐造的Redis缓存系统——最直接最不绕弯子的方式
后端·ai编程
Kyrie_kk3 小时前
Java--ProcessBuilder操作系统进程
java·后端
Ticnix3 小时前
迁移脚本能跑通,不代表你回滚得回来
后端·python
SamDeepThinking3 小时前
关于java final关键字的可见性
java·后端·面试
我的xiaodoujiao3 小时前
Django 基础知识详细图文教程 10-Django 模板引擎 3
后端·python·测试工具·django
拖孩3 小时前
给小程序加「一键发公众号贴图」,我把 wx.shareToOfficialAccount 的三个坑趟了一遍
前端·后端·微信小程序