总结
作为一个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开发中,我们会使用宏定义让NSLog在Debug模式下有效,在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代码,需要三个步骤:
- 创建一个文件名为
{targetName}-Bridging-Header.h的桥接文件.targetName就是你的工程名.
targetName
- 在
Build Settings -> Objective-C Bridging Header中写明桥接文件路径.
桥接文件路径
- 在桥接文件中导入
OC暴露给Swift的类.
导入用到的类
做好以上三步后,就可以在swift中调用OC类了.
OC类
在swift中调用OC
如上图所示,现在已经可以在swift中调用OC类的方法了.
需要注意的是, 在OC类中有一个-初始化方法和一个+初始化方法,但是在swift中调用初始化方法只会调用-的.不会调用+的.即使把OC中的-初始化方法声明注释掉,swift也不会调用+初始化方法.
五: @_silgen_name( )
如果有的C语言函数没有暴露给我们,但是我们又想调用,可以使用@_silgen_name( )给这个方法重命名,然后再调用.比如OC的Person.m中有一个sum方法,没有在.h头文件中暴露给外界,我们仍然可以调用它:
.m文件中没有暴露的方法
在 swift 中调用
@_silgen_name( )很有用,系统没有暴露的C语言方法都可以通过它来调用.但是注意只能是C语言方法.
六: OC 调用 Swift
如果想在OC文件中调用Swift的内容,同样也需要一个桥接文件.这个桥接文件的命名方式为{targetName-Swift.h},但是这个桥接文件不需要我们创建,Xcode默认已经创建好了.
要想在OC中成功调用Swift内容,需要2步:
-
Swift暴露给OC的类最终要继承NSObject -
要暴露给
OC的成员需要用@objc修饰;或者使用objcMembers修饰类,代表所有成员都暴露给OC.
swift类暴露给OC
做完以上两步后,我们就可以在OC类中调用Swift的东西了:
OC中调用Swift
我们在做完以上两步后,Xcode会把Swift代码生成对应的OC声明,写入到{targetName-Swift.h}文件中:
swift代码的OC声明
到目前为止我们已经可以在OC与Swift之间互相调用了.我们知道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也可以使用方法选择器,但是有两个前提:
必须是继承自 NSObject 的类必须是被 @objc 或者 @objcMembers 修饰的方法才可以定义选择器
如图:
八: String
Swift中的String和OC的NSString在API的设计上有很大的差异.
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
String和NSString可以互相转换:
var str1: String = "good"
var str2: NSString = "better"
var str3 = str1 as NSString
var str4 = str2 as String
判断两个字符串内容是否相同,可以使用==运算符,也可以使用isEqual方法.
如果是OC的NSString使用==判断两个字符串是否相同,它们的本质还是调用isEqual方法:
Swfit的String使用==判断是否相等,观察汇编语言没有发现调用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中那样直接使用,必须满足两个条件:
- 属性所在的类,监听器必须继承自
NSObject - 用
@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")
上一种方法需要创建一个继承自NSObject的Observer类,还有一种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












网友评论