OC項目轉Swift指南

OC項目轉Swift指南

運行環境:Xcode 11.1 Swift5.0html

最近參與的一個項目須要從Objective-C(如下簡稱OC)轉到Swift,期間遇到了一些坑,因而有了這篇總結性的文檔。
若是你也有將OC項目Swift化的需求,能夠做爲參考。git

OC轉Swift有一個大前提就是你要對Swift有必定的瞭解,熟悉Swift語法,最好是完整看過一遍官方的Language Guidegithub

轉換的過程分自動化和手動轉譯,鑑於自動化工具的識別率不能讓人滿意,大部分狀況都是須要手動轉換的。面試

自動化工具

有一個比較好的自動化工具Swiftify,能夠將OC文件甚至OC工程整個轉成Swift,號稱準確率能達到90%。我試用了一些免費版中的功能,但感受效果並不理想,由於沒有使用過付費版,因此也很差評價它就是很差。swift

Swiftify還有一個Xcode的插件Swiftify for Xcode,能夠實現對選中代碼和單文件的轉化。這個插件還挺不錯,對純系統代碼轉化還算精確,但部分代碼還存在一些識別問題,須要手動再修改。xcode

OC項目轉Swift指南

手動Swift化

橋接文件

若是你是在項目中首次使用Swift代碼,在添加Swift文件時,Xcode會提示你添加一個.h的橋接文件。若是不當心點了不添加還能夠手動導入,就是本身手動生成一個.h文件,而後在Build Settings > Swift Compiler - General > Objective-C Bridging Header中填入該.h文件的路徑。多線程

OC項目轉Swift指南

<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>閉包

這個橋接文件的做用就是供Swift代碼引用OC代碼,或者OC的三方庫。框架

#import "Utility.h"
#import <Masonry/Masonry.h>
複製代碼

Bridging Header的下面還有一個配置項是Objective-C Generated Interface Header Name,對應的值是ProjectName-Swift.h。這是由Xcode自動生成的一個隱藏頭文件,每次Build的過程會將Swift代碼中聲明爲外接調用的部分轉成OC代碼,OC部分的文件會相似pch同樣全局引用這個頭文件。由於是Build過程當中生成的,因此只有.m文件中能夠直接引用,對於在.h文件中的引用下文有介紹。ide

Appdelegate(程序入口)

Swift中沒有main.m文件,取而代之的是@UIApplicationMain命令,該命令等效於原有的執行main.m。因此咱們能夠把main.m文件進行移除。

系統API

對於UIKit框架中的大部分代碼轉換能夠直接查看系統API文檔進行轉換,這裏就不過多介紹。

property(屬性)

Swift沒有property,也沒有copynonatomic等屬性修飾詞,只有表示屬性是否可變的letvar

注意點一 OC中一個類分.h.m兩個文件,分別表示用於暴露給外接的方法,變量和僅供內部使用的方法變量。遷移到Swift時,應該將.m中的property標爲private,即外接沒法直接訪問,對於.h中的property不作處理,取默認的internal,即同模塊可訪問。

對於函數的遷移也是相同的。

注意點二 有一種特殊狀況是在OC項目中,某些屬性在內部(.m)可變,外部(.h)只讀。這種狀況能夠這麼處理:

private(set) var value: String
複製代碼

就是隻對valueset方法就行private標記。

注意點三 Swift中針對空類型有個專門的符號?,對應OC中的nil。OC中沒有這個符號,可是能夠經過在nullablenonnull表示該種屬性,方法參數或者返回值是否能夠空。

若是OC中沒有聲明一個屬性是否能夠爲空,那就去默認值nonnull

若是咱們想讓一個類的全部屬性,函數返回值都是nonnull,除了手動一個個添加以外還有一個宏命令。

NS_ASSUME_NONNULL_BEGIN
/* code */
NS_ASSUME_NONNULL_END
複製代碼

這是個人iOS開發交流羣:519832104無論你是小白仍是大牛歡迎入駐,能夠一塊兒分享經驗,討論技術,共同窗習成長!
另附上一份各好友收集的大廠面試題,須要iOS開發學習資料、面試真題,進羣便可獲取!
OC項目轉Swift指南

點擊此處,當即與iOS大牛交流學習

enum(枚舉)

OC代碼:

typedef NS_ENUM(NSInteger, PlayerState) {
    PlayerStateNone = 0,
    PlayerStatePlaying,
    PlayerStatePause,
    PlayerStateBuffer,
    PlayerStateFailed,
};

typedef NS_OPTIONS(NSUInteger, XXViewAnimationOptions) {
    XXViewAnimationOptionNone            = 1 <<  0,
    XXViewAnimationOptionSelcted1        = 1 <<  1,
    XXViewAnimationOptionSelcted2        = 1 <<  2,
}
複製代碼

Swift代碼:

enum PlayerState: Int {
    case none = 0
    case playing
    case pause
    case buffer
    case failed
}
struct ViewAnimationOptions: OptionSet {
    let rawValue: UInt
    static let None = ViewAnimationOptions(rawValue: 1<<0)
    static let Selected1 = ViewAnimationOptions(rawValue: 1<<0)
    static let Selected2 = ViewAnimationOptions(rawValue: 1 << 2)
    //...
}
複製代碼

Swift沒有NS_OPTIONS的概念,取而代之的是爲了知足OptionSet協議的struct類型。

懶加載

OC代碼:

- (MTObject *)object {
    if (!_object) {
        _object = [MTObject new];
    }
    return _object;
}
複製代碼

Swift代碼:

lazy var object: MTObject = {
    let object = MTObject()
    return imagobjecteView
}()
複製代碼

閉包

OC代碼:

typedef void (^DownloadStateBlock)(BOOL isComplete);
複製代碼

Swift代碼:

typealias DownloadStateBlock = ((_ isComplete: Bool) -> Void)
複製代碼

單例

OC代碼:

+ (XXManager *)shareInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}
複製代碼

Swift對單例的實現比較簡單,有兩種方式:

第一種

let shared = XXManager()// 聲明在全局命名區(global namespace)
Class XXManager { 
}
複製代碼

你可能會疑惑,爲何沒有dispatch_once,如何保證多線程下建立的惟一性?實際上是這樣的,Swift中全局變量是懶加載,在AppDelegate中被初始化,以後全部的調用都會使用該實例。並且全局變量的初始化是默認使用dispatch_once的,這保證了全局變量的構造器(initializer)只會被調用一次,保證了shard原子性

第二種

Class XXManager {
        static let shared = XXManager()
    private override init() {
        // do something 
    }
}
複製代碼

Swift 2 開始增長了static關鍵字,用於限定變量的做用域。若是不使用static,那麼每個shared都會對應一個實例。而使用static以後,shared成爲全局變量,就成了跟上面第一種方式原理一致。能夠注意到,因爲構造器使用了 private 關鍵字,因此也保證了單例的原子性。

初始化方法和析構函數

對於初始化方法OC先調用父類的初始化方法,而後初始本身的成員變量。Swift先初始化本身的成員變量,而後在調用父類的初始化方法。

OC代碼:

// 初始化方法
@interface MainView : UIView
@property (nonatomic, strong) NSString *title;
- (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title NS_DESIGNATED_INITIALIZER;
@end

@implementation MainView
- (instancetype)initWithFrame:(CGRect)frame title:(NSString *)title {
    if (self = [super initWithFrame:frame]) {
        self.title = title;
    }
    return self;
}
@end
// 析構函數
- (void)dealloc {
    //dealloc
}
複製代碼

上面類在調用時

Swift代碼:

class MainViewSwift: UIView {
    let title: String
    init(frame: CGRect, title: String) {
        self.title = title
        super.init(frame: frame)
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
        deinit {
      //deinit
    }
}
複製代碼

函數調用

OC代碼:

// 實例函數(共有方法)
- (void)configModelWith:(XXModel *)model {}
// 實例函數(私有方法)
- (void)calculateProgress {}
// 類函數
+ (void)configModelWith:(XXModel *)model {}
複製代碼
// 實例函數(共有方法)
func configModel(with model: XXModel) {}
// 實例函數(私有方法)
private func calculateProgress() {}
// 類函數(不能夠被子類重寫)
static func configModel(with model: XXModel) {}
// 類函數(能夠被子類重寫)
class func configModel(with model: XXModel) {}
// 類函數(不能夠被子類重寫)
class final func configModel(with model: XXModel) {}
複製代碼

OC能夠經過是否將方法聲明在.h文件代表該方法是否爲私有方法。Swift中沒有了.h文件,對於方法的權限控制是經過權限關鍵詞進行的,各關鍵詞權限大小爲: private &lt; fileprivate &lt; internal &lt; public &lt; open

其中internal爲默認權限,能夠在同一module下訪問。

NSNotification(通知)

OC代碼:

// add observer
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(method) name:@"NotificationName" object:nil];
// post
[NSNotificationCenter.defaultCenter postNotificationName:@"NotificationName" object:nil];
複製代碼

Swift代碼:

// add observer
NotificationCenter.default.addObserver(self, selector: #selector(method), name: NSNotification.Name(rawValue: "NotificationName"), object: nil)
// post
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "NotificationName"), object: self)
複製代碼

能夠注意到,Swift中通知中心NotificationCenter不帶NS前綴,通知名由字符串變成了NSNotification.Name的結構體。

改爲結構體的目的就是爲了便於管理字符串,本來的字符串類型變成了指定的NSNotification.Name類型。上面的Swift代碼能夠修改成:

extension NSNotification.Name {
    static let NotificationName = NSNotification.Name("NotificationName")
}
// add observer
NotificationCenter.default.addObserver(self, selector: #selector(method), name: .NotificationName, object: nil)
// post
NotificationCenter.default.post(name: .NotificationName, object: self)
複製代碼

protocol(協議/代理)

OC代碼:

@protocol XXManagerDelegate <NSObject>
- (void)downloadFileFailed:(NSError *)error;
@optional
- (void)downloadFileComplete;
@end

@interface XXManager: NSObject
@property (nonatomic, weak) id<XXManagerDelegate> delegate;  
@end
複製代碼

Swift中對protocol的使用拓寬了許多,不光是class對象,structenum也均可以實現協議。須要注意的是structenum爲指引用類型,不能使用weak修飾。只有指定當前代理只支持類對象,才能使用weak。將上面的代碼轉成對應的Swift代碼,就是:

@objc protocol XXManagerDelegate {
    func downloadFailFailed(error: Error)
    @objc optional func downloadFileComplete() // 可選協議的實現
}
class XXManager: NSObject {
    weak var delegate: XXManagerDelegate?  
}
複製代碼

@objc是代表當前代碼是針對NSObject對象,也就是class對象,就能夠正常使用weak了。

若是不是針對NSObject對象的delegate,僅僅是普通的class對象能夠這樣設置代理:

protocol XXManagerDelegate: class {
    func downloadFailFailed(error: Error)
}
class XXManager {
    weak var delegate: XXManagerDelegate?
}
複製代碼

值得注意的是,僅@objc標記的protocol可使用@optional

Swift和OC混編注意事項

函數名的變化

若是你在一個Swift類裏定義了一個delegate方法:

@objc protocol MarkButtonDelegate {
    func clickBtn(title: String)
}
複製代碼

若是你要在OC中實現這個協議,這時候方法名就變成了:

- (void)clickBtnWithTitle:(NSString *)title {
    // code
}
複製代碼

這主要是由於Swift有指定參數標籤,OC卻沒有,因此在由Swift方法名生成OC方法名時編譯器會自動加一些修飾詞,已使函數做爲一個句子能夠"通順"。

在OC的頭文件裏調用Swift類

若是要在OC的頭文件裏引用Swift類,由於Swift沒有頭文件,而爲了讓在頭文件可以識別該Swift類,須要經過@class的方法引入。

@class SwiftClass;

@interface XXOCClass: NSObject
@property (nonatomic, strong) SwiftClass *object;
@end
複製代碼

對OC類在Swift調用下重命名

由於Swift對不一樣的module都有命名空間,因此Swift類都不須要添加前綴。若是有一個帶前綴的OC公共組件,在Swift環境下調用時不得不指定前綴是一件很不優雅的事情,因此蘋果添加了一個宏命令NS_SWIFT_NAME,容許在OC類在Swift環境下的重命名:

NS_SWIFT_NAME(LoginManager)
@interface XXLoginManager: NSObject
@end
複製代碼

這樣咱們就將XXLoginManager在Swift環境下的類名改成了LoginManager

引用類型和值類型

  • struct 和 enum 是值類型,類 class 是引用類型。
  • StringArray和 Dictionary都是結構體,所以賦值直接是拷貝,而NSStringNSArrayNSDictionary則是類,因此是使用引用的方式。
  • struct 比 class 更「輕量級」,struct 分配在棧中,class 分配在堆中。

id類型和AnyObject

OC中id類型被Swift調用時會自動轉成AnyObject,他們很類似,但卻其實概念並不一致。Swift中還有一個概念是Any,他們三者的區別是:

  • id 是一種通用的對象類型,它能夠指向屬於任何類的對象,在OC中便是能夠表明全部繼承於NSObject的對象。
  • AnyObject能夠表明任何class類型的實例。
  • Any能夠表明任何類型,甚至包括func類型。

從範圍大小比較就是:id &lt; AnyObject &lt; Any

其餘語法區別及注意事項(待補充)

一、Swift語句中不須要加分號;

二、關於Bool類型更加嚴格,Swift再也不是OC中的非0就是真,真假只對應truefalse

三、Swift類內通常不須要寫self,可是閉包內是須要寫的。

四、Swift是強類型語言,必需要指定明確的類型。在Swift中IntFloat是不能直接作運算的,必需要將他們轉成同一類型才能夠運算。

五、Swift拋棄了傳統的++--運算,拋棄了傳統的C語言式的for循環寫法,而改成for-in

六、Swift的switch操做,不須要在每一個case語句結束的時候都添加break

七、Swift對enum的使用作了很大的擴展,能夠支持任意類型,而OC枚舉僅支持Int類型,若是要寫兼容代碼,要選擇Int型枚舉。

八、Swift代碼要想被OC調用,須要在屬性和方法名前面加上@objc

九、Swift獨有的特性,如泛型,struct,非Int型的enum等被包含才函數參數中,即便添加@objc也不會被編譯器經過。

十、Swift支持重載,OC不支持。

十一、帶默認值的Swift函數再被OC調用時會自動展開。

語法檢查

對於OC轉Swift以後的語法變化還有不少細節值得注意,特別是對於初次使用Swift這門語言的同窗,很容易遺漏或者待着OC的思想去寫代碼。這裏推薦一個語法檢查的框架SwiftLint,能夠自動化的檢查咱們的代碼是否符合Swift規範。

能夠經過cocoapods進行引入,配置好以後,每次Build的過程,Lint腳本都會執行一遍Swift代碼的語法檢查操做,Lint還會將代碼規範進行分級,嚴重的代碼錯誤會直接報錯,致使程序沒法啓動,不太嚴重的會顯示代碼警告(⚠️)。

若是你感受SwiftLint有點過於嚴格了,還能夠經過修改.swiftlint.yml文件,自定義屬於本身的語法規範。

文章來源:掘金 zhangferry

相關文章
相關標籤/搜索