一见秽、前言
在我們的工程中處于swift
和OC
混編的狀態(tài)愉烙,使用swift
已經(jīng)有一年半的時(shí)間了,隨著Xcode9
的更新解取,swift3.2
和swift4.0
也隨之到來步责,swift3.2
相較于Xcode8
的swift3.1
變動(dòng)極小,適配沒遇到問題禀苦,主要關(guān)注swift4.0
的適配蔓肯。
二、查看當(dāng)前工程的 swift 版本
三振乏、使用 Xcode 將工程轉(zhuǎn)換到 swift4.0
1蔗包、環(huán)境
- Xcode9.1
- 當(dāng)前
swift
版本 3.2
2、轉(zhuǎn)換步驟:
- 選中要轉(zhuǎn)換的
target
-
Edit -> Convert -> To Current Swift Syntax
- 勾選需要轉(zhuǎn)換的
target
(pod
引用不用勾選)慧邮,Next
- 選擇轉(zhuǎn)換選項(xiàng)调限,Next
這兩個(gè)選項(xiàng)是關(guān)于swift
的@objc
推斷特性的舟陆,如果使用了swift4.0
顯式的@objc
屬性,能減少整體代碼的大小旧噪。此時(shí)我們選 Minimize Inference(recommend)吨娜,
關(guān)于兩個(gè)選項(xiàng):
Minimize Inference(recommend)
根據(jù)靜態(tài)推斷,僅在需要的地方添加@objc屬性淘钟。使用此選項(xiàng)后宦赠,需要按照Completing a Swift 4 minimize inference migration來完成轉(zhuǎn)換。Match Swift 3 Behavior
在編譯器隱式推斷的任何地方向代碼添加一個(gè)@objc屬性米母。這個(gè)選項(xiàng)不會(huì)改變你的二進(jìn)制文件的大小勾扭,因?yàn)楸籗wift 3隱式推斷在所有的地方都添加了顯式的@objc屬性。
- 預(yù)覽轉(zhuǎn)換代碼铁瞒,沒問題妙色,Save。
3慧耍、修改錯(cuò)誤
完成上述5步之后身辨,看一下 swift
版本,已經(jīng)是4.0了:
至此打完收工芍碧,適配結(jié)束煌珊。然而并沒有,當(dāng)你運(yùn)行的時(shí)候會(huì)看到這個(gè):
是否欲哭無淚泌豆,居然這么多錯(cuò)誤定庵,不用怕,其實(shí)要改動(dòng)的地方并不多踪危,有些都是重復(fù)的蔬浙,可以直接全局替換就行。
舉個(gè)栗子:
- class dynamic func
// 轉(zhuǎn)換前
class dynamic func bookMoneyToUpController() -> MPBookMoneyToUpController {
let vc = MPBookMoneyToUpController.init(nibName: "MPBookMoneyToUpController", bundle: Bundle.main)
return vc
}
// 轉(zhuǎn)換后
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
注:
上面使用 dynamic
修飾符是由于以前使用 JSPatch
來做 hotfix
贞远,需要用到原來OC的運(yùn)行時(shí)特性畴博。
四、@objc
swift4.0
最大的特性之一就是 @objc
修飾符的變化了兴革,它主要處理 OC
和 swift
混編時(shí)一些方法的調(diào)用以及屬性獲取問題绎晃,swift4.0
將在 swift3.x
中一些隱式類型推斷的特性去除以后,需要我們來手動(dòng)管理 @objc
修飾符杂曲。
在上文中使用 Xcode
轉(zhuǎn)換 swift4.0
時(shí)我們勾選了 Minimize Inference
選項(xiàng)庶艾,那么我們就需要手動(dòng)處理相關(guān)的 @objc
修飾符,來保證 OC
和 swift
代碼能正常相互調(diào)用擎勘。
1咱揍、@objc
修飾符手動(dòng)處理步驟
使用“最小化”轉(zhuǎn)換代碼后,需要處理構(gòu)建和運(yùn)行時(shí)的問題棚饵,在完成初始的 swift4.0
轉(zhuǎn)換后煤裙,需要按照下面步驟來處理其它問題掩完。
運(yùn)行你的工程
修復(fù)編譯器提示需要添加
@objc
的地方測(cè)試你的代碼,并修復(fù)編譯器提示使用了不推薦的隱式
@objc
引用的警告硼砰。直到?jīng)]有警告發(fā)生且蓬。打開工程的 build settings.
將
Swift 3 @objc inference
設(shè)置為Default
.
2、@objc
修飾符需要處理的問題
- 編譯警告
- swift 中編譯的警告
#selector
參數(shù)指定的實(shí)例方法必須使用@objc
修飾题翰,因?yàn)?code>swift4中棄用了@objc
屬性推斷恶阴。
// 下面的代碼會(huì)有警告
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 編譯時(shí)警告
在OC
中調(diào)用的swift
方法,在swift
中需要追加@objc
修飾豹障,swift4
廢棄了該類型推斷冯事。
// 下面的代碼會(huì)有警告
@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
- 修復(fù)編譯時(shí)警告
// 通過追加 @objc 來消除警告
class MyClass : NSObject {
@objc func foo() {
}
func bar() {
self.perform(#selector(MyClass.foo)
}
}
- 查看所有需要添加
@objc
的編譯警告
直接選中定位到相應(yīng)位置,追加@objc
修飾即可血公。
- 運(yùn)行時(shí)警告
運(yùn)行時(shí)警告會(huì)打印在控制臺(tái):
***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
中昵仅,運(yùn)行時(shí)警告在這里也能看到:
想要修復(fù)運(yùn)行時(shí)警告,需要添加 @objc
修飾符到對(duì)應(yīng)的方法或者符號(hào)累魔。
-
運(yùn)行時(shí)警告的常見原因:
- 在
OC
中使用SEL
- 在
swift
中使用了perform methods
- 在
OC
中使用了performSelector methods
- 使用了
@IBOutlet
或者@IBAction
- 在
// 下面 swift 代碼會(huì)產(chǎn)生運(yùn)行時(shí)警告
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)
示例:
// 轉(zhuǎn)換前
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)
// 轉(zhuǎn)換后
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 初始化報(bào)錯(cuò)提示
Cannot convert value of type '[String : Any]' to expected argument type '[NSAttributedStringKey : Any]?'
// 修改
NSAttributedStringKey.foregroundColor.rawValue -> NSAttributedStringKey.foregroundColor
去掉 as! [String : Any]
2垦写、String
-
String
的characters
屬性被廢棄了
let string = "abc"
var count = string.characters.count
// 第二行報(bào)錯(cuò)
'characters' is deprecated: Please use String or Substring directly
// 對(duì)應(yīng)新方法
count = string.count
-
String
的addingPercentEscapes
方法被廢棄了
// swift3.x
var url = @"http://www.example.com?username=姓名"
url = url.addingPercentEscapes(using: String.Encoding.utf8)!
// 報(bào)錯(cuò)
'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
}
// 報(bào)錯(cuò)
Method 'initialize()' defines Objective-C class method 'initialize', which is not permitted by Swift
Swift3.x 繼續(xù) Method Swizzling這篇文章里面介紹了一種解決思路梯澜。
4、swift3
使用 #selector
指定的方法渴析,只有當(dāng)方法權(quán)限為 private
時(shí)需要加 @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
}
// 第二行報(bào)錯(cuò)
Argument of '#selector' refers to instance method 'refreshUserLoginStatus()' that is not exposed to Objective-C
-
swift4.0
不再允許重載extension
中的方法(包括instance
、static
俭茧、class
方法)
// 示例代碼
class TestSuperClass: NSObject {
}
extension TestSuperClass {
func test() {
// some code
}
}
class TestClass: TestSuperClass {
// 報(bào)錯(cuò):Declarations from extensions cannot be overridden yet
override func test() {
// some code
}
}
六咆疗、pod
引用
添加以下內(nèi)容到 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
協(xié)議方法名變更午磁,沒有錯(cuò)誤提示:
// swift3.x
func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: IndexPath) -> CGFloat
// swift4.0
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat