作為一個(gè)iOS
開發(fā)者,如何從OC
過渡到Swift
.今天我們就來講解一下從OC
開發(fā)轉(zhuǎn)到Swift
開發(fā)的注意點(diǎn).
一: 條件編譯
有時(shí)候我們要限制我們的代碼在某些平臺(tái),某種架構(gòu),某一個(gè)語言版本下運(yùn)行,這時(shí)候就用到了條件編譯.
swift
中的條件編譯和OC
中的一樣:
#if os(macOS) || os(iOS)
print("在macOS 或者 iOS 平臺(tái)下執(zhí)行")
#elseif arch(x86_64) || arch(arm64)
print("x86 或者 arm64 架構(gòu)下執(zhí)行")
#elseif swift(>=5.0)
print("swift 版本要大于等于 5.0")
#elseif targetEnvironment(simulator)
print("在模擬器下執(zhí)行")
#elseif canImport(Foundation)
print("如果能導(dǎo)入Foundation模塊就執(zhí)行")
#endif
debug , release
條件編譯:
#if DEBUG
print("debug 模式下執(zhí)行此代碼")
#else
print("release 模式下執(zhí)行此代碼")
#endif
我們也可以自定義DEBUG
標(biāo)簽:
#if MYDEBUG
print("DEBUG 模式下執(zhí)行此代碼")
#endif
#if TESTDEBUG
print(...)
#endif
二: 打印
在OC
開發(fā)中,我們會(huì)使用宏定義讓NSLog
在Debug
模式下有效,在Release
模式下無效,比如這樣:
#if DEBUG
#define DLOG(fmt, ...) NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);
#else
#define XLOG(fmt,...) {}
#endif
但是在swift
中不支持宏定義,我們?nèi)绾螌?shí)現(xiàn)呢?
我們可以寫一個(gè)方法,讓其在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, *){
//對(duì)于iOS平臺(tái),只在iOS9及以上版本執(zhí)行
//對(duì)于macOS平臺(tái),只在macOS10及以上版本執(zhí)行
//*表示支持其他所有平臺(tái)
}
@available(iOS 10.0,macOS 10.0,*)
class Person{
//將run改名為fastRun
@available(*,unavailable,renamed: "fastRun")
func run(){
}
func fastRun(){
}
//從iOS 10,macOS 11 開始已經(jīng)棄用此方法
@available(iOS,deprecated: 10)
@available(macOS,deprecated: 11)
func eat(){
}
}
四: swift 調(diào)用 OC
要想在swift
項(xiàng)目中調(diào)用OC
代碼,需要三個(gè)步驟:
- 創(chuàng)建一個(gè)文件名為
{targetName}-Bridging-Header.h
的橋接文件.targetName
就是你的工程名.
- 在
Build Settings -> Objective-C Bridging Header
中寫明橋接文件路徑.
- 在橋接文件中導(dǎo)入
OC
暴露給Swift
的類.
做好以上三步后,就可以在swift
中調(diào)用OC
類了.
如上圖所示,現(xiàn)在已經(jīng)可以在swift
中調(diào)用OC
類的方法了.
需要注意的是, 在OC
類中有一個(gè)-
初始化方法和一個(gè)+
初始化方法,但是在swift
中調(diào)用初始化方法只會(huì)調(diào)用-
的.不會(huì)調(diào)用+
的.即使把OC
中的-
初始化方法聲明注釋掉,swift
也不會(huì)調(diào)用+
初始化方法.
五: @_silgen_name( )
如果有的C語言函數(shù)沒有暴露給我們,但是我們又想調(diào)用,可以使用@_silgen_name( )
給這個(gè)方法重命名,然后再調(diào)用.比如OC
的Person.m
中有一個(gè)sum
方法,沒有在.h
頭文件中暴露給外界,我們?nèi)匀豢梢哉{(diào)用它:
@_silgen_name( )
很有用,系統(tǒng)沒有暴露的C語言方法都可以通過它來調(diào)用.但是注意只能是C語言方法.
六: OC 調(diào)用 Swift
如果想在OC
文件中調(diào)用Swift
的內(nèi)容,同樣也需要一個(gè)橋接文件.這個(gè)橋接文件的命名方式為{targetName-Swift.h}
,但是這個(gè)橋接文件不需要我們創(chuàng)建,Xcode
默認(rèn)已經(jīng)創(chuàng)建好了.
要想在OC
中成功調(diào)用Swift
內(nèi)容,需要2步:
Swift
暴露給OC
的類最終要繼承NSObject
要暴露給
OC
的成員需要用@objc
修飾;或者使用@objcMembers
修飾類,代表所有成員都暴露給OC
.
做完以上兩步后,我們就可以在OC
類中調(diào)用Swift
的東西了:
我們?cè)谧鐾暌陨蟽刹胶?Xcode
會(huì)把Swift
代碼生成對(duì)應(yīng)的OC聲明
,寫入到{targetName-Swift.h}
文件中:
到目前為止我們已經(jīng)可以在OC
與Swift
之間互相調(diào)用了.我們知道OC
調(diào)用方法是通過runtime
,Swift
調(diào)用方法是通過函數(shù)虛表.那我們?cè)?code>OC中調(diào)用Swift
,或者在Swift
中調(diào)用OC
.到底走的是哪一套流程呢?
6.1: OC 調(diào)用 Swift
在OC
中調(diào)用Swift
方法,看看其匯編底層:
可以看到,在OC
中調(diào)用Swift
方法,其底層走的是runtime
的那一套流程.
6.2 : Swift 調(diào)用 OC
匯編底層:
6.3 : Swift 類繼承自 NSObject,但是在 Swift 文件中調(diào)用
以上兩種情況,不管是Swift
調(diào)用OC
,還是OC
調(diào)用Swift
方法,底層都是走runtime
機(jī)制.
如果是Swift
代碼繼承自NSObject
,暴露給OC
.但是是在Swift
文件中調(diào)用的,這時(shí)候會(huì)走哪套流程呢?
匯編底層如下:
可以看到,即使繼承自NSObject
,但是在Swift
中調(diào)用Swift
自己的方法,仍然走的是方法虛表流程.
在OC
開發(fā)中,類名方法名經(jīng)常會(huì)使用一些前綴.而Swift
編碼沒有這些規(guī)范要求.所以如果我們?cè)?code>OC中調(diào)用Swift
.可以使用@objc
重命名Swift
暴露給OC
的類名,方法名,屬性名:
七: 選擇器( Selector )
Swift
也可以使用方法選擇器,但是有兩個(gè)前提:
必須是繼承自 NSObject 的類
必須是被 @objc 或者 @objcMembers 修飾的方法才可以定義選擇器
如圖:
八: String
Swift
中的String
和OC
的NSString
在API
的設(shè)計(jì)上有很大的差異.
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
中對(duì)字符串進(jìn)行插入操作是通過insertString:(nonnull NSString *) atIndex:(NSUInteger)
,傳入一個(gè)索引下標(biāo).而在Swift
中,String
引入了一個(gè)內(nèi)部類型String.Index
.
下面我們就好好認(rèn)識(shí)一下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)
//插入到第一個(gè)字符
str.insert(contentsOf: "ok", at: str.index(after: str.startIndex))
//插入到最后一個(gè)字符前面
str.insert(contentsOf: "666", at: str.index(before: str.endIndex))
//插入到從開始位置偏移4
str.insert(contentsOf: "888", at: str.index(str.startIndex, offsetBy: 4))
刪除操作:
//刪除第一個(gè)6
str.remove(at: str.firstIndex(of: "6")!)
//刪除所有的6
str.removeAll {(c) -> Bool in
c == "6"
}
print(str)
//刪除一個(gè)區(qū)間范圍的字符
let rang = str.index(str.startIndex, offsetBy: 4) ..< str.index(str.endIndex, offsetBy: -2)
str.removeSubrange(rang)
截取字符串:
var str = "123456789"
//刪除前3個(gè)字符
var subStr1 = str.prefix(3)
print(subStr1)
//刪除后3個(gè)字符
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
獲取獲取原來的字符串.
如果沒有對(duì)Substring
進(jìn)行修改或者轉(zhuǎn)換為String
類型,那么Substring
和它的base
共享同一塊內(nèi)存數(shù)據(jù).
8.3 多行字符串
在Swift
中可以用"""
來定義多行字符串:
注意:多行字符串的作用域以最后一個(gè)"""
為準(zhǔn),字符串內(nèi)容不能越過最后一個(gè)"""
的左邊界:
8.4 String 與 NSString
String
和NSString
可以互相轉(zhuǎn)換:
var str1: String = "good"
var str2: NSString = "better"
var str3 = str1 as NSString
var str4 = str2 as String
判斷兩個(gè)字符串內(nèi)容是否相同,可以使用==
運(yùn)算符,也可以使用isEqual
方法.
如果是OC
的NSString
使用==
判斷兩個(gè)字符串是否相同,它們的本質(zhì)還是調(diào)用isEqual
方法:
Swfit
的String
使用==
判斷是否相等,觀察匯編語言沒有發(fā)現(xiàn)調(diào)用isEqual
方法.
需要注意的是String
可以和NSString
互相橋接轉(zhuǎn)換.
但是String
不可以和NSMutableString
互相轉(zhuǎn)換,具體的說就是NSMutableString
可以轉(zhuǎn)換為String
;而String
不可以轉(zhuǎn)換為NSMutableString
九: 只能被類遵守的協(xié)議
有時(shí)候在開發(fā)中,我們想讓一個(gè)協(xié)議只能被類遵守,不能被結(jié)構(gòu)體和枚舉遵守.有三種方式方法可以達(dá)到這種效果.
//AnyObject
protocol Setable1: AnyObject{
}
//class
protocol Setable2: class{
}
//@objc
@objc protocol Setable3{
}
被@objc
修飾的協(xié)議還暴露給OC
遵守.
十: 可選協(xié)議
之前我們講協(xié)議的時(shí)候說過,可以通過擴(kuò)展為協(xié)議添加默認(rèn)實(shí)現(xiàn),從而達(dá)到可選協(xié)議的效果:
現(xiàn)在又多了一種方法,通過@objc
定義可選協(xié)議,并且這種協(xié)議只能被類遵守:
十一: @objc dynamic
@objc dynamic
修飾的內(nèi)容會(huì)具有動(dòng)態(tài)性,比如說objc dynamic
如果修飾Swift
方法,那么即使在Swift
文件內(nèi)調(diào)用Swift
方法,仍然會(huì)走runtime
那一套流程.
十二: KVC,KVO
在swift
開發(fā)中依然可以使用KVC , KVO
,只不過不能像在OC
中那樣直接使用,必須滿足兩個(gè)條件:
- 屬性所在的類,監(jiān)聽器必須繼承自
NSObject
- 用
@objc dynamic
修飾需要監(jiān)聽的屬性.
class Observer: NSObject{
override func observeValue(forKeyPath keyPath: String?,
of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {
print("對(duì)象:\(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")
上一種方法需要?jiǎng)?chuàng)建一個(gè)繼承自NSObject
的Observer
類,還有一種block
方式實(shí)現(xiàn)KVO
不需要?jiǎng)?chuàng)建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
十三: 關(guān)聯(lián)對(duì)象
我們知道通過擴(kuò)展是不能給類添加存儲(chǔ)屬性的,因?yàn)榇鎯?chǔ)屬性保存在類的實(shí)例中.添加存儲(chǔ)屬性會(huì)影響到類的內(nèi)存結(jié)構(gòu).
如果我們想要實(shí)現(xiàn)動(dòng)態(tài)的給一個(gè)類添加存儲(chǔ)屬性,可以和OC
一樣使用關(guān)聯(lián)對(duì)象
:
十四: 資源名統(tǒng)一管理
我們?cè)陂_發(fā)中會(huì)用到很多的圖片資源,按鈕標(biāo)題,提示語,字體樣式等等.在OC
開發(fā)中通常會(huì)用宏定義文件把經(jīng)常需要的文件,文案宏定義一下.這樣我們?cè)谇么a的時(shí)候會(huì)有提示,并且以后修改的話只需要修改宏定義文件即可.
那么在Swift
中不支持宏定義.我們?cè)趺磳?shí)現(xiàn)這種效果呢?
我們可以像下面這樣,使用枚舉,把我們用到的文案列舉出來:
然后再調(diào)用我們自己擴(kuò)展的方法:
像上面的方法還繞了一個(gè)彎,我們可以更加直接點(diǎn),直接在枚舉中返回我們想要的東西:
十五: 多線程
Swift
中的多線程于OC
中的大同小異.
1: 異步
DispatchQueue.global().async {
//子線程異步
print("1 - ",Thread.current)
//回到主線程
DispatchQueue.main.async {
print("2 - ",Thread.current)
}
}
可以封裝成工具類,直接把任務(wù)傳入進(jìn)去:
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)
}
//可以接收異步線程,和主線程任務(wù)
private static func _sync(_ task: @escaping Task, _ mainTask: Task? = nil){
//創(chuàng)建item
let item = DispatchWorkItem(block: task)
//異步子線程執(zhí)行item
DispatchQueue.global().async(execute: item)
if let main = mainTask{
item.notify(queue: DispatchQueue.main, execute: main)
}
}
}
2: 延遲執(zhí)行
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: 多線程開發(fā) once
swift
中廢棄了dispatch_once
.所以要想實(shí)現(xiàn)單例,只能通過全局變量或者靜態(tài)變量來實(shí)現(xiàn).因?yàn)殪o態(tài)變量和全局變量在內(nèi)存中只有一份,并且只會(huì)初始化一次,還是懶加載,用到的時(shí)候才初始化.
static var once: Bool = {
print("1")
return true
}()