從零學(xué)習(xí)Swift 15: 從OC到Swift過渡

總結(jié)

作為一個(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)簽:

自定義DEBUG標(biāo)簽

#if MYDEBUG
print("DEBUG 模式下執(zhí)行此代碼")
#endif

#if TESTDEBUG
print(...)
#endif

二: 打印

OC開發(fā)中,我們會(huì)使用宏定義讓NSLogDebug模式下有效,在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è)步驟:

  1. 創(chuàng)建一個(gè)文件名為{targetName}-Bridging-Header.h的橋接文件.targetName就是你的工程名.
targetName
  1. Build Settings -> Objective-C Bridging Header中寫明橋接文件路徑.
橋接文件路徑
  1. 在橋接文件中導(dǎo)入OC暴露給Swift的類.
導(dǎo)入用到的類

做好以上三步后,就可以在swift中調(diào)用OC類了.

OC類
在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)用.比如OCPerson.m中有一個(gè)sum方法,沒有在.h頭文件中暴露給外界,我們?nèi)匀豢梢哉{(diào)用它:

.m文件中沒有暴露的方法
在 swift 中調(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步:

  1. Swift暴露給OC的類最終要繼承NSObject

  2. 要暴露給OC的成員需要用@objc修飾;或者使用@objcMembers修飾類,代表所有成員都暴露給OC.

swift類暴露給OC

做完以上兩步后,我們就可以在OC類中調(diào)用Swift的東西了:

OC中調(diào)用Swift

我們?cè)谧鐾暌陨蟽刹胶?Xcode會(huì)把Swift代碼生成對(duì)應(yīng)的OC聲明,寫入到{targetName-Swift.h}文件中:

swift代碼的OC聲明

到目前為止我們已經(jīng)可以在OCSwift之間互相調(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

swift調(diào)用OC

匯編底層:

底層走的還是runtime機(jī)制

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ì)走哪套流程呢?

在swift中調(diào)用暴露給OC的swift方法

匯編底層如下:

可以看到,即使繼承自NSObject,但是在Swift中調(diào)用Swift自己的方法,仍然走的是方法虛表流程.

OC開發(fā)中,類名方法名經(jīng)常會(huì)使用一些前綴.而Swift編碼沒有這些規(guī)范要求.所以如果我們?cè)?code>OC中調(diào)用Swift.可以使用@objc重命名Swift暴露給OC的類名,方法名,屬性名:

七: 選擇器( Selector )

Swift也可以使用方法選擇器,但是有兩個(gè)前提:

  1. 必須是繼承自 NSObject 的類
  2. 必須是被 @objc 或者 @objcMembers 修飾的方法才可以定義選擇器

如圖:

八: String

Swift中的StringOCNSStringAPI的設(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

StringNSString可以互相轉(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方法.

如果是OCNSString使用==判斷兩個(gè)字符串是否相同,它們的本質(zhì)還是調(diào)用isEqual方法:

SwfitString使用==判斷是否相等,觀察匯編語言沒有發(fā)現(xiàn)調(diào)用isEqual方法.

需要注意的是String可以和NSString互相橋接轉(zhuǎn)換.
但是String不可以和NSMutableString互相轉(zhuǎn)換,具體的說就是NSMutableString可以轉(zhuǎn)換為String;而String不可以轉(zhuǎn)換為NSMutableString

String 不可以通過 as 轉(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é)議的效果:

通過擴(kuò)展實(shí)現(xiàn)可選協(xié)議

現(xiàn)在又多了一種方法,通過@objc定義可選協(xié)議,并且這種協(xié)議只能被類遵守:

@objc 實(shí)現(xiàn)可選協(xié)議

十一: @objc dynamic
@objc dynamic修飾的內(nèi)容會(huì)具有動(dòng)態(tài)性,比如說objc dynamic如果修飾Swift方法,那么即使在Swift文件內(nèi)調(diào)用Swift方法,仍然會(huì)走runtime那一套流程.

@objc dynamic
走runtime機(jī)制

十二: KVC,KVO

swift開發(fā)中依然可以使用KVC , KVO,只不過不能像在OC中那樣直接使用,必須滿足兩個(gè)條件:

  1. 屬性所在的類,監(jiān)聽器必須繼承自NSObject
  2. @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è)繼承自NSObjectObserver類,還有一種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ò)展的方法:

調(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
    }()

全局變量和靜態(tài)變量底層其實(shí)調(diào)用的是 dispatch_once
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末劝堪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子揉稚,更是在濱河造成了極大的恐慌,老刑警劉巖熬粗,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搀玖,死亡現(xiàn)場離奇詭異,居然都是意外死亡驻呐,警方通過查閱死者的電腦和手機(jī)灌诅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門芳来,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人猜拾,你說我怎么就攤上這事即舌。” “怎么了挎袜?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵顽聂,是天一觀的道長。 經(jīng)常有香客問我盯仪,道長紊搪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任全景,我火速辦了婚禮耀石,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘爸黄。我一直安慰自己滞伟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布炕贵。 她就那樣靜靜地躺著梆奈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鲁驶。 梳的紋絲不亂的頭發(fā)上鉴裹,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音钥弯,去河邊找鬼径荔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛脆霎,可吹牛的內(nèi)容都是我干的总处。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼睛蛛,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼鹦马!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忆肾,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤荸频,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后客冈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體旭从,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了和悦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片退疫。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鸽素,靈堂內(nèi)的尸體忽然破棺而出褒繁,到底是詐尸還是另有隱情,我是刑警寧澤馍忽,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布棒坏,位于F島的核電站,受9級(jí)特大地震影響舵匾,放射性物質(zhì)發(fā)生泄漏俊抵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一坐梯、第九天 我趴在偏房一處隱蔽的房頂上張望徽诲。 院中可真熱鬧,春花似錦吵血、人聲如沸谎替。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钱贯。三九已至,卻和暖如春侦另,著一層夾襖步出監(jiān)牢的瞬間秩命,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工褒傅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弃锐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓殿托,卻偏偏與公主長得像霹菊,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子支竹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354