“踩坑”经验分享:Swift语言落地实践

news2025/1/10 23:44:16

作者 | 路涛、艳红

导读

Swift 是一种适用于iOS/macOS应用开发、服务器端的编程语言。自2014年苹果发布 Swift 语言以来,Swift5 实现了 ABI 稳定性、Module 稳定性和Library Evolution,与Objective-C(下文简称“OC”)相比,Swift 在开发效率、安全、编译优化、运行性能和内存管理方面具有显著优势。(官方博客:https://www.swift.org/about/ )

百度App 已在工程和环境上支持 Swift 开发,百度搜索大前端团队负责搜索服务的稳定落地,我们积极探索 Swift的应用,希望能大幅提升开发效率和灵活性、提升端用户的搜索体验。然而,在实施过程中可能会遇到各种问题,例如代码陈旧且不支持Swift,人员对Swift掌握不够熟练、意识不足,协作方对Swift的支持不足等。

对于其他语言来说,Swift相对年轻,我们在实践过程中整理一些常见问题及其解决方法,希望能帮助读者更顺利地使用Swift进行编程,提高研发效率。

全文6947字,预计阅读时间18分钟。

01 Swift 适用场景

在决定是否引入Swift前,我们需要判断场景是否适合。通常情况下,可以用OC的场景均适合使用Swift,但也有一些不太适合直接替换的场景,需要慎重,比如:

1、涉及OC动态性,频繁在runtime时操作属性和方法;

2、核心基础功能,出现问题影响面较大的逻辑;

3、调用C++(目前Swift不能直接调用C++);

4、继承不支持Swift组件的类。

此外,对使用OC比较久远的工程,使用Swift前也应注意:

1、能在工程环境和单独模块上支持Swift;

2、模块较多的工程,可以内外OC和Swift混编;

3、为了避免Swift Waring带来的潜在问题,可以把SWIFT_TREAT_WARNINGS_AS_ERRORS设置为YES,这样警告会作为错误,辅助程序员更好的规范代码;

4、模块Module化后,要注意维护 umbrella header 中的公开头文件。

注:本文中的“组件”均指代工程中的“Target”。

02 Swift的基本用法

2.1 Swift 的字符串为什么这么难用?

如:字符串不能通过索引取字符

  • 原因:Swift认为字符串是由一个个字形群集(grapheme clusters) 组成的,字形群集的大小不固定所以不能用整数去索引 (字形群集其实就是Swift中的Character(字符)类)。

  • 解决方案:如要通过下标取字符可以为String添加扩展在下标subscript实现通过传入Int索引,在subscript转为String.index获取对应字符的方式。

2.2 try try? try! 的区别

当你进行文件操作时,可能会遇到需要使用try、try?和try!的情况。它们在异常处理方面有所不同。

1、使用try时,如果出现异常,程序会进入异常处理流程,你可以在catch语句块中处理这个异常。

2、使用try?时,如果发生异常,它不会进入异常处理流程,而是返回一个可选值类型。也就是说,如果出现异常,它将返回nil。

3、使用try!时,它不允许异常继续传播。一旦出现异常,程序会立即停止执行。

因此,在文件操作中,你可以根据需要选择合适的异常处理方式。在百度App中一般推荐使用try?。

2.3 public 和 open 的区别

在Swift语言中,public和open都是用于在模块中声明需要对外界暴露的函数的关键字,但它们在继承和公开程度上有所不同。

1、public关键字修饰的类在模块外部无法被继承。 这意味着,如果其他模块试图继承这个类,编译器会报错。这样的限制可以保护类的完整性,但也可能限制了其在其他模块中的可重用性。

2、open关键字则允许任意继承。 如果一个类被open关键字修饰,那么其他模块中的类可以自由地继承这个类,不受任何限制。这样的公开程度使得open关键字修饰的类在模块间的重用性和扩展性更加灵活。

从公开程度上来说,public的限制比open更严格,所以可以说public < open,即public的公开程度比open要低。

2.4 解析JSON情况

在Swift中解析JSON的情况,如果自行将JSON转换为字典,需要涉及到类型判断、转换等操作,代码比较复杂。这时可以使用第三方库SwiftyJSON、ObjectMapper或者系统库JSONEncoder来简化操作,提高开发效率。

2.5 UIView子类必须添加init?(coder decoder: NSCoder)的原因

1、这是NSCoding protocol定义的,遵守了NSCoding protocol的所有类必须继承。只是有的情况会隐式继承,而有的情况下需要显示实现。

2、当我们在子类定义了指定初始化器(包括自定义和重写父类指定初始化器),那么必须显示实现required init?(coder aDecoder: NSCoder),而其他情况下则会隐式继承,我们可以不用理会。

3、当我们使用storyboard实现界面的时候,程序会调用这个初始化器。

4、注意要去掉fatalError,fatalError的意思是无条件停止执行并打印。

2.6 Swift类和子类的初始化

Swift的类和子类初始化涉及到两个关键阶段。首先,确保所有的存储属性被赋予初始值,然后,在实例准备使用之前,可以自定义存储属性的值。为了确保这两个阶段成功,实施了四步安全检查,详细如下:

1、在完成本类所有存储属性赋值之后,指定构造器才能向上代理到父类的构造器。

2、在为继承的属性设置新值之前,指定构造器必须向上代理调用父类构造器。

3、便利构造器必须先调用其他构造器,再为任意属性(包括所有同类中定义的)赋新值。

4、在第一阶段构造完成之前,构造器不能调用任何实例方法,不能读取任何实例属性的值,不能引用self作为一个值。

总之,类初始化必须完成的一个任务就是让所有的存储属性都有初始值(optional 除外)。如果父类有指定初始化,子类必须也有指定初始化,并且必须调用父类的其中一个指定初始化(如果是必须初始化,就是重载),并遵循两段式初始化的规则。一个便利初始化必须调用同一类中的初始化方法(可以是另一个便利初始化,也可以是指定初始化),但最终一定会调用到一个指定初始化。便利初始化不遵循两段式初始化的规则,不能被子类调用或者重载。

03 OC与Swift的互相调用及跳转

3.1 组件内Swift文件调用公开OC头文件

  • 将公开OC头文件(如:xyz.h)添加到组件(如:ABC)umbrella header中(如:#import);

  • Swift文件中直接调用公开OC头文件内容。

3.2 组件内Swift文件调用非公开(私有)的OC文件

组件应该尽可能少的公开暴露头文件,但Swift和OC混编不可避免使用OC非公开头文件,因此我们可以采取以下措施:将Framework 中将私有头文件声明为一个私有 module(modulemap内声明),由组件内的 Swift 源码 import 该私有 module 即可。

1、创建Private.modulemap文件,以NewModule做为组件名为例,可以命名为NewModule.private.modulemap,内容为下,module后面加_Private

  • 罗列头文件的形式
framework module NewModule_Private {
  header "xxxxx.h"
}
  • 使用根头文件的形式,添加头文件NewModule_Private.h
framework module NewModule_Private {
  umbrella header "NewModule_Private.h"

  export *
  module * { export * }
}

2、在组件build settings中配置MODULEMAP_PRIVATE_FILE路径,MODULEMAP_PRIVATE_FILE=‘NewModule.private.modulemap’;百度App中在NewModule.boxspec中如下代码设置路径;

s.xcconfig = {
    'MODULEMAP_PRIVATE_FILE' => '${BOX_ROOT}/NewModule.private.modulemap'
}

3、将NewModule.private.modulemap添加到工程目录;百度App中在NewModule.boxspec中如下代码设置路径;

s.refer_files = [
      "NewModule.private.modulemap",
]

4、将xxxxx.h设置为Private header,百度App中在NewModule.boxspec中如下代码设置xxxxx.h到Private header

s.private_headers = [
    "Sources/xxxxx.h"
  ]

5、调用方式

import NewModule_Private
let objectX = xxxxx()
print(objectX)

注意:

  • 添加的Private头文件可能存在传递头文件的情况,即import其他头文件,也需要将传递的头文件添加到NewModule_Private中,同时import需要使用尖括号;

  • Private Header也会暴露在framework中,所以可以约定外部组件使用Public Header,而避免使用Private Header,因为随着业务发展和Swift&OC混编,Private Header是不稳定的。

3.3 组件内OC文件如何调用Swift文件?

  • Swift 类需要继承 NSObject,方法前面加上@objc 标识,并且是 public 或者 open 的;

  • 引入方式 #import"

3.4 OC中的向前声明,被Swift文件引用该组件会报错

如error: cannot find protocol definition for ‘xxxProtocol’

  • 原因:此报错在OC中是代码警告,百度App中默认情况Swift中SWIFT_TREAT_WARNINGS_AS_ERRORS 设置为 YES,导致OC中的Warning视为Error;

  • 解决方案:三选一

1、暂时设置 SWIFT_TREAT_WARNINGS_AS_ERRORS 为 NO

2、import xxxProtocol 不要向前声明

3、使用 pragma 忽略警告

3.5 Swift怎么用OC定义的宏?

  • 在Swift中,能直接使用定义为常量的宏,不能使用带有方法调用的宏,也不能使用静态常量。
下面这种定义为常量的宏可以使用
#define APP_LANGUAGE_EN @"en" 
#define kNavigationBarHeight 44.0

下面带有方法调用的宏不可以使用
#define kScreenHeight [[UIScreen mainScreen] bounds].size.height
#define kScreenWidth [[UIScreen mainScreen] bounds].size.width

下面带有静态常量swift不能使用,可以改成宏
static NSString *const StopTabRefreshNotifyNameHtml = @"TabRefreshNotifyNameHtml";

3.6 Swift与OC泛型的混编

  • 在我分们基础框架中,有一个使用了OC泛型的类,如:
@interface BBAXYZ<T> : NSObject <BBAXYZEventProtocol>  
@property (nonatomic, weak) T page;  
@end

这个泛型的使用导致无法使用Swift来继承和开发BBAXYZ的子类。然而,这个基础框架是业务的核心部分,因此,我们需要在未来支持Swift的开发。

  • 经过仔细观察和分析,我们发现泛型主要被用于指定page属性的类型。因此,我们可以考虑去掉泛型,改为提供一个返回适当类型的方法。这样,我们就可以在Swift中顺利地继承和使用这个基础框架。修改后的代码如下:
@interface BBAXYZ : NSObject <BBAXYZEventProtocol>  
- (id<BBAXYZEventProtocol>)page;  
@end

然后,我们可以创建一个OC类来实现这个基础框架,并让所有的子类继承这个OC类并实现 page 方法,以返回适当类型的对象。这样,我们就可以在Swift中顺利地继承和使用这个基础框架。

例如:

@interface BBAABC : BBAXYZ  
- (UIViewController<BBAXYZEventProtocol> *)page; 
@end

需要注意的是,虽然这样的修改增加了轻量级的中间OC类,但它仍然实现了Swift与OC的混编,并允许我们在Swift中开发新的子类。这种方式既保证了代码的兼容性,又使得我们可以继续利用OC的优点。

  • 使用方式
class BBAEFG: BBAABC {
    
}

3.7 Swift调用OC接口,OC的nullability标注使用时的注意事项

问题场景:

1、OC 接口定义为 nonnull,swfit 调用时正常是当做不可选类型使用,这时如果 OC 接口不规范返回 nil,则出现运行时崩溃。

2、OC 接口未定义 nonnull 或 nullable,在这种情况下,编译器会将 OC 的指针类型当成是隐式解析可选类型(例如 String!)导入到 Swift 中。swift 调用时,OC接口如果返回 nil,将会因为隐式解析一个为 nil 的可选值导致运行时崩溃。

解决方式:

1、Swift 调用 OC 接口时,如果 OC 的接口声明为 nonnull 或未指定 nullability 时,只有明确 OC 接口不为空的情况下才可调用

2、在 OC 环境下,将 nil 赋值给 nonnull 指针也没有关系,编译器只会产生警告。这就需要程序员按规范编写 OC 代码,正确使用 nullability 标注,并增加运行时判空的断言,以支持向后兼容。

04 其他常见问题

4.1 Xcode编译只提示编译错误,提示信息非常少

  • 原因:使用Swift语言开发的组件,依赖了不支持Module化的组件,导致组件都能编译成功,但整个工程却编译失败了;

  • 解决方案:二选一

1、检查并保障所有依赖的组件都已经Module化了,如配置build settings;

2、在组件中新增Swift文件(空文件也行)。

4.2 由于组件开启了Library Evolution 导致的编译报错

错误显示:@objc’ instance method in extension of subclass of ‘xxxxx’ requires iOS 13.0.0

这是由于组件开启了Library Evolution导致,开关BUILD_LIBRARY_FOR_DISTRIBUTION 控制的。

一个库开启了Library Evolution,在依赖链下游的库中将:

1、对它的类实现 @objc 子类。

2、对它的类使用 extension 实现 @objc 的方法(这在 UIKit 的 protocol 中经常会遇到)。

3、对它的类实现子类,并添加 @objc 方法,且方法中使用父类的类型作为参数。

这些功能是实现的局限。估计是需要在 Swift 运行时有一些对应的更改,所以只在 swift 5.1 (iOS 13)运行时里才可以运行。

除此之外,还会有一些编译时没有报错,但运行时 crash 或结果不正确的情况。

百度App中默认开启Library Evolution,一个组件关闭Library Evolution会导致二进制存在不兼容的情况,暂时无解决方案。

4.3 暴露的Private头文件如果使用双引号import,会报警告,需要修改为尖括号

如果使用import <xxxx.h>,Project下其他Target就引用不到了,如百度App中Debug模块引用此Private头文件时,会报错 not found with include, use “quotes” instead。

  • 原因:这是由于其他Target对主模块引用时默认是从当前项目下引用头文件,而尖括号方式从系统库或用户库中引用;

  • 解决方案:其他Target将Private Header 配置到其 HEADER_SEARCH_PATHS,使用双引号和尖括号均可。

05 总结

以上是我们在Swift开发过程中所遇到的一些常见问题及其相应的解决方案。然而,随着我们不断深入Swift开发这片浩渺的海洋,更多独特的问题将会逐渐浮现。我们会持续将这些新问题以及其对应的解决方案整理并发布出来,为广大的开发者们提供有价值的参考。欢迎大家留言探讨。

百度搜索大前端团队,持续招聘 iOS/Android/Web前端 研发工程师。

简历欢迎投递至joinefe@baidu.com

——END——

推荐阅读

移动端防截屏录屏技术在百度账户系统实践

AI Native工程化:百度App AI互动技术实践

揭开事件循环的神秘面纱

百度搜索展现服务重构:进步与优化

百度APP iOS端包体积50M优化实践(七)编译器优化

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1341780.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据仓库-数仓优化小厂实践

一、背景 由于公司规模较小&#xff0c;大数据相关没有实现平台化&#xff0c;相关的架构都是原生的Apache组件&#xff0c;所以集群的维护和优化都需要人工的参与。根据自己的实践整理一些数仓相关的优化。 二、优化 1、简易架构图 2、ODS层优化 2.1 分段式解析 随着业务增长…

数据结构与算法教程,数据结构C语言版教程!(第二部分、线性表详解:数据结构线性表10分钟入门)一

第二部分、线性表详解&#xff1a;数据结构线性表10分钟入门 线性表&#xff0c;数据结构中最简单的一种存储结构&#xff0c;专门用于存储逻辑关系为"一对一"的数据。 线性表&#xff0c;基于数据在实际物理空间中的存储状态&#xff0c;又可细分为顺序表&#xff…

助力城市部件[标石/电杆/光交箱/人井]精细化管理,基于YOLOv7【tiny/yolov7】开发构建生活场景下城市部件检测识别系统

井盖、店杆、光交箱、通信箱、标石等为城市中常见部件&#xff0c;在方便居民生活的同时&#xff0c;因为后期维护的不及时往往会出现一些“井盖吃人”、“线杆、电杆、线缆伤人”事件。造成这类问题的原因是客观的多方面的&#xff0c;这也是城市化进程不断发展进步的过程中难…

点亮AI未来的U盘

随着人工智能行业蓬勃发展&#xff0c;如今国内外大模型如雨后春笋般涌现&#xff0c;国内AI赛道更是步入水深火热的发展阶段。上半年的AIGC赛道国内投融资规模以模型层为主&#xff0c;这一现象充分说明了国内人工智能应用场景的丰富多样&#xff0c;投资机会也更加聚焦于应用…

用电脑将图片转为excel表格有几种方法?怎么操作?

将图片转为Excel表格&#xff0c;一般需要借助OCR(光学字符识别)技术。OCR技术可以将图片中的文字提取出来&#xff0c;并转换成Excel表格中的数据。以下是几种常用的方法&#xff1a; 一、.使用在线OCR工具 1、打开金鸣表格文字识别&#xff08;简称金鸣识别&#xff09;网站…

软件测试/测试开发丨Python自动化测试学习笔记

1. 引言 自动化测试是软件开发中的关键环节&#xff0c;它可以提高测试效率、减少重复工作&#xff0c;并提供更快速、稳定的测试结果。Python作为一种易学易用的编程语言&#xff0c;为自动化测试提供了强大的工具和库。本文将介绍如何使用Python进行自动化测试。 2. 安装Py…

如何将弹性公网IP绑定到负载均衡CLB

创建的CLB实例为私网类型&#xff0c;没有公网IP&#xff0c;无法通过公网访问&#xff0c;如果需要让其网站能够通过公网访问&#xff0c;只需要绑定前面创建的EIP即可。 第一步 如果弹性公网IP已经绑定了资源&#xff0c;需要先解绑 第二步 将私网CLB实例绑定到弹性公网IP …

蓝牙物联网智能安防系统设计方案

1概述 安防系统(安全防护)的作用是预防损失&#xff0c;是人们保障人身和财产安全最重要的工具之一。近年来&#xff0c;伴随经济的飞速发展和城市人口的急剧增加&#xff0c;盗窃、入室抢劫等事件的增多给人们的安定生活带来了很大的影响&#xff0c;同时&#xff0c;交通的快…

[OCR]Python 3 下的文字识别CnOCR

目录 1 CnOCR 2 安装 3 实践 1 CnOCR CnOCR 是 Python 3 下的文字识别&#xff08;Optical Character Recognition&#xff0c;简称OCR&#xff09;工具包。 工具包支持简体中文、繁体中文&#xff08;部分模型&#xff09;、英文和数字的常见字符识别&#xff0c;支持竖…

如何实现内部产品权限集成

当前我国各领域正在加速向数字化、移动化、智能化发展&#xff0c;大力投入信息化建设与数字化转型已成为企业的共识&#xff0c;而企业门户系统是企业信息化系统建设是一个重要支撑&#xff0c;以企业业务系统为基础&#xff0c;搭建门户系统作为统一入口和应用中心可以有效支…

通过Python将PDF转为文本,快速提取PDF中的文字

快速高效地从PDF文档中提取信息对于专业人士来说非常重要。处理大量PDF文件时&#xff0c;将PDF转换为可编辑的文本格式可以节省时间和精力。而强大的Python语言正是在这些方面发挥其作用。利用Python中丰富的API&#xff0c;我们可以轻松在Python程序中将PDF转换为文本&#x…

YOLOv5改进 | 2023注意力篇 | BiFormer双层路由注意力机制(Bi-level Routing Attention)

一、本文介绍 BiFormer是一种结合了Bi-level Routing Attention的视觉Transformer模型&#xff0c;BiFormer模型的核心思想是引入了双层路由注意力机制。在BiFormer中&#xff0c;每个图像块都与一个位置路由器相关联。这些位置路由器根据特定的规则将图像块分配给上层和下层路…

重装系统以后无法git跟踪

总结&#xff1a;权限问题 故障定位 解决方案&#xff1a; 复制一份新的文件夹。&#xff08;新建的文件创建和写入权限都变了&#xff09; 修改文件为新的用户 执行提示的命令

【iOS安全】越狱iOS安装Frida | 安装指定版本Frida

越狱iPhone安装Frida 本文的方法适用于已越狱的iPhone手机 打开Cydia&#xff0c;软件源&#xff0c;编辑&#xff08;右上角&#xff09;&#xff0c;添加&#xff08;左上角&#xff09;&#xff1a;https://build.frida.re 然后搜索Frida&#xff0c;点击安装 参考&#x…

gzip引入后node_modules中.cache compression-webpack-plugin占用内存过多

1.Gzip Gzip&#xff08;GNU zip&#xff09;是一种常见的文件压缩格式和压缩算法&#xff0c;通常用于在 Web 服务器上对静态资源文件进行压缩&#xff0c;以减小文件大小并加快文件传输速度。在前端开发中&#xff0c;经常会使用 Gzip 压缩来优化网站的性能。 Gzip 压缩通过…

3D动态路障生成

3D动态路障生成 介绍设计实现1.路面创建2.空物体的创建3.Create.cs脚本创建 总结 介绍 上一篇文章介绍了Mathf.Lerp的底层实现原理&#xff0c;这里介绍一下跑酷类游戏的动态路障生成是如何实现的。 动态路障其实比较好生成&#xff0c;但是难点在哪里&#xff0c;如果都是平面…

18-网络安全框架及模型-信息系统安全保障模型

信息系统安全保障模型 1 基本概念 信息系统安全保障是针对信息系统在运行环境中所面临的各种风险&#xff0c;制定信息系统安全保障策略&#xff0c;设计并实现信息系统安全保障架构或模型&#xff0c;采取工程、技术、管理等安全保障要素&#xff0c;将风险减少至预定可接受的…

详解Vue3中的表单验证

本文主要介绍Vue3中的表单验证。 目录 普通语法setup语法注意事项 在Vue3中&#xff0c;表单验证可以使用Vue的内置指令v-model和自定义指令来实现。 普通语法 下面是一个详细介绍Vue3中表单验证的步骤&#xff1a; 创建Vue实例&#xff0c;并定义需要验证的表单数据。 imp…

网页在不同Android机表现有差异时需要排查页面样式是否针对主题模式作配置

前言 如题&#xff0c;这周有跟进一个BUG&#xff0c;后来分析出是跟手机主题模式有关。 bug情报&#xff1a;测试小年青那边提供的情报是我们的一个业务报告页面在某台华为手机上&#xff0c;页面列表项的文字颜色异常&#xff08;正常是显示黑色&#xff09;还有文字的背景显…

Springboot整合Elastic-job

一 概述 Elastic-Job 最开始只有一个 elastic-job-core 的项目&#xff0c;定位轻量级、无中心化&#xff0c;最核心的服务就是支持弹性扩容和数据分片&#xff01;从 2.X 版本以后&#xff0c;主要分为 Elastic-Job-Lite 和 Elastic-Job-Cloud 两个子项目。esjbo官网地址 Ela…