【iOS開發(fā)】如何將舊的Objective-C項(xiàng)目逐漸轉(zhuǎn)為Swift項(xiàng)目

Swift從2014年發(fā)布到現(xiàn)在,馬上接近三年,經(jīng)過蘋果的不斷改進(jìn)和完善,語法方面已經(jīng)趨于穩(wěn)定。如果是新建的項(xiàng)目,嚴(yán)重建議使用Swift,因?yàn)镾wift必定會取代Objective-C。然后對于用Objective-C寫的舊項(xiàng)目,我們有兩個(gè)選擇:1)直接整個(gè)項(xiàng)目用Swift重寫;2)在舊項(xiàng)目的基礎(chǔ)上,新的東西用Swift編寫,然后再把之前用Objective-C寫的代碼慢慢改為Swift。我個(gè)人更偏向于在舊項(xiàng)目的基礎(chǔ)上逐漸把整個(gè)項(xiàng)目轉(zhuǎn)為Swift。下面我將會結(jié)合實(shí)際工作和蘋果的官方文檔《Using Swift with Cocoa and Objective-C (Swift 3.1)》來總結(jié)下如何將舊的Objective-C項(xiàng)目逐漸轉(zhuǎn)為Swift項(xiàng)目。

學(xué)習(xí)Swift

首先,你要懂得Swift(這TMD不是講廢話嗎 ...)。英文能力不錯(cuò)的建議看官方的文檔《The Swift Programming Language (Swift 3.1)》,官方的文檔總是最好的。不嫌棄的話,可以看看我寫的《Swift文集》,總結(jié)了Swift的關(guān)鍵知識點(diǎn)。另外,大家可以看看Swift翻譯組翻譯的內(nèi)容。

Objective-C和Swift的互用

在這部分內(nèi)容里,我將會根據(jù)官方的文檔,總結(jié)下Objective-C和Swift是如何互用的。

初始化

在Objective-C中,類的初始化方法通常是以init或者initWith開頭的。在Swift中使用Objective-C的類時(shí),Swift會把init開頭的方法作為初始化方法,如果是以initWith開頭的,在Swift中調(diào)用時(shí),會把With去掉,例如:

在Objective-C中:

- (instancetype)init;
- (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style;

在Swift中調(diào)用上面的接口,就會是下面這種形式:

init() { /* ... */ }
init(frame: CGRect, style: UITableViewStyle) { /* ... */ }
類方法和便利初始化器

在Objective-C的類方法,在Swift中會被作為便利初始化器:

在Objective-C中:

UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];

在Swift中,就會是下面這種形式:

let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)
訪問屬性

Objective-C中的屬性將會按照下面這個(gè)規(guī)則來導(dǎo)入Swift:

  • nonnull, nullablenull_resettable標(biāo)記的屬性導(dǎo)入Swift之后,會變成optional和nonoptional類型
  • readonly標(biāo)記的屬性導(dǎo)入Swift之后,變成計(jì)算屬性 ({ get })。
  • weak標(biāo)記的屬性導(dǎo)入Swift之后,同樣是被weak標(biāo)記 (weak var)。
  • assign, copy, strong或者unsafe_unretained標(biāo)記的,將會以適當(dāng)?shù)拇鎯?dǎo)入Swift。
  • class標(biāo)記的屬性導(dǎo)入Swift之后,變成類型屬性。
  • 原子性屬性(atomicnonatomic)在對應(yīng)的Swift屬性中沒有反應(yīng)出來,但是在Swift中被訪問的時(shí)候,Objective-C原子性的實(shí)現(xiàn)仍然會保留。
  • getter=setter=在Swift中被省略。

在Swift中,直接用點(diǎn)語法來訪問Objective-C的屬性。

方法

同樣地,在Swift中也是使用點(diǎn)語法來訪問方法。

當(dāng)Objective-C的方法被導(dǎo)入Swift后,Objective-C的Selector的第一部分會被作為Swift的方法名。例如:

在Objective-C中:

[myTableView insertSubview:mySubview atIndex:2]; 

導(dǎo)入Swift后:

myTableView.insertSubview(mySubview, at: 2)
id兼容性

Objective-C的id類型,導(dǎo)入Swift之后,成為Swift的Any類型。

Swift還有一個(gè)類型AnyObject,可以代表所有的class類型,它可以動(dòng)態(tài)的搜索任何@objc方法,而無需向下轉(zhuǎn)型。例如:

var myObject: AnyObject = UITableViewCell()
myObject = NSDate()
let futureDate = myObject.addingTimeInterval(10)
let timeSinceNow = myObject.timeIntervalSinceNow

但是我們在運(yùn)行代碼之前,AnyObject的具體類型是不確定的,所以上面這種寫法非常危險(xiǎn)。,例如下面這個(gè)例子,在運(yùn)行的時(shí)候會crash:

myObject.character(at: 5)
// crash, myObject doesn't respond to that method

我們可以使用可選鏈或者if let來解決這個(gè)問題:

// 可選鏈
let myChar = myObject.character?(at: 5)

// if let
if let fifthCharacter = myObject.character?(at: 5) {
    print("Found \(fifthCharacter) at index 5")
}
空屬性和可選

我們都知道在Objective-C中,可以使用一些注釋來標(biāo)記屬性、參數(shù)或者返回值是否可以為空,例如_nullable、_Nonull等等。他們會按照下面的規(guī)則來導(dǎo)入Swift:

  • _Nonnull標(biāo)記的,在導(dǎo)入Swift之后,會被作為非可選類型
  • _Nullable標(biāo)記的,在導(dǎo)入Swift之后,會被作為可選類型
  • 沒有被任何注釋標(biāo)記的,在導(dǎo)入Swift之后,會被作為隱式解包可選類型

例如,在Objective-C中:

@property (nullable) id nullableProperty;
@property (nonnull) id nonNullProperty;
@property id unannotatedProperty;
 
NS_ASSUME_NONNULL_BEGIN
- (id)returnsNonNullValue;
- (void)takesNonNullParameter:(id)value;
NS_ASSUME_NONNULL_END
 
- (nullable id)returnsNullableValue;
- (void)takesNullableParameter:(nullable id)value;
 
- (id)returnsUnannotatedValue;
- (void)takesUnannotatedParameter:(id)value;

導(dǎo)入Swift之后:

var nullableProperty: Any?
var nonNullProperty: Any
var unannotatedProperty: Any!
 
func returnsNonNullValue() -> Any
func takesNonNullParameter(value: Any)
 
func returnsNullableValue() -> Any?
func takesNullableParameter(value: Any?)
 
func returnsUnannotatedValue() -> Any!
func takesUnannotatedParameter(value: Any!)
輕量級泛型

在Swift中:

@property NSArray<NSDate *> *dates;
@property NSCache<NSObject *, id<NSDiscardableContent>> *cachedData;
@property NSDictionary <NSString *, NSArray<NSLocale *>> *supportedLocales;

導(dǎo)入Swift之后:

var dates: [Date]
var cachedData: NSCache<AnyObject, NSDiscardableContent>
var supportedLocales: [String: [Locale]]
擴(kuò)展

Swift的擴(kuò)展其實(shí)類似于Objective-C的分類。Swift的擴(kuò)展可以對現(xiàn)有的類、結(jié)構(gòu)和枚舉添加新的成員,即使是在Objective-C中定義的類、結(jié)構(gòu)和枚舉,都可以進(jìn)行擴(kuò)展。

例如下面這個(gè)例子,為UIBezierPath添加一個(gè)便利初始化器,可用來畫一個(gè)等邊三角形:

extension UIBezierPath {
    convenience init(triangleSideLength: CGFloat, origin: CGPoint) {
        self.init()
        let squareRoot = CGFloat(sqrt(3.0))
        let altitude = (squareRoot * triangleSideLength) / 2
        move(to: origin)
        addLine(to: CGPoint(x: origin.x + triangleSideLength, y: origin.y))
        addLine(to: CGPoint(x: origin.x + triangleSideLength / 2, y: origin.y + altitude))
        close()
    }
}
閉包

Objective-C的block,導(dǎo)入Swift之后變?yōu)镃losure。例如在Objective-C中有一個(gè)block:

void (^completionBlock)(NSData *) = ^(NSData *data) {
   // ...
}

在Swift中是這樣的:

let completionBlock: (Data) -> Void = { data in
    // ...
}

Objective-C的block和Swift的Closure基本上可以說是等價(jià)的,但是有一點(diǎn)不同的是:外部的變量在Swift的Closure中是可變的,我們可以直接在Closure內(nèi)部更新變量的值;而在Objective-C中,需要用__block標(biāo)記變量。

解決Block中的循環(huán)引用問題

在Objective-C中:

__weak typeof(self) weakSelf = self;
self.block = ^{
   __strong typeof(self) strongSelf = weakSelf;
   [strongSelf doSomething];
};

在Swift中是這樣解決的,[unowned self]被稱為捕獲列表(Capture List):

self.closure = { [unowned self] in
    self.doSomething()
}
對象之間的比較

在Swift中,比較兩個(gè)對象是否相等有兩種方法:1) ==:比較兩個(gè)對象的內(nèi)容是否相等;2) ===:比較兩個(gè)常量或者變量是否引用著同一個(gè)對象實(shí)例。

Swift為繼承自NSObject的子類提供了默認(rèn)的=====實(shí)現(xiàn),并實(shí)現(xiàn)了Equatable協(xié)議。默認(rèn)的==實(shí)現(xiàn)調(diào)用了isEqual:方法,默認(rèn)的===實(shí)現(xiàn)檢查指針是否相等。我們不能重寫從Objective-C導(dǎo)入的類的這兩個(gè)操作符。

Swift類型的兼容性

下面這些Swift特有的類型,是不兼容Objective-C的:

  • 泛型
  • 元組
  • Swift中定義的沒有Int類型原始值的枚舉
  • Swift中定義的結(jié)構(gòu)
  • Swift中定義的高階函數(shù)
  • Swift中定義的全局變量
  • Swift中定義的類型別名
  • Swift風(fēng)格的variadics
  • 嵌套類型
  • Curried functions

Swift轉(zhuǎn)換為Objective-C:

  • 可選類型,被__nullable標(biāo)記
  • 非可選類型,被__nonnull標(biāo)記
  • 常量和計(jì)算屬性,變成只讀屬性
  • 類型屬性在Objective-C中被class標(biāo)記
  • 類型方法在Objective-C是類方法
  • 初始化器和實(shí)例方法變成Objective-C的實(shí)例方法
  • 會拋出錯(cuò)誤的方法,在Objective-C中會多了一個(gè)NSerror **參數(shù)。如果Swift的方法沒有返回值,在Objective-C中會返回一個(gè)BOOL。

例如,在Swift中:

class Jukebox: NSObject {
    var library: Set<String>
    
    var nowPlaying: String?
    
    var isCurrentlyPlaying: Bool {
        return nowPlaying != nil
    }
    
    class var favoritesPlaylist: [String] {
        // return an array of song names
    }
    
    init(songs: String...) {
        self.library = Set<String>(songs)
    }
    
    func playSong(named name: String) throws {
        // play song or throw an error if unavailable
    }
}

轉(zhuǎn)換成Objective-C后:

@interface Jukebox : NSObject
@property (nonatomic, strong, nonnull) NSSet<NSString *> *library;
@property (nonatomic, copy, nullable) NSString *nowPlaying;
@property (nonatomic, readonly, getter=isCurrentlyPlaying) BOOL currentlyPlaying;
@property (nonatomic, class, readonly, nonnull) NSArray<NSString *> * favoritesPlaylist;
- (nonnull instancetype)initWithSongs:(NSArray<NSString *> * __nonnull)songs OBJC_DESIGNATED_INITIALIZER;
- (BOOL)playSong:(NSString * __nonnull)name error:(NSError * __nullable * __null_unspecified)error;
@end
自定義Swift在Objective-C的接口

我們可以使用@objc(name)自定義Swift的類、屬性、方法、枚舉類型或者枚舉case在Objective-C中使用時(shí)的名字。

例如,在Swift中:

@objc(Color)
enum Цвет: Int {
    @objc(Red)
    case Красный
    
    @objc(Black)
    case Черный
}
 
@objc(Squirrel)
class Белка: NSObject {
    @objc(color)
    var цвет: Цвет = .Красный
    
    @objc(initWithName:)
    init (имя: String) {
        // ...
    }
    @objc(hideNuts:inTree:)
    func прячьОрехи(количество: Int, вДереве дерево: Дерево) {
        // ...
    }
}

Swift還提供了一個(gè)屬性@nonobjc,被這個(gè)屬性標(biāo)記的成員將不能在Objective-C中使用。

需要?jiǎng)討B(tài)調(diào)度

當(dāng)Swift的API被Objective-C runtime使用時(shí),不能保證能動(dòng)態(tài)調(diào)度屬性、方法、下標(biāo)或者初始化器。Swift的編譯器仍然會反虛擬化或者內(nèi)聯(lián)成員訪問來優(yōu)化代碼的屬性,并繞過Objective-C runtime。

我們可以使用dynamic在使用Objective-C runtime時(shí)動(dòng)態(tài)的訪問成員。需要?jiǎng)討B(tài)調(diào)度的情況是非常少的。但是,在Objective-C runtime中使用key-value observing或者method_exchangeImplementations時(shí),我們就需要?jiǎng)討B(tài)調(diào)度,在運(yùn)行的時(shí)候來動(dòng)態(tài)地替換一個(gè)方法的實(shí)現(xiàn)。

注意:使用了dynamic標(biāo)記的聲明,不能再使用@nonobjc。因?yàn)槭褂昧?code>@nonobjc,就意味著不能在Objective-C中使用,而dynamic就是為了給Objective-C使用,這兩個(gè)屬性是完全沖突的。

Selector

在Objective-C中,我們使用@selector來構(gòu)造一個(gè)Selector;而在Swift中,我們要使用#selector

Key和Key Path

在Swift中,可以使用#keyPath來生成編譯器檢查(也就是說編譯的時(shí)候就能知道key和keyPath是否有誤,而不必等到運(yùn)行時(shí)才能確定)的key和keyPath,然后就可以給這些方法使用:value(forKey:)、value(forKeyPath:)、addObserver(_:forKeyPath:options:context:)#keyPath支持鏈?zhǔn)椒椒ɑ蛘邔傩?,?code>#keyPath(Person.bestFriend.name)。

例如:

class Person: NSObject {
    var name: String
    var friends: [Person] = []
    var bestFriend: Person? = nil
    
    init(name: String) {
        self.name = name
    }
}
 
let gabrielle = Person(name: "Gabrielle")
let jim = Person(name: "Jim")
let yuanyuan = Person(name: "Yuanyuan")
gabrielle.friends = [jim, yuanyuan]
gabrielle.bestFriend = yuanyuan
 
#keyPath(Person.name)
// "name"
gabrielle.value(forKey: #keyPath(Person.name))
// "Gabrielle"
#keyPath(Person.bestFriend.name)
// "bestFriend.name"
gabrielle.value(forKeyPath: #keyPath(Person.bestFriend.name))
// "Yuanyuan"
#keyPath(Person.friends.name)
// "friends.name"
gabrielle.value(forKeyPath: #keyPath(Person.friends.name))
// ["Yuanyuan", "Jim"]

Cocoa Frameworks

Swift能自動(dòng)地將一些類型在Swift和Objective-C之間互相轉(zhuǎn)換。例如我們可以傳一個(gè)String值給NSString參數(shù)。

Foundation
橋接類型

Swift Foundation提供了下列橋接值類型:

Objective-C引用類型 Swift值類型
NSAffineTransform AffineTransform
NSArray Array
NSCalendar Calendar
NSCharacterSet CharacterSet
NSData Data
NSDateComponents DateComponents
NSDateInterval DateInterval
NSDate Date
NSDecimalNumber Decimal
NSDictionary Dictionary
NSIndexPath IndexPath
NSIndexSet IndexSet
NSMeasurement Measurement
NSNotification Notification
NSNumber Swift的數(shù)字類型(IntFloat等等)
NSPersonNameComponents PersonNameComponents
NSSet Set
NSString String
NSTimeZone TimeZone
NSURL URL
NSURLComponents URLComponents
NSURLQueryItem URLQueryItem
NSURLRequest URLRequest
NSUUID UUID

我們可以看到,就是直接把Objective-C的前綴NS去掉,就是Swift的值類型(但是有些情況例外)。這些Swift的值類型擁有Objective-C引用類型的所有方法。任何使用Objective-C引用類型的地方,都可以使用對應(yīng)的Swift值類型。

統(tǒng)一的Logging

統(tǒng)一的logging系統(tǒng)提供了一些平臺通用的API來打印一些信息,但是這個(gè)API只在 iOS 10.0, macOS 10.12, tvOS 10.0和watchOS 3.0以后的版本才可用。

下面是使用的例子:

import os.log
 
 // 直接打印一個(gè)String
os_log("This is a log message.")

// 拼接一個(gè)或多個(gè)參數(shù)
let fileSize = 1234567890
os_log("Finished downloading file. Size: %{iec-bytes}d", fileSize)

// 定義打印等級,例如info、debug、error、fault
os_log("This is additional info that may be helpful for troubleshooting.", type: .info)

// 打印信息到特定的子系統(tǒng)
let customLog = OSLog("com.your_company.your_subsystem_name.plist", "your_category_name")
os_log("This is info that may be helpful during development or debugging.", log: customLog, type: .debug)
Cocoa的結(jié)構(gòu)

當(dāng)Swift的結(jié)構(gòu)被橋接成Objective-C時(shí),下面這些結(jié)構(gòu)會變成NSValue。

  • CATransform3D
  • CLLocationCoordinate2D
  • CGAffineTransform
  • CGPoint
  • CGRect
  • CGSize
  • CGVector
  • CMTimeMapping
  • CMTimeRange
  • CMTime
  • MKCoordinateSpan
  • NSRange
  • SCNMatrix4
  • SCNVector3
  • SCNVector4
  • UIEdgeInsets
  • UIOffset

Cocoa設(shè)計(jì)模式

代理

代理設(shè)計(jì)模式,是我們經(jīng)常用到的。在Objective-C中,在調(diào)用代理方法之前,我們首先要檢查代理是否有實(shí)現(xiàn)這個(gè)代理方法。而在Swift中,我們可以使用可選鏈來調(diào)用代理方法。例如:

class MyDelegate: NSObject, NSWindowDelegate {
    func window(_ window: NSWindow, willUseFullScreenContentSize proposedSize: NSSize) -> NSSize {
        return proposedSize
    }
}
myWindow.delegate = MyDelegate()
if let fullScreenSize = myWindow.delegate?.window(myWindow, willUseFullScreenContentSize: mySize) {
    print(NSStringFromSize(fullScreenSize))
}
Lazy初始化

一個(gè)lazy屬性只會在第一次被訪問的時(shí)候才會初始化,相當(dāng)于在Objective-C的懶加載(重寫getter方法)。當(dāng)需要進(jìn)行比較復(fù)雜或者耗時(shí)的計(jì)算才能初始化一個(gè)屬性時(shí),我們應(yīng)該盡量使用lazy屬性。

在Objective-C中:

@property NSXMLDocument *XML;
 
- (NSXMLDocument *)XML {
    if (_XML == nil) {
        _XML = [[NSXMLDocument alloc] initWithContentsOfURL:[[Bundle mainBundle] URLForResource:@"/path/to/resource" withExtension:@"xml"] options:0 error:nil];
    }
 
    return _XML;
}

而在Swift,我們使用lazy屬性:

lazy var XML: XMLDocument = try! XMLDocument(contentsOf: Bundle.main.url(forResource: "document", withExtension: "xml")!, options: 0)

對于其他需要更復(fù)雜的初始化的屬性,可以寫成:

lazy var currencyFormatter: NumberFormatter = {
    let formatter = NumberFormatter()
    formatter.numberStyle = .currency
    formatter.currencySymbol = "¤"
    return formatter
}()
單例

單例模式使我們在開發(fā)中經(jīng)常用到的。

在Objective-C中,我們通常用GCD來實(shí)現(xiàn):

+ (instancetype)sharedInstance {
    static id _sharedInstance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedInstance = [[self alloc] init];
    });
 
    return _sharedInstance;
}

在Swift中,直接使用static類型屬性即可,可以保證只初始化一次,即使時(shí)在多線程中被同時(shí)訪問。

class Singleton {
    static let sharedInstance = Singleton()
}

如果我們需要其他設(shè)置,可以寫成:

class Singleton {
    static let sharedInstance: Singleton = {
        let instance = Singleton()
        // setup code
        return instance
    }()
}
API可用性

有些類和方法并不是在所有平臺或者版本都可用的,所有有時(shí)我們需要進(jìn)行API可用性檢查。

例如,CLLocationManagerrequestWhenInUseAuthorization方法只能在iOS 8.0和macOS 10.10以后的版本才能使用:

let locationManager = CLLocationManager()
if #available(iOS 8.0, macOS 10.10, *) {
    locationManager.requestWhenInUseAuthorization()
}

*是為了處理未來的平臺。

平臺名稱:

  • iOS
  • iOSApplicationExtension
  • macOS
  • macOSApplicationExtension
  • watchOS
  • watchOSApplicationExtension
  • tvOS
  • tvOSApplicationExtension

同樣地,我們在寫自己的API時(shí),也可以指定那些平臺可以使用:

@available(iOS 8.0, macOS 10.10, *)
func useShinyNewFeature() {
    // ...
}

Swift和Objective-C混編

把Objective-C代碼導(dǎo)入Swift

為了把Objective-C代碼導(dǎo)入Swift中,我們需要用到Objective-C bridging header。當(dāng)你把Objective-C文件拖入Swift項(xiàng)目中時(shí),Xcode會提示你是否新建一個(gè)bridging header,如下圖:

Create Bridging Header

點(diǎn)擊Create Bridging Header,項(xiàng)目的文件路徑下就會創(chuàng)建一個(gè)名為項(xiàng)目名稱-Bridging-Header.h的文件(如果項(xiàng)目名稱不是英文,將會以_代替;如果第一個(gè)是字母,也會以_代替)。

當(dāng)然,我們也可以手動(dòng)創(chuàng)建:File > New > File > (iOS, watchOS, tvOS, or macOS) > Source > Header File。

當(dāng)-Bridging-Header.h文件創(chuàng)建好之后,我們還需要進(jìn)行以下操作:

  • 把Swift中要用到的Objective-C類的頭文件,以下面這種形式添加到Bridging-Header.h文件
#import "XYZCustomCell.h"
#import "XYZCustomView.h"
#import "XYZCustomViewController.h"
  • 在Build Settings > Swift Compiler - General > Objective-C Bridging Header添加-Bridging-Header.h的路徑,路徑的格式:項(xiàng)目名/項(xiàng)目名稱-Bridging-Header.h如圖
Objective-C Bridging Header

這樣我們就配置完成了,可以在Swift中調(diào)用Objective-C的代碼:

let myCell = XYZCustomCell()
myCell.subtitle = "A custom cell"
Swift代碼導(dǎo)入Objective-C

當(dāng)需要在Objective-C中使用Swift的代碼時(shí),我們依賴于Xcode自動(dòng)生成的頭文件,這個(gè)頭文件的名稱是項(xiàng)目名-Swift.h(如果項(xiàng)目名稱不是英文,將會以_代替;如果第一個(gè)是字母,也會以_代替)。

默認(rèn)情況下,這個(gè)自動(dòng)生成的頭文件包含了在Swift中被public或者open標(biāo)記的聲明,如果這個(gè)項(xiàng)目中有Objective-C bridging header,那么,internal標(biāo)記的聲明也包含在內(nèi)。被privatefileprivate標(biāo)記的不包含在內(nèi)。私有的聲明不會暴露給Objective-C,除非他們被@IBAction、@IBOutlet或者@objc標(biāo)記。

當(dāng)需要在Objective-C中使用Swift的代碼時(shí),直接導(dǎo)入頭文件項(xiàng)目名-Swift.h,然后我們就可以在Objective-C中調(diào)用Swift的接口,用法與Objective-C的語法相同:

// 初始化實(shí)例,并調(diào)用方法
MySwiftClass *swiftObject = [[MySwiftClass alloc] init];
[swiftObject swiftMethod];

// 引用類或者協(xié)議
// MyObjcClass.h
@class MySwiftClass;
@protocol MySwiftProtocol;
 
@interface MyObjcClass : NSObject
- (MySwiftClass *)returnSwiftClassInstance;
- (id <MySwiftProtocol>)returnInstanceAdoptingSwiftProtocol;
// ...
@end

// 實(shí)現(xiàn)Swift協(xié)議
// MyObjcClass.m
#import "ProductModuleName-Swift.h"
 
@interface MyObjcClass () <MySwiftProtocol>
// ...
@end
 
@implementation MyObjcClass
// ...
@end



注意:如果是剛剛寫的Swift代碼,馬上就想在Objective-C調(diào)用,我們需要先編譯一下,然后Objective-C中才能訪問到Swift的接口。

聲明可以被Objective-C使用的Swift協(xié)議

為了聲明一個(gè)可以被Objective-C使用的Swift協(xié)議,我們要用@objc標(biāo)記,如果協(xié)議的方法是optional,也需要用@objc。

@objc public protocol MySwiftProtocol {
    func requiredMethod()
    
    @objc optional func optionalMethod()
}

把Objective-C代碼轉(zhuǎn)為Swift

前面講了一大堆基礎(chǔ)知識,就是為了更好地將Objective-C代碼轉(zhuǎn)為Swift。

遷移過程
  • 創(chuàng)建一個(gè)對應(yīng)Objective-C.m.h的Swift類,創(chuàng)建方法:File > New > File > (iOS, watchOS, tvOS, or macOS) > Source > Swift File。類的名稱可以相同,也可以不同。
  • 導(dǎo)入相關(guān)的系統(tǒng)框架
  • 如果要需要用到Objective-C的代碼,需要在bridging header中導(dǎo)入相關(guān)的頭文件
  • 為了讓這個(gè)Swift類可以在Objective-C中使用,需要讓這個(gè)類繼承自O(shè)bjective-C的類。如果要自定義在Objective-C中調(diào)用的Swift接口的名稱,使用@objc(name)。
  • 我們可以通過繼承Objective-C的類,實(shí)現(xiàn)Objective-C協(xié)議等來集成Objective-C已有的成員。
  • 在遷移過程中,我們要知道:1)Objective-C的語言特性轉(zhuǎn)換成Swift后,是變成怎樣;2)Cocoa框架中Objective-C的類型,在Swift中是什么類型;3)常用的設(shè)計(jì)模式;4)Objective-C的屬性如何遷移到Swift。這些大部分內(nèi)容我上面都有提到。
  • Objective-C的(-)和(+)方法,對應(yīng)到Swift就是funcclass func
  • Objective-C的簡單的宏定義改為全局常量,復(fù)雜的宏定義改為方法
  • 遷移完成后,在有導(dǎo)入Objective-C類的地方,用#import "項(xiàng)目名稱-Swift.h"替換。
  • 把之前的.m文件的target membership這個(gè)勾去掉。先別著急把之前的.m.h文件刪掉,因?yàn)槲覀儎倓倢懲甑腟wift類可能不太完善,我們還需要用之前的文件來解決問題。
target membership
  • 如果Swift的類名和之前的Objective-C的類名不一樣,在用到Objective-C的類的地方,更新為新的類名。

有任何問題,歡迎大家留言!

歡迎加入我管理的Swift開發(fā)群:536353151,本群只討論Swift相關(guān)內(nèi)容。

原創(chuàng)文章,轉(zhuǎn)載請注明出處。謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容