Objective-C与Swift混编实践

Leroy70F 4年前
   <h3>前言</h3>    <p>由于Swift的语法趋于稳定,目前本人负责的项目也已经在小范围地引入Swift。关于混编过程中遇到的各种思考与选择,胡波的 <a href="http://mp.weixin.qq.com/s?__biz=MzA3ODg4MDk0Ng==&mid=403474677&idx=1&sn=5163adb2d80aa5b4f0099f79e6d783e1&scene=0#wechat_redirect" rel="nofollow,noindex">这篇文章</a> 已经阐述得得比较全面了,看完文章回头看自己在混编过程中的各种选择,与胡先生的看法是一致的。在此梳理一下一些值得注意的小细节。</p>    <h3>Optional</h3>    <p>基于Objective-C的工程一般都是将JSON转为Model,把数据以Model的形式在对象之间传递。当服务器传过来的值为空或者类型错误时,大部分解析框架都会将相对应的值置为nil。我们知道在Objective-C的世界里,向nil发送消息是不会引发任何错误的。</p>    <p>Objective-C中没有任何问题的属性:</p>    <pre>  @property (nonatomic, strong) NSString *GroupName;  </pre>    <p>到了Swift的世界里,变成了:</p>    <p><img src="https://simg.open-open.com/show/abce866ac983a0866392c1691dca1a25.jpg"></p>    <p>没错,隐式解析的存在,导致了容错能力的下降。只要服务端回传的参数中有一个空字段,必然引发Crash。这简直是自寻死路。</p>    <p>因此我们需要在Objective-C中,将属性标上 nullable ,这样Swift中该属性变成了optional,然后采用 if let 或者 guard let 来安全地拆包。</p>    <p>即使业务上决定了某些字段绝对不可能为null,也仍然要将其设为nullable。校验任何外来输入是编程时的基本准则之一。</p>    <p>想必大家也想到了,这样一来,在混编的过程中,if let充斥着Swift的代码,虽然保证了安全,但一定程度上降低了开发效率。个人还在不断摸索解决这个问题的方法。</p>    <h3>Selector</h3>    <p>在Objective-C的世界里, @selector 是我们的老朋友了。但到了Swift中我们不得不这样来用 Selector :</p>    <pre>  self.addTarget(self, action: "resignFristResponder", forControlEvents: .EditingDidEndOnExit)  </pre>    <p>如果不仔细看,一定不会发现上面的代码中我错将 resignFirstResponder 写成了 resignFristResponder 。这在编译时不会出现任何提醒,但到了运行时会引起崩溃。</p>    <p>幸运的是,Swift2.2用 #selector 关键字替代了字符串反射。此时拼写错误可以被编译期正确地纠正了--因为编译期会检查到方法不存在,并报一个error。</p>    <p>你可以这样写一个不带参数的selector:</p>    <pre>  #selector(resignFirstResponder)  </pre>    <p>如果是带参数的selector,则会稍微特别一点:</p>    <pre>  #selector(textFieldDidChange(_:)  </pre>    <p>采用下划线来忽略参数名,但记得保留分号。</p>    <p>有一点需要注意一下,Selector只支持反射Objective-C的方法,如果想要让#selector能正确识别Swift的方法,需要在方法前面加上 @objc 关键字</p>    <h3>命名</h3>    <p>Swift得益于Module而避免了类的命名冲突。在用Swift编写代码的时候,应当遵循Apple官方的命名规范。例如类命名不需要在类名前加前缀。如果类也需要在Objective-C中被调用,可以用@objc关键字来为Objective-C生成对应的类名。</p>    <pre>  @objc(PSLimitedTextField) public class LimitedTextField: UITextField {  </pre>    <p>同时API的设计也建议按照官方的 <a href="/misc/goto?guid=4959670390103542201" rel="nofollow,noindex">指导原则</a> 来。我从开始写Objective-C时就尽量模仿Apple的命名方式,后来在Objective-C到Swift的自动桥接上尝到了甜头。比如说:</p>    <pre>  - (instancetype)initWithName:(NSString *)name;  </pre>    <p>被自动桥接成了</p>    <pre>  init(name: String)  </pre>    <p>工厂方法</p>    <p>如果仔细对比Objective-C和Swift的接口,你会发现有的类的工厂方法消失了,有的类的还在。这同样是由于命名的问题所导致的。Apple将那些同类名一致的工厂方法桥接成了init方法,将一些默认单词(default,standard,shared etc.)开头的工厂方法保留了下来。</p>    <p>如: [NSDate date] 变成了 NSDate() .</p>    <p>但 [NSUserDefaults standardUserDefaults] ,保留了下来,变成了 NSUserDefaults.standardUserDefaults()</p>    <p><a href="/misc/goto?guid=4959670390190966210" rel="nofollow,noindex">PSNumberPad</a> 就因为工厂方法的命名问题,没能自动桥接成Swift的方法。因此,在混编过程中,如果Objective-C的组件有可能被Swift调用的,需要妥善设计接口以便自动桥接。一个最佳实践是,多采用 <strong>convince initialzer</strong> 替代工厂方法。</p>    <h3>参考资料</h3>    <p>在混编的过程中,大部分问题你可以在 <a href="/misc/goto?guid=4959670390274973503" rel="nofollow,noindex">这里</a> 找到想要的答案。再一次感谢Apple完善的文档!</p>    <p> </p>    <p>来自: <a href="/misc/goto?guid=4959670390358292784" rel="nofollow">http://shengpan.net/usingswiftwithobjc/</a></p>