swift4.0 適配

一、前言

在我們的工程中處于swiftOC混編的狀態,使用swift已經有一年半的時間了,隨著Xcode9的更新,swift3.2swift4.0也隨之到來,swift3.2相較于Xcode8swift3.1變動極小,適配沒遇到問題,主要關注swift4.0的適配。

二、查看當前工程的 swift 版本

image.png

三、使用 Xcode 將工程轉換到 swift4.0

1、環境

  • Xcode9.1
  • 當前 swift 版本 3.2

2、轉換步驟:

  1. 選中要轉換的 target
  2. Edit -> Convert -> To Current Swift Syntax


    image.png
  3. 勾選需要轉換的 targetpod 引用不用勾選),Next
    image.png
  4. 選擇轉換選項,Next
    這兩個選項是關于 swift@objc 推斷特性的,如果使用了 swift4.0 顯式的 @objc 屬性,能減少整體代碼的大小。此時我們選 Minimize Inference(recommend),
    image.png

    關于兩個選項:
  • Minimize Inference(recommend)
    根據靜態推斷,僅在需要的地方添加@objc屬性。使用此選項后,需要按照Completing a Swift 4 minimize inference migration來完成轉換。

  • Match Swift 3 Behavior
    在編譯器隱式推斷的任何地方向代碼添加一個@objc屬性。這個選項不會改變你的二進制文件的大小,因為被Swift 3隱式推斷在所有的地方都添加了顯式的@objc屬性。

  1. 預覽轉換代碼,沒問題,Save。

3、修改錯誤

完成上述5步之后,看一下 swift 版本,已經是4.0了:

image.png

至此打完收工,適配結束。然而并沒有,當你運行的時候會看到這個:


image.png

是否欲哭無淚,居然這么多錯誤,不用怕,其實要改動的地方并不多,有些都是重復的,可以直接全局替換就行。

舉個栗子:

  • class dynamic func
// 轉換前
class dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
  let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
  return vc
}

// 轉換后
class @objc dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
  let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
  return vc
}

// 問題 @objc 修飾符需要前置
// 修改成下面即可
@objc class dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
  let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
  return vc
}

// 全局替換即可
class @objc dynamic func  -> @objc class dynamic func
image.png


上面使用 dynamic 修飾符是由于以前使用 JSPatch 來做 hotfix,需要用到原來OC的運行時特性。

四、@objc

swift4.0 最大的特性之一就是 @objc 修飾符的變化了,它主要處理 OCswift 混編時一些方法的調用以及屬性獲取問題,swift4.0 將在 swift3.x 中一些隱式類型推斷的特性去除以后,需要我們來手動管理 @objc 修飾符。
在上文中使用 Xcode 轉換 swift4.0 時我們勾選了 Minimize Inference 選項,那么我們就需要手動處理相關的 @objc 修飾符,來保證 OCswift 代碼能正常相互調用。

1、@objc 修飾符手動處理步驟

使用“最小化”轉換代碼后,需要處理構建和運行時的問題,在完成初始的 swift4.0 轉換后,需要按照下面步驟來處理其它問題。

  1. 運行你的工程

  2. 修復編譯器提示需要添加 @objc 的地方

  3. 測試你的代碼,并修復編譯器提示使用了不推薦的隱式 @objc 引用的警告。直到沒有警告發生。

  4. 打開工程的 build settings.

  5. Swift 3 @objc inference 設置為 Default.

2、@objc 修飾符需要處理的問題

  1. 編譯警告
  • swift 中編譯的警告
    #selector 參數指定的實例方法必須使用 @objc 修飾,因為swift4中棄用了 @objc屬性推斷。
// 下面的代碼會有警告
class MyClass : NSObject {
   func foo() {
   }
  
   func bar() {
      self.perform(#selector(MyClass.foo)
   }
}
warning: argument of ‘#selector’ refers to instance method ‘foo’ in ‘MyClass’ that depends on ‘@objc’ attribute inference deprecated in Swift 4
  • Objective-C 編譯時警告
    OC 中調用的 swift 方法,在 swift 中需要追加 @objc 修飾,swift4 廢棄了該類型推斷。
// 下面的代碼會有警告
@implementation MyClass (ObjCMethods)
- (void)other {
      [self foo];
   }
@end
warning: Swift method MyClass.foo uses @objc inference deprecated in Swift 4; add @objc to provide an Objective-C entrypoint
  • 修復編譯時警告
// 通過追加 @objc 來消除警告
class MyClass : NSObject {
   @objc func foo() {
   }
  
   func bar() {
      self.perform(#selector(MyClass.foo)
   }
}
  • 查看所有需要添加 @objc 的編譯警告
    image.png

    直接選中定位到相應位置,追加 @objc 修飾即可。
  1. 運行時警告
    運行時警告會打印在控制臺:
***Swift runtime: 
ClassName.swift:lineInFile:columnInLine: 
entrypoint -[ClassName methodName] generated by implicit @objc inference is deprecated and will be removed in Swift 4; 
add explicit @objc to the declaration to emit the Objective-C entrypoint in Swift 4 and suppress this message

Xcode9.1 中,運行時警告在這里也能看到:

image.png

想要修復運行時警告,需要添加 @objc 修飾符到對應的方法或者符號。

  • 運行時警告的常見原因:

    • OC 中使用 SEL
    • swift 中使用了 perform methods
    • OC 中使用了 performSelector methods
    • 使用了 @IBOutlet 或者 @IBAction
// 下面 swift 代碼會產生運行時警告
class MyClass : NSObject {
   func foo() {
   }
  
   func bar() {
      let selectorName = "foo"
      self.perform(Selector(selectorName)
   }
}
***Swift runtime: MyClass.swift:7:7: entrypoint -[MyClass foo] generated by implicit @objc inference is deprecated and will be removed in Swift 4; add explicit @objc to the declaration to emit the Objective-C entrypoint in Swift 4 and suppress this message

五、swift4.0 其它部分特性

1、NSAttributedStringKey

NSAttributedString 的初始化方法變化:

// swift3.x
public init(string str: String, attributes attrs: [AnyHashable : Any]? = nil)

// swift4.0
public init(string str: String, attributes attrs: [NSAttributedStringKey : Any]? = nil)

示例:

// 轉換前
let attributes = [NSForegroundColorAttributeName: RGB(128, g: 134, b: 146),
                          NSParagraphStyleAttributeName: paragraph,
                          NSFontAttributeName: UIFont.systemFont(ofSize: 14)] as [String : Any]
var tipAttrText = NSAttributedString.init(string: tipText, attributes: attributes)

// 轉換后
let attributes = [NSAttributedStringKey.foregroundColor.rawValue: RGB(128, g: 134, b: 146),
                          NSAttributedStringKey.paragraphStyle: paragraph,
                          NSAttributedStringKey.font: UIFont.systemFont(ofSize: 14)] as! [String : Any]
var tipAttrText = NSAttributedString(string: tipText, attributes: attributes)

// tipAttrText 初始化報錯提示
Cannot convert value of type '[String : Any]' to expected argument type '[NSAttributedStringKey : Any]?'

// 修改
NSAttributedStringKey.foregroundColor.rawValue -> NSAttributedStringKey.foregroundColor
去掉 as! [String : Any]

2、String

  • Stringcharacters 屬性被廢棄了
let string = "abc"
var count = string.characters.count

// 第二行報錯
'characters' is deprecated: Please use String or Substring directly

// 對應新方法
count = string.count
  • StringaddingPercentEscapes 方法被廢棄了
// swift3.x
var url = @"http://www.example.com?username=姓名"
url = url.addingPercentEscapes(using: String.Encoding.utf8)!

// 報錯
'addingPercentEscapes(using:)' is unavailable: Use addingPercentEncoding(withAllowedCharacters:) instead, which always uses the recommended UTF-8 encoding, and which encodes for a specific URL component or subcomponent since each URL component or subcomponent has different rules for what characters are valid.

// 修改
uri = uri.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
  • substring(to:) 被廢棄了
let index = tagText.index(tagText.startIndex, offsetBy: MPMultipleStyleListItemTagMaxLength)

// 警告:'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range upto' operator.
let b = tagText.substring(to: index)

// 新 API
// 注意:a 的類型是 Substring,不是 String
let a = tagText.prefix(upTo: index)

3、initialize 廢棄

// swift3.x
override class func initialize() {
    // some code
}

// 報錯
Method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift

Swift3.x 繼續 Method Swizzling這篇文章里面介紹了一種解決思路。

4、swift3 使用 #selector 指定的方法,只有當方法權限為 private 時需要加 @objc 修飾符,swift4.0 都要加 @objc 修飾符

// 示例代碼
func startMonitor() {
    NotificationCenter.default.addObserver(self, selector: #selector(self.refreshUserLoginStatus), name: NSNotification.Name.XSLUserLogin, object: nil)
}
func refreshUserLoginStatus() {
    // some code
}

// 第二行警告
Argument of '#selector' refers to instance method 'refreshUserLoginStatus()' in 'MPUnreadMessageCountManager' that depends on '@objc' inference deprecated in Swift 4

// 追加 private
func startMonitor() {
    NotificationCenter.default.addObserver(self, selector: #selector(self.refreshUserLoginStatus), name: NSNotification.Name.XSLUserLogin, object: nil)
}
private func refreshUserLoginStatus() {
    // some code
}

// 第二行報錯
Argument of '#selector' refers to instance method 'refreshUserLoginStatus()' that is not exposed to Objective-C
  • swift4.0 不再允許重載 extension 中的方法(包括instancestaticclass方法)
// 示例代碼
class TestSuperClass: NSObject {
}
extension TestSuperClass {
    func test() {
        // some code
    }
}
class TestClass: TestSuperClass {
    // 報錯:Declarations from extensions cannot be overridden yet
    override func test() {
        // some code
    }
}

六、pod 引用

添加以下內容到 Podfile

post_install do |installer|
    installer.pods_project.targets.each do |target|
        if ['WTCarouselFlowLayout', 'XSLRevenue', 'OHHTTPStubs/Swift'].include? target.name
            target.build_configurations.each do |config|
                config.build_settings['SWIFT_VERSION'] = '3.2'
            end
        end
    end
end

七、踩坑

UITableViewDelegate 協議方法名變更,沒有錯誤提示:

// swift3.x
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat 

// swift4.0
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat 
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容