草庐IT

从零学习Swift 15: 从OC到Swift过渡

小心韩国人 2023-09-21 原文
总结

作为一个iOS开发者,如何从OC过渡到Swift.今天我们就来讲解一下从OC开发转到Swift开发的注意点.

一: 条件编译

有时候我们要限制我们的代码在某些平台,某种架构,某一个语言版本下运行,这时候就用到了条件编译.

swift中的条件编译和OC中的一样:


#if os(macOS) || os(iOS)
print("在macOS 或者 iOS 平台下执行")
#elseif arch(x86_64) || arch(arm64)
print("x86 或者 arm64 架构下执行")
#elseif swift(>=5.0)
print("swift 版本要大于等于 5.0")
#elseif targetEnvironment(simulator)
print("在模拟器下执行")
#elseif canImport(Foundation)
print("如果能导入Foundation模块就执行")
#endif

debug , release条件编译:


#if DEBUG
print("debug 模式下执行此代码")
#else
print("release 模式下执行此代码")
#endif

我们也可以自定义DEBUG标签:

自定义DEBUG标签

#if MYDEBUG
print("DEBUG 模式下执行此代码")
#endif

#if TESTDEBUG
print(...)
#endif

二: 打印

OC开发中,我们会使用宏定义让NSLogDebug模式下有效,在Release模式下无效,比如这样:


#if DEBUG
#define DLOG(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);

#else
#define XLOG(fmt,...) {}

#endif

但是在swift中不支持宏定义,我们如何实现呢?

我们可以写一个方法,让其在Debug模式下打印,Release模式下不做任何事情:


func mylog<T>(_ msg: T,
            file: NSString = #file,
            line: Int = #line,
            fn: String = #function){
    #if DEBUG
    let str = "\(file.lastPathComponent)_ \(line) _ \(fn) _ \(msg)"
    print(str)
    #endif
}

三: API可用性说明

if #available(iOS 9.0,macOS 10.0, *){
    //对于iOS平台,只在iOS9及以上版本执行
    //对于macOS平台,只在macOS10及以上版本执行
    //*表示支持其他所有平台
}


@available(iOS 10.0,macOS 10.0,*)
class Person{
    //将run改名为fastRun
    @available(*,unavailable,renamed: "fastRun")
    func run(){
    }
    func fastRun(){
    }
    
    //从iOS 10,macOS 11 开始已经弃用此方法
    @available(iOS,deprecated: 10)
    @available(macOS,deprecated: 11)
    func eat(){
    }
}

四: swift 调用 OC

要想在swift项目中调用OC代码,需要三个步骤:

  1. 创建一个文件名为{targetName}-Bridging-Header.h的桥接文件.targetName就是你的工程名.
targetName
  1. Build Settings -> Objective-C Bridging Header中写明桥接文件路径.
桥接文件路径
  1. 在桥接文件中导入OC暴露给Swift的类.
导入用到的类

做好以上三步后,就可以在swift中调用OC类了.

OC类
在swift中调用OC

如上图所示,现在已经可以在swift中调用OC类的方法了.

需要注意的是, 在OC类中有一个-初始化方法和一个+初始化方法,但是在swift中调用初始化方法只会调用-的.不会调用+的.即使把OC中的-初始化方法声明注释掉,swift也不会调用+初始化方法.

五: @_silgen_name( )

如果有的C语言函数没有暴露给我们,但是我们又想调用,可以使用@_silgen_name( )给这个方法重命名,然后再调用.比如OCPerson.m中有一个sum方法,没有在.h头文件中暴露给外界,我们仍然可以调用它:

.m文件中没有暴露的方法
在 swift 中调用

@_silgen_name( )很有用,系统没有暴露的C语言方法都可以通过它来调用.但是注意只能是C语言方法.

六: OC 调用 Swift

如果想在OC文件中调用Swift的内容,同样也需要一个桥接文件.这个桥接文件的命名方式为{targetName-Swift.h},但是这个桥接文件不需要我们创建,Xcode默认已经创建好了.

要想在OC中成功调用Swift内容,需要2步:

  1. Swift暴露给OC的类最终要继承NSObject

  2. 要暴露给OC的成员需要用@objc修饰;或者使用objcMembers修饰类,代表所有成员都暴露给OC.

swift类暴露给OC

做完以上两步后,我们就可以在OC类中调用Swift的东西了:

OC中调用Swift

我们在做完以上两步后,Xcode会把Swift代码生成对应的OC声明,写入到{targetName-Swift.h}文件中:

swift代码的OC声明

到目前为止我们已经可以在OCSwift之间互相调用了.我们知道OC调用方法是通过runtime,Swift调用方法是通过函数虚表.那我们在OC中调用Swift,或者在Swift中调用OC.到底走的是哪一套流程呢?

6.1: OC 调用 Swift

OC中调用Swift方法,看看其汇编底层:

可以看到,在OC中调用Swift方法,其底层走的是runtime的那一套流程.

6.2 : Swift 调用 OC

swift调用OC

汇编底层:

底层走的还是runtime机制

6.3 : Swift 类继承自 NSObject,但是在 Swift 文件中调用

以上两种情况,不管是Swift调用OC,还是OC调用Swift方法,底层都是走runtime机制.

如果是Swift代码继承自NSObject,暴露给OC.但是是在Swift文件中调用的,这时候会走哪套流程呢?

在swift中调用暴露给OC的swift方法

汇编底层如下:

可以看到,即使继承自NSObject,但是在Swift中调用Swift自己的方法,仍然走的是方法虚表流程.

OC开发中,类名方法名经常会使用一些前缀.而Swift编码没有这些规范要求.所以如果我们在OC中调用Swift.可以使用@objc重命名Swift暴露给OC的类名,方法名,属性名:

七: 选择器( Selector )

Swift也可以使用方法选择器,但是有两个前提:

  1. 必须是继承自 NSObject 的类
  2. 必须是被 @objc 或者 @objcMembers 修饰的方法才可以定义选择器

如图:

八: String

Swift中的StringOCNSStringAPI的设计上有很大的差异.

8.1 String 的拼接

Swift字符串的拼接很活,有很多拼接方法:


var str = "1"
//append拼接
str.append("_2")
//重载 +
str = str + "_3"
//重载 +=
str += "_4"
//插值
str = "\(str)_5"
print(str)
//长度
print(str.count)


8.2 插入和删除

OC中对字符串进行插入操作是通过insertString:(nonnull NSString *) atIndex:(NSUInteger),传入一个索引下标.而在Swift中,String引入了一个内部类型String.Index.

下面我们就好好认识一下String.Index

插入操作:

var str = "abc"
//str.startIndex : 在a的位置插入
str.insert(contentsOf: "1", at: str.startIndex)
//str.endIndex : 在c后面插入
str.insert(contentsOf: "2", at: str.endIndex)
//插入到第一个字符
str.insert(contentsOf: "ok", at: str.index(after: str.startIndex))
//插入到最后一个字符前面
str.insert(contentsOf: "666", at: str.index(before: str.endIndex))
//插入到从开始位置偏移4
str.insert(contentsOf: "888", at: str.index(str.startIndex, offsetBy: 4))

删除操作:


//删除第一个6
str.remove(at: str.firstIndex(of: "6")!)

//删除所有的6
str.removeAll {(c) -> Bool in
    c == "6"
}
print(str)
//删除一个区间范围的字符
let rang = str.index(str.startIndex, offsetBy: 4) ..< str.index(str.endIndex, offsetBy: -2)
str.removeSubrange(rang)


截取字符串:


var str = "123456789"
//删除前3个字符
var subStr1 = str.prefix(3)
print(subStr1)
//删除后3个字符
var subStr2 = str.suffix(3)
print(subStr2)
let range = str.index(str.startIndex, offsetBy: 3) ..< str.index(str.endIndex, offsetBy: -3)
var subStr3 = str[range]
print(subStr3)


String截取字符串返回的是Substring类型,并不是String类型.Substring可以通过base获取获取原来的字符串.

如果没有对Substring进行修改或者转换为String类型,那么Substring和它的base共享同一块内存数据.

8.3 多行字符串
Swift中可以用"""来定义多行字符串:

多行字符串

注意:多行字符串的作用域以最后一个"""为准,字符串内容不能越过最后一个"""的左边界:

8.4 String 与 NSString

StringNSString可以互相转换:


var str1: String = "good"
var str2: NSString = "better"

var str3 = str1 as NSString
var str4 = str2 as String

判断两个字符串内容是否相同,可以使用==运算符,也可以使用isEqual方法.

如果是OCNSString使用==判断两个字符串是否相同,它们的本质还是调用isEqual方法:

SwfitString使用==判断是否相等,观察汇编语言没有发现调用isEqual方法.

需要注意的是String可以和NSString互相桥接转换.
但是String不可以和NSMutableString互相转换,具体的说就是NSMutableString可以转换为String;而String不可以转换为NSMutableString

String 不可以通过 as 转换为 NSMutableString

九: 只能被类遵守的协议

有时候在开发中,我们想让一个协议只能被类遵守,不能被结构体和枚举遵守.有三种方式方法可以达到这种效果.


//AnyObject
protocol Setable1: AnyObject{
    
}
//class
protocol Setable2: class{
    
}
//@objc
@objc protocol Setable3{
    
}

@objc修饰的协议还暴露给OC遵守.

十: 可选协议

之前我们讲协议的时候说过,可以通过扩展为协议添加默认实现,从而达到可选协议的效果:

通过扩展实现可选协议

现在又多了一种方法,通过@objc定义可选协议,并且这种协议只能被类遵守:

@objc 实现可选协议

十一: @objc dynamic
@objc dynamic修饰的内容会具有动态性,比如说objc dynamic如果修饰Swift方法,那么即使在Swift文件内调用Swift方法,仍然会走runtime那一套流程.

@objc dynamic
走runtime机制

十二: KVC,KVO

swift开发中依然可以使用KVC , KVO,只不过不能像在OC中那样直接使用,必须满足两个条件:

  1. 属性所在的类,监听器必须继承自NSObject
  2. @objc dynamic修饰需要监听的属性.

class Observer: NSObject{
    
    override func observeValue(forKeyPath keyPath: String?,
                                     of object: Any?,
                                     change: [NSKeyValueChangeKey : Any]?,
                                     context: UnsafeMutableRawPointer?) {
        print("对象:\(String(describing: object)),属性:\(String(describing: keyPath)),新值:\(String(describing: change?[.newKey]))")
    }
}

class Teacher: NSObject{
    var name: String = ""
    @objc dynamic var age: Int = 3

    var observer: Observer = Observer()
    override init() {
        super.init()
        self.addObserver(observer, forKeyPath: "age", options: .new, context: nil)
        
    }

    deinit {
        self.removeObserver(observer, forKeyPath: "age")
    }

}

var teacher = Teacher()
teacher.age = 20
teacher.setValue(30, forKey: "age")

上一种方法需要创建一个继承自NSObjectObserver类,还有一种block方式实现KVO不需要创建Observer类:


class Teacher: NSObject{
    var name: String = ""
    @objc dynamic var age: Int = 3

    var observation: NSKeyValueObservation?
    override init() {
        super.init()
        
        observation = observe(\Teacher.age, options: .new) {
            (person, change) in
            print(change.newValue ?? 0)
        }
        
    }
}

var teacher = Teacher()
teacher.age = 35

第二种方式写法上要注意,要观察的类的属性书写格式为\Teacher.age

十三: 关联对象

我们知道通过扩展是不能给类添加存储属性的,因为存储属性保存在类的实例中.添加存储属性会影响到类的内存结构.

如果我们想要实现动态的给一个类添加存储属性,可以和OC一样使用关联对象:

十四: 资源名统一管理

我们在开发中会用到很多的图片资源,按钮标题,提示语,字体样式等等.在OC开发中通常会用宏定义文件把经常需要的文件,文案宏定义一下.这样我们在敲代码的时候会有提示,并且以后修改的话只需要修改宏定义文件即可.

那么在Swift中不支持宏定义.我们怎么实现这种效果呢?

我们可以像下面这样,使用枚举,把我们用到的文案列举出来:

使用枚举列举出文案

然后再调用我们自己扩展的方法:

调用自己扩展的方法

像上面的方法还绕了一个弯,我们可以更加直接点,直接在枚举中返回我们想要的东西:

直接返回需要的东西
直接使用

十五: 多线程

Swift中的多线程于OC中的大同小异.

1: 异步


        DispatchQueue.global().async {
            //子线程异步
            print("1 - ",Thread.current)

            //回到主线程
            DispatchQueue.main.async {
                print("2 - ",Thread.current)
            }
        }

可以封装成工具类,直接把任务传入进去:


typealias Task = () -> Void

struct Async{
    
    //在子线程中处理
    static func sync(_ task: @escaping Task){
        _sync(task)
    }
    
    //在子线程中处理完成后,回到主线程处理
    static func sync(_ task: @escaping Task, _ mainTask: @escaping Task){
        _sync(task, mainTask)
    }
    
    
    
    //可以接收异步线程,和主线程任务
    private static func _sync(_ task: @escaping Task, _ mainTask: Task? = nil){
        
        //创建item
        let item = DispatchWorkItem(block: task)
        //异步子线程执行item
        DispatchQueue.global().async(execute: item)
        
        if let main = mainTask{
            item.notify(queue: DispatchQueue.main, execute: main)
        }
    }
}

2: 延迟执行


    static func asyncDeleay(_ seconds: Double,
                     _ task: @escaping Task) -> DispatchWorkItem{
        _deleay(seconds, task)
    }
    
    static func asyncDeleay(_ seconds: Double,
                            _ task: @escaping Task,
                            _ mainTask: @escaping Task) -> DispatchWorkItem{
        _deleay(seconds, task, mainTask)
    }
    
    
    private static func _deleay(_ seconds: Double,
                                _ task: @escaping Task,
                                _ mainTask: Task? = nil) -> DispatchWorkItem{
        let time = DispatchTime.now() + seconds
        let item = DispatchWorkItem(block: task)
        DispatchQueue.global().asyncAfter(deadline: time, execute: item)
        if let main = mainTask{
            item.notify(queue: DispatchQueue.main, execute: main)
        }
        return item
    }

3: 多线程开发 once

swift中废弃了dispatch_once.所以要想实现单例,只能通过全局变量或者静态变量来实现.因为静态变量和全局变量在内存中只有一份,并且只会初始化一次,还是懒加载,用到的时候才初始化.


    static var once: Bool = {
        print("1")
        return true
    }()

全局变量和静态变量底层其实调用的是 dispatch_once

有关从零学习Swift 15: 从OC到Swift过渡的更多相关文章

  1. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  2. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  3. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  4. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  5. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  6. ruby - 我如何学习 ruby​​ 的正则表达式? - 2

    如何学习ruby​​的正则表达式?(对于假人) 最佳答案 http://www.rubular.com/在Ruby中使用正则表达式时是一个很棒的工具,因为它可以立即将结果可视化。 关于ruby-我如何学习ruby​​的正则表达式?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/1881231/

  7. 深度学习12. CNN经典网络 VGG16 - 2

    深度学习12.CNN经典网络VGG16一、简介1.VGG来源2.VGG分类3.不同模型的参数数量4.3x3卷积核的好处5.关于学习率调度6.批归一化二、VGG16层分析1.层划分2.参数展开过程图解3.参数传递示例4.VGG16各层参数数量三、代码分析1.VGG16模型定义2.训练3.测试一、简介1.VGG来源VGG(VisualGeometryGroup)是一个视觉几何组在2014年提出的深度卷积神经网络架构。VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG

  8. 机器学习——时间序列ARIMA模型(四):自相关函数ACF和偏自相关函数PACF用于判断ARIMA模型中p、q参数取值 - 2

    文章目录1、自相关函数ACF2、偏自相关函数PACF3、ARIMA(p,d,q)的阶数判断4、代码实现1、引入所需依赖2、数据读取与处理3、一阶差分与绘图4、ACF5、PACF1、自相关函数ACF自相关函数反映了同一序列在不同时序的取值之间的相关性。公式:ACF(k)=ρk=Cov(yt,yt−k)Var(yt)ACF(k)=\rho_{k}=\frac{Cov(y_{t},y_{t-k})}{Var(y_{t})}ACF(k)=ρk​=Var(yt​)Cov(yt​,yt−k​)​其中分子用于求协方差矩阵,分母用于计算样本方差。求出的ACF值为[-1,1]。但对于一个平稳的AR模型,求出其滞

  9. Unity Shader 学习笔记(5)Shader变体、Shader属性定义技巧、自定义材质面板 - 2

    写在之前Shader变体、Shader属性定义技巧、自定义材质面板,这三个知识点任何一个单拿出来都是一套知识体系,不能一概而论,本文章目的在于将学习和实际工作中遇见的问题进行总结,类似于网络笔记之用,方便后续回顾查看,如有以偏概全、不祥不尽之处,还望海涵。1、Shader变体先看一段代码......Properties{ [KeywordEnum(on,off)]USL_USE_COL("IsUseColorMixTex?",int)=0 [Toggle(IS_RED_ON)]_IsRed("IsRed?",int)=0}......//中间省略,后续会有完整代码 #pragmamulti_c

  10. ruby-on-rails - 这个 C 和 PHP 程序员如何学习 Ruby 和 Rails? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我来自C、php和bash背景,很容易学习,因为它们都有相同的C结构,我可以将其与我已经知道的联系起来。然后2年前我学了Python并且学得很好,Python对我来说比Ruby更容易学。然后从去年开始,我一直在尝试学习Ruby,然后是Rails,我承认,直到现在我还是学不会,讽刺的是那些打着简单易学的烙印,但是对于我这样一个老练的程序员来说,我只是无法将它

随机推荐