Swift開發(fā)指南:使用Swift與Cocoa和Objective-C(Swift 4) - 2.互通性

章節(jié)導航:
Swift開發(fā)指南:使用Swift與Cocoa和Objective-C(Swift 4) - 1.入門
Swift開發(fā)指南:使用Swift與Cocoa和Objective-C(Swift 4) - 2.互通性


與Objective-C API進行交互

互操作性是能夠在任何一個方向上與Swift和Objective-C進行接口,讓您訪問并使用以其他語言的文件中的一些代碼先嬉。當您開始將Swift集成到應用程序開發(fā)工作流程中時轧苫,了解如何利用互操作性來重新定義、改進和增強編寫Cocoa應用程序的方式是一個好主意疫蔓。

互操作性的一個重要特點是含懊,它可以讓您在編寫Swift代碼時使用Objective-C API。導入Objective-C框架后衅胀,您可以實例化類岔乔,并使用本地Swift語法與它們進行交互。

初始化

要在Swift中實例化一個Objective-C類滚躯,可以使用Swift構造器語法調用其中的一個構造方法雏门。

Objective-C構造方法以init開始,如果構造方法需要一個或多個參數則使用initWith:掸掏。當由Swift導入Objective-C初始化程序時茁影,該init前綴將成為一個init關鍵字,表示該方法是Swift初始化程序丧凤。如果構造方法接受參數募闲,則將其With移除,并將構造器其余部分對應地分割為命名了的參數愿待。

參照以下Objective-C構造器

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

以下是Swift構造器的聲明:

init() { /* ... / }
init(frame: CGRect, style: UITableViewStyle) { /
... */ }

在實例化對象時浩螺,Objective-C和Swift語法之間的區(qū)別更為明顯。

在Objective-C中呼盆,您可以執(zhí)行以下操作:

UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];

在Swift年扩,你這樣做:

let myTableView: UITableView = UITableView(frame: .zero, style: .grouped)

請注意蚁廓,您不需要調用alloc; Swift為您處理這個访圃。還要注意,在調用Swift形式的構造方法時相嵌,init不會出現在任何地方腿时。

您可以在指定常量或變量時顯式提供類型况脆,或者您可以省略類型,并且Swift會從構造方法自動推斷類型批糟。

let myTextField = UITextField(frame: CGRect(x: 0.0, y: 0.0, width: 200.0, height: 40.0))

這些UITableViewUITextField對象是您在Objective-C中實例化的對象格了。您可以以與Objective-C中相同的方式使用它們,訪問任何屬性并調用其各自類型上定義的任何方法徽鼎。


類初始化方法和便利構造器

為了一致性和簡單性盛末,在Swift中,Objective-C類初始化方法作為便利構造器導入否淤。這允許它們與構造器使用相同的語法悄但。

例如,在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中,構造器直接返回它們初始化的對象啰扛。要在初始化失敗時通知調用者嚎京,Objective-C構造器可以返回nil。在Swift中隐解,此模式內置于稱為可失敗構造器的語言功能中鞍帝。

系統(tǒng)框架中的許多Objective-C初始化程序已被審計,可指示構造是否可以失敗厢漩。您可以在你的Objective-C類中使用nullability annotations指出構造方法是否可以失敗膜眠,如可空性和可選項章節(jié)所述。Objective-C構造器導入為init(...)如果構造不會失敗溜嗜,導入為init?(...)如果會失敗宵膨。否則,構造器將被導入為init!(...)

例如炸宵,如果在提供的路徑上不存在圖像文件辟躏,則UIImage(contentsOfFile:)初始化程序可能無法初始化UIImage對象。如果初始化成功土全,您可以使用可選項綁定來打開可用構造器的結果捎琐。

if let image = UIImage(contentsOfFile: "MyImage.png") {
    // loaded the image successfully
} else {
   // could not load the image
}
<br>

訪問屬性

使用@property語法的Objective-C屬性聲明將以以下列方式導入為Swift屬性:

  • 具有可空屬性屬性(nonnullnullablenullresettable)的屬性作為Swift屬性導入裹匙,具有可選或非可選類型瑞凑,如可空項和可選項所述。
  • 具有readonly屬性屬性的屬性將導入為具有getter({ get })的Swift計算屬性概页。
  • 具有weak屬性屬性的屬性將導入為標有weak關鍵字(weak var)的Swift屬性眯牧。
  • weak外與所有權有關的屬性(即元旬,assign更胖,copystrong铃将,或unsafe_unretained)被導入為合適的Swift屬性存儲。
  • class屬性導入為Swift類型屬性哑梳。
  • 原子屬性(atomicnonatomic)不會反映在相應的Swift屬性聲明中劲阎,但是從Swift訪問導入的屬性時,Objective-C實現的原子屬性仍然保持不變鸠真。
  • Swift忽略 Accessor屬性(getter=setter=)悯仙。

您可以使用點語法訪問Swift中的Objective-C對象上的屬性,使用不帶括號的屬性名稱吠卷。

例如雁比,您可以使用以下代碼設置對象的屬性textColortext屬性UITextField

myTextField.textColor = .darkGray
myTextField.text = "Hello world"

返回值并且不帶參數的Objective-C方法可以像使用點語法的Objective-C屬性一樣調用。但是撤嫩,由Swift作為實例方法導入偎捎,因為只有Objective-C @property聲明由Swift作為屬性導入。方法被導入并按照“ 使用方法”章節(jié)中的描述進行調用序攘。


使用方法

您可以使用點語法從Swift調用Objective-C方法茴她。

當將Objective-C方法導入到Swift中時,Objective-C選擇器的第一部分將成為基本方法名稱程奠,并顯示在括號之前丈牢。第一個參數立即出現在括號內,沒有名稱瞄沙。選擇的其余部分對應于參數名稱己沛,并顯示在括號內。調用時需要所有選擇器對應的參數距境。

例如申尼,在Objective-C中,你可以這樣做:

[myTableView insertSubview:mySubview atIndex:2];

在Swift垫桂,你這樣做:

myTableView.insertSubview(mySubview, at: 2)

如果您調用一個沒有參數的方法师幕,那么還必須包含括號。

myTableView.layoutIfNeeded()



id兼容性

Objective-C id類型被Swift導入為Any诬滩。在編譯時和運行時霹粥,當Swift值或對象作為id參數傳入Objective-C時,編譯器將引入通用橋接轉換操作疼鸟。當id值導入Swift時成為Any后控,運行時會自動處理橋接到類引用或Swift值類型。

var x: Any = "hello" as String
x as? String // String with value "hello"
x as? NSString // NSString with value "hello"

x = "goodbye" as NSString
x as? String // String with value "goodbye"
x as? NSString // NSString with value "goodbye"

向下轉換Any

當處理的Any是已知基礎類型或可以合理確定的類型的對象時空镜,將這些對象降級到更具體的類型通常是有用的浩淘。但是矾利,由于該Any類型可以引用任何類型,所以不能保證向更具體類型的轉換成功馋袜。

您可以使用條件類型轉換運算符(as?),該運算符返回一個可選的您嘗試向下轉換的類型值:

let userDefaults = UserDefaults.standard
let lastRefreshDate = userDefaults.object(forKey: "LastRefreshDate") // lastRefreshDate is of type Any?
if let date = lastRefreshDate as? Date {
print("(date.timeIntervalSinceReferenceDate)")
}

如果您確定對象的類型舶斧,可以使用強制downcast操作符(as!)欣鳖。

let myDate = lastRefreshDate as! Date
let timeInterval = myDate.timeIntervalSinceReferenceDate

但是,如果強制降級失敗茴厉,則會觸發(fā)運行時錯誤:

let myDate = lastRefreshDate as! String // Error

動態(tài)方法查找

Swift還包括一種AnyObject表示某種對象的類型泽台,并具有動態(tài)查找任何@objc方法的特殊功能。這允許您訪問返回id值的Objective-C API時保持未定義類型的靈活性矾缓。

例如怀酷,您可以將任何類類型的對象分配給AnyObject類型的常量或變量,然后將再分配給其他類型的對象嗜闻。您還可以調用任何Objective-C方法并訪問AnyObject值上的任何屬性而不轉換為更具體的類類型蜕依。

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

無法識別的選擇器和可選鏈接

因為在AnyObject運行時之前,值的具體類型是不知道的琉雳,所以有可能無意中寫入不安全的代碼样眠。在Swift以及Objective-C中,嘗試調用不存在的方法會觸發(fā)無法識別的選擇器錯誤翠肘。

例如檐束,以下代碼編譯時沒有編譯器警告,但會在運行時觸發(fā)錯誤:

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

Swift使用可選項來防范這種不安全的行為束倍。當您調用AnyObject類型值的方法時被丧,該方法調用的行為就像一個隱式解包的可選項。您可以使用與協(xié)議中可選方法相同的可選鏈接語法來可選地調用AnyObject的方法绪妹。

例如甥桂,在下面列舉的代碼中,不執(zhí)行第一行和第二行邮旷,因為NSDate對象上不存在count屬性和character(at:)方法格嘁。該myCount常數被推斷為是可選的Int,并且被設定為nil廊移。您還可以使用if- let語句有條件地解開對象可能無法響應的方法的結果糕簿,如第三行所示。

// myObject has AnyObject type and NSDate value
let myCount = myObject.count
// myCount has Int? type and nil value
let myChar = myObject.character?(at: 5)
// myChar has unichar? type and nil value
if let fifthCharacter = myObject.character?(at: 5) {
print("Found (fifthCharacter) at index 5")
}
// conditional branch not executed

注意
雖然Swift在調用類型值的方法時不需要強制展開AnyObject狡孔,但建議您采取措施來防止意外行為懂诗。



可空性和可選項

在Objective-C中,使用裸指針來處理對可能為NULL的對象(Objective-C中稱為nil)的引用苗膝。在Swift中殃恒,所有值(包括結構和對象引用)都被保證為非空值。作為替代,通過將值的類型包裝在可選類型中來表示可能丟失的值离唐。當您需要指出值丟失時病附,你可以使用nil值。有關可選項的更多信息亥鬓,請參見The Swift Programming Language (Swift 4)文檔里的Optional章節(jié)

Objective-C可以使用可空性注釋來指定參數類型完沪、屬性類型或返回類型可以有NULLnil值。單個類型聲明可以使用_Nullable_Nonnull修飾來進行靜態(tài)編譯時檢查嵌戈,單個屬性聲明可以使用nullable覆积,nonnullnull_resettable修飾來進行檢查,或整個區(qū)域可使用NS_ASSUME_NONNULL_BEGINNS_ASSUME_NONNULL_END宏來設置可空性檢查熟呛。如果沒有為類型提供可空性信息宽档,則Swift不能區(qū)分可選項和不可選項引用,并將其導入為隱式解包可選項庵朝。

  • 聲明為nonnullable或有_Nonnull修飾吗冤、或處在整個靜態(tài)編譯檢查區(qū)域的類型,會被Swift導入為nonoptional(不可選項)
  • 聲明為nullable或有_Nullable修飾的類型九府,會被Swift導入為optional(可選項)
  • 沒有可空性聲明的類型欣孤,會被Swift導入為implicitly unwrapped optional(隱式解包可選項)

例如,參考以下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;

以下是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!)

大多數Objective-C系統(tǒng)框架(包括Foundation)都提供了可空性注釋昔逗,允許您以慣用的和類型安全的方式處理值降传。

橋接可選項到不可空對象

Swift根據可選項是否包含包裝了的值,將可選值橋接至Objective-C對象勾怒。如果可選項是nil婆排,Swift將該nil橋接為NSNull實例。否則笔链,Swift將可選項橋接為其展開的值段只。例如,當可選項傳遞給Objective-C API中具有非空值的id類型鉴扫,或者將可選項數組([T?])橋接到一個NSArray時赞枕,您會看到此行為。

以下代碼顯示了String?實例如何與Objective-C橋接坪创,具體取決于它們的值炕婶。

@implementation OptionalBridging

  • (void)logSomeValue:(nonnull id)valueFromSwift {
     if ([valueFromSwift isKindOfClass: [NSNull class]]) {
      os_log(OS_LOG_DEFAULT, "Received an NSNull value.");
     } else {
      os_log(OS_LOG_DEFAULT, "%s", [valueFromSwift UTF8String]);
     }
    }
    @end

由于參數valueFromSwiftid類型的,它以Swift的Any類型導入下面的Swift代碼莱预。但是柠掂,由于Any在預期之中時傳遞可選項并不常見,所以傳遞給logSomeValue(_:)類方法的可選項被顯式轉換為Any類型依沮,這會使編譯警告靜默涯贞。

let someValue: String? = "Bridge me, please."
let nilValue: String? = nil

OptionalBridging.logSomeValue(someValue as Any) // Bridge me, please.
OptionalBridging.logSomeValue(nilValue as Any) // Received an NSNull value.



協(xié)議限制類

由一個或多個協(xié)議限定的Objective-C類由Swift作為協(xié)議組合類型導入枪狂。例如,給定以下引用視圖控制器的Objective-C屬性:

@property UIViewController< UITableViewDataSource, UITableViewDelegate> * myController;

以下是Swift導入的方式:

var myController: UIViewController & UITableViewDataSource & UITableViewDelegate

Objective-C協(xié)議限制的元類由Swift作為協(xié)議元類型導入宋渔。例如州疾,給定以下Objective-C方法對指定的類執(zhí)行操作:

- (void)doSomethingForClass:(Class< NSCoding>)codingClass;

以下是Swift導入的方式:

func doSomething(for codingClass: NSCoding.Type)



輕量級泛型

使用通過輕量泛型聲明的Objective-C的參數化類型由Swift導入時,包含著其保存的類型的內容皇拣。例如严蓖,給定如下Objective-C的屬性聲明:

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

以下是Swift導入它們的方式:

var dates: [Date]
var cachedData: NSCache< NSObject, NSDiscardableContent>
var supportedLocales: [String: [Locale]]

用Objective-C編寫的參數化類導入到Swift中將作為具有相同數量類型參數的泛型類。Swift導入的所有Objective-C通用類型參數都有一個類型約束审磁,它要求該類型為一個類(T: Any)。如果Objective-C的泛型參數化類指定了類限定岂座,則導入的Swift類具有一個約束态蒂,該約束要求該類型是指定類的子類。如果Objective-C泛型參數化類指定了協(xié)議限定條件费什,則導入的Swift類具有一個約束钾恢,要求該類型符合指定的協(xié)議。對于非特異化的Objective-C類型鸳址,Swift推斷導入類類型約束的泛型參數化瘩蚪。例如,給出以下Objective-C類和類型聲明:

@interface List< T: id< NSCopying>> : NSObject

  • (List< T> *)listByAppendingItemsInList:(List< T> *)otherList;
    @end

@interface ListContainer : NSObject

  • (List< NSValue *> *)listOfValues;
    @end

@interface ListContainer (ObjectList)

  • (List *)listOfObjects;
    @end

以下是Swift導入的方式:

class List< T: NSCopying> : NSObject {
 func listByAppendingItemsInList(otherList: List< T>) -> List< T>
}

class ListContainer : NSObject {
 func listOfValues() -> List< NSValue>
}

extension ListContainer {
 func listOfObjects() -> List< NSCopying>
}



擴展

Swift的擴展類似于Objective-C的類別稿黍。擴展擴展了現有類疹瘦、結構和枚舉的行為,包括在Objective-C中定義的行為巡球。您可以從系統(tǒng)框架或您自己的自定義類型之一定義類型上的擴展言沐。只需導入相應的模塊,并使用與Objective-C中使用的名稱相同的名稱來引用類酣栈,結構或枚舉险胰。

例如,您可以根據UIBezierPath提供的邊長和起點矿筝,擴展類以創(chuàng)建具有等邊三角形的簡單貝塞爾路徑起便。

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

您可以使用擴展來添加屬性(包括類和靜態(tài)屬性)。但是窖维,必須計算這些屬性; 擴展不能將存儲的屬性添加到類榆综,結構或枚舉中。

此示例將CGRect結構擴展為包含area計算屬性:

extension CGRect {
 var area: CGFloat {
 return width * height
 }
}
let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)
let area = rect.area

您也可以使用擴展來添加協(xié)議一致性而不對其進行子類化铸史。如果在Swift中定義了協(xié)議奖年,那么您也可以將它添加到結構或枚舉中,無論是在Swift還是Objective-C中定義沛贪。

您不能使用擴展來覆蓋Objective-C類型上的現有方法或屬性陋守。



閉包

Objective-C的block被Swift自動導入為具有Objective-Cblock調用約定的閉包震贵,由@convention(block)屬性表示。例如水评,這里是一個Objective-C塊變量:

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

這里是Swift的樣子:

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

Swift的閉包和Objective-C的block是兼容的猩系,所以您可以將Swift閉包傳遞給Objective-C方法中預期的block。Swift的閉包和函數具有相同的功能中燥,所以你甚至可以傳遞Swift函數的名稱寇甸。

閉包具有與block類似的捕獲語義,但在一個關鍵方面有所不同:變量是可變的而不是復制的疗涉。換句話說拿霉,Objective-C中__block的行為是Swift中變量的默認行為。

避免獲取自己時產生強引用循環(huán)

在Objective-C中咱扣,如果您需要在block中獲取self绽淘,那么考慮內存管理的含義就顯得很重要。

block保留對任何獲取的對象的強引用闹伪,包括self沪铭。如果self保持對block的強引用,例如拷貝屬性偏瓤,這將創(chuàng)建一個強引用循環(huán)杀怠。為了避免這種情況,你可以讓block獲取一個弱引用self

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

像Objective-C中的block一樣厅克,Swift中的閉包也保持對任何獲取的對象的強引用赔退,包括self。為了防止強引用循環(huán)证舟,您可以在閉包的捕獲列表里指定selfunowned

self.closure = { [unowned self] in
 self.doSomething()
}

欲了解更多信息离钝,請參見 The Swift Programming Language (Swift 4)Resolving Strong Reference Cycles for Closures 章節(jié)。



對象比較

您可以在Swift中的兩個對象之間進行兩種截然不同的比較褪储。第一種卵渴,平等性(==),比較對象的內容鲤竹。第二種浪读,一致性(===)確定常量或變量是否引用相同的對象實例。

Swift提供了=====操作符的默認實現辛藻,并為從NSObject類派生的對象采用Equatable協(xié)議碘橘。==操作符的默認實現調用isEqual:方法,===運算符的默認實現檢查指針一致性吱肌。您不應該重載從Objective-C導入的類型的平等性或一致性運算符痘拆。

NSObject類提供的isEqual:的基本實現等同于通過指針相等性的身份檢查。您可以在一個子類覆蓋isEqual:方法氮墨,使Swift和Objective-C API基于對象的內容而不是其指針一致性纺蛆。有關比較邏輯的詳細信息吐葵,請參閱 *** Cocoa Core Competencies*** 的 Object comparison 章節(jié)。

注意
Swift自動提供平等性和一致性運算符的邏輯互補實現(!=!==)桥氏。這些不應該被重載温峭。

哈希

Swift將聲明為NSDictionary的沒有指定鍵類型的Objective-C對象導入為Key類型為AnyHashableDictionary類型。類似的字支,不具有類型限定的NSSet類型被導入為Element類型為AnyHashableSet類型凤藏。如果NSDictionaryNSSet聲明分別對其鍵或對象類型進行參數化,則使用該類型堕伪。例如揖庄,給定以下Objective-C聲明:

@property NSDictionary *unqualifiedDictionary;
@property NSDictionary< NSString *, NSDate *> *qualifiedDictionary;

@property NSSet *unqualifiedSet;
@property NSSet< NSString *> *qualifiedSet;

以下是Swift導入它們的方式:

var unqualifiedDictionary: [AnyHashable: Any]
var qualifiedDictionary: [String: Date]

var unqualifiedSet: Set< AnyHashable>
var qualifiedSet: Set< String>

當導入未指定或id這樣的不能導入為Any的Objective-C類型時,Swift會使用AnyHashable類型欠雌,因為類型需要遵守Hashable協(xié)議蹄梢。該AnyHashable類型是從任何Hashable類型隱式轉換的,您可以使用as?as!運算符將AnyHashable轉換為更具體的類型桨昙。

有關更多信息检号,請參閱AnyHashable章節(jié)腌歉。



Swift類型兼容性

當從Objective-C類創(chuàng)建Swift類時蛙酪,可以從Objective-C中自動獲得與Objective-C兼容的類及其成員屬性、方法翘盖、下標和構造器桂塞。這不包括僅Swift擁有的功能,例如列出的功能:

  • 泛型
  • 元組
  • 在Swift中定義的不包含Int原始值類型的枚舉
  • Swift中定義的結構
  • Swift中定義的頂級函數
  • Swift中定義的全局變量
  • 在Swift中定義的類型別名
  • Swift風格的變體
  • 嵌套類型
  • 柯里化函數

Swift API被翻譯成Objective-C馍驯,類似于Objective-C API如何翻譯成Swift阁危,但反過來:

  • Swift可選類型注釋為__nullable
  • Swift非選擇類型被注釋為__nonnull
  • Swift常量的存儲屬性和計算屬性成為只讀Objective-C屬性
  • Swift變量的存儲屬性成為讀寫Objective-C屬性
  • Swift類屬性成為具有class屬性的Objective-C屬性
  • Swift類方法成為Objective-C類方法
  • Swift構造器和實例化方法成為Objective-C實例方法
  • 拋出錯誤的Swift方法成為具有NSError **參數的Objective-C方法。如果Swift方法沒有參數汰瘫,AndReturnError:將附加到Objective-C方法名稱上狂打,否則附加error:。如果Swift方法未指定返回類型混弥,則相應的Objective-C方法具有BOOL返回類型趴乡。如果Swift方法返回不可選類型,則相應的Objective-C方法具有可選的返回類型蝗拿。

例如晾捏,參考以下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
 }
}

以下是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



注意
您不能在Objective-C中繼承Swift的類

在Objective-C中配置Swift的接口

在某些情況下,您需要對Swift API如何暴露于Objective-C進行更細粒度的控制哀托。您可以使用@objc(name)屬性來更改接口中暴露于Objective-C代碼的類惦辛,屬性,方法仓手,枚舉類型或枚舉情況聲明的名稱胖齐。

例如玻淑,如果你的Swift類名稱中包含Objective-C不支持的字符,則您可以提供其在Objective-C中的替代字符市怎。如果您為Swift函數提供Objective-C名稱岁忘,請使用Objective-C選擇器語法。記著在參數跟隨選擇器的地方加一個冒號(:)区匠。

@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類中使用@objc(name)屬性時干像,該類可在Objective-C中使用,而無需任何命名空間驰弄。因此麻汰,當將可歸檔的Objective-C類遷移到Swift時,此屬性也將非常有用戚篙。因為歸檔對象將其類的名稱存儲在存檔中五鲫,您應該使用@objc(name)屬性來指定與Objective-C類相同的名稱,以便舊的存檔可以由新的Swift類取消存檔岔擂。

注意
相反的位喂,Swift還提供了一個@nonobjc屬性,這使得在Objective-C中不能使用Swift聲明乱灵。您可以使用它來解決橋接方法的循環(huán)性塑崖,并允許由Objective-C導入的類的方法重載。如果通過不能在Objective-C中表示的Swift方法覆蓋Objective-C方法痛倚,例如將參數指定為變量规婆,則該方法必須標記為@nonobjc

需要動態(tài)調度

可以從Objective-C調用的Swift API必須通過動態(tài)調度才能使用蝉稳。然而抒蚜,當從Swift代碼調用這些API時,動態(tài)調度的可用性并不能阻止Swift編譯器選擇更有效的調度方法耘戚。

您使用@objc屬性及dynamic修飾符要求通過Objective-C運行時動態(tài)調度成員的訪問嗡髓。要求這種動態(tài)調度是很沒有必要的。然而收津,對于使用Objective-C運行時的API饿这,比如鍵值觀察者(KVO)或者 method_exchangeImplementations 方法,這一類在運行時需要動態(tài)替換具體實現的情況朋截,動態(tài)調度是很有必要的蛹稍。

dynamic修飾符標記的聲明也必須用@objc屬性顯式標記,除非@objc屬性被聲明的上下文隱式添加部服。有關什么時候@objc隱式添加屬性的信息唆姐,請參閱 The Swift Programming Language (Swift 4)Declaration Attributes 章節(jié)。



選擇器

在Objective-C中廓八,選擇器是一種引用Objective-C方法名稱的類型奉芦。在Swift中赵抢,Objective-C選擇器由Selector結構表示,可以使用#selector表達式構造声功。要為可以從Objective-C調用的方法創(chuàng)建一個選擇器烦却,請傳遞方法的名稱,例如#selector(MyViewController.tappedButton(_:))先巴。要為屬性的Objective-C gettersetter方法構造一個選擇器其爵,請傳遞以getter:setter:標簽為前綴的屬性名稱,例如#selector(getter: MyViewController.myButton)伸蚯。

import UIKit
class MyViewController: UIViewController {
 let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
 
 override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
  super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
  let action = #selector(MyViewController.tappedButton)
  myButton.addTarget(self, action: action, forControlEvents: .touchUpInside)
 }
 
 @objc func tappedButton(_ sender: UIButton?) {
  print("tapped button")
 }
 
 required init?(coder: NSCoder) {
  super.init(coder: coder)
 }
}



注意
可以對Objective-C方法加上括號摩渺,并且可以使用as運算符來消除重載函數之間的歧義,例如#selector(((UIView.insert(subview:at:)) as (UIView) -> (UIView, Int) -> Void))剂邮。

Objective-C方法的不安全調用

您可以使用具有perform(_:)方法或其變體之一的選擇器在Objective-C兼容對象上調用Objective-C方法摇幻。調用使用選擇器的方法本質上是不安全的,因為編譯器不能對結果做出任何保證挥萌,甚至不能保證對象是否響應選擇器绰姻。因此,除非您的代碼特別依賴于Objective-C運行時提供的動態(tài)方法解析引瀑,否則強烈建議不要使用這些API狂芋。例如,如果你需要在其界面中實現使用目標動作設計模式的類伤疙,那么這樣時恰當的银酗,如同NSResponder所做的辆影。而在大多數情況下徒像,將對象轉化為AnyObject以及在方法調用時使用可選鏈接更加安全和方便,如同id兼容性章節(jié)所述蛙讥。

因為通過執(zhí)行選擇器返回的值的類型和所有權在編譯時無法確定锯蛀,所以同步執(zhí)行的選擇器的方法,例如perform(_:)次慢,返回一個將隱式解包的可選項指到一個AnyObject實例上的非托管指針(Unmanaged< AnyObject>!)旁涤。相反,在特定線程上執(zhí)行選擇器或延遲之后的方法迫像,例如 perform(_:on:with:waitUntilDone:modes:)perform(_:with:afterDelay:)劈愚,不返回值。有關詳細信息闻妓,請參閱非托管對象菌羽。

let string: NSString = "Hello, Cocoa!"
let selector = #selector(NSString.lowercased(with:))
let locale = Locale.current
if let result = string.perform(selector, with: locale) {
 print(result.takeUnretainedValue())
}
// Prints "hello, cocoa!"

嘗試調用一個對象的無法識別的選擇器的方法會導致接收方調用doesNotRecognizeSelector(_:),默認情況下會引發(fā)NSInvalidArgumentException異常由缆。

let array: NSArray = ["delta", "alpha", "zulu"]

// Not a compile-time error because NSDictionary has this selector.
let selector = #selector(NSDictionary.allKeysForObject)

// Raises an exception because NSArray does not respond to this selector.
array.perform(selector)



鍵和鍵路徑

在Objective-C中注祖,是用于標識對象的特定屬性的字符串猾蒂。鍵路徑是一個由用點作分隔符的鍵組成的字符串,用于指定一個連接在一起的對象性質序列。鍵路徑經常用于鍵值對編碼(KVC)是晨,一種間接訪問對象的屬性使用字符串來標識屬性的機制肚菠。鍵路徑也常用于鍵值對觀察者(KVO),一種可以在另一個對象的屬性更改時直接通知對象的機制罩缴。

在Swift中蚊逢,您可以使用鍵路徑表達式創(chuàng)建訪問屬性的關鍵路徑。例如箫章,您可以使用\Animal.namekey-path表達式來訪問下面顯示nameAnimal類的屬性时捌。使用鍵路徑表達式創(chuàng)建的關鍵路徑包括有關其引用的屬性的類型信息。將實例的關鍵路徑應用于實例會產生與直接訪問該實例屬性相同類型的值炉抒。鍵路徑表達式接受屬性引用和鏈接屬性引用奢讨,例如\Animal.name.count

class Animal: NSObject {
 @objc var name: String
 
 init(name: String) {
  self.name = name
 }
}

let llama = Animal(name: "Llama")
let nameAccessor = \Animal.name
let nameCountAccessor = \Animal.name.count

llama[keyPath: nameAccessor]
// "Llama"
llama[keyPath: nameCountAccessor]
// "5"

在Swift中焰薄,你也可以使用#KeyPath字符串表達式來創(chuàng)建可以在如value(forKey:)value(forKeyPath:)之類的KVC方法中使用的編譯檢查鍵和鍵路徑拿诸,以及類似addObserver(_:forKeyPath:options:context:)的KVO方法。#keyPath字符串表達式允許鏈式方法或屬性引用塞茅。它還支持通過鏈中的可選值鏈接亩码,例如#keyPath(Person.bestFriend.name)。與使用鍵路徑表達式創(chuàng)建的關鍵路徑不同野瘦,使用#keyPath字符串表達式創(chuàng)建的關鍵路徑不會傳遞有關其引用到接受關鍵路徑的API的屬性或方法的類型信息描沟。

注意
#keyPath字符串表達式的語法類似于#selector表達的語法,如選擇器章節(jié)所述鞭光。



class Person: NSObject {
 @objc var name: String
 @objc var friends: [Person] = []
 @objc 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"]






文章翻譯自Apple Developer Page : Using Swift with Cocoa and Objective-C (Swift 4)
方便大家學習之用吏廉,如果翻譯存在錯誤,歡迎指正惰许。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末席覆,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子汹买,更是在濱河造成了極大的恐慌佩伤,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晦毙,死亡現場離奇詭異生巡,居然都是意外死亡,警方通過查閱死者的電腦和手機见妒,發(fā)現死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門孤荣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事垃环⊙悖” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵遂庄,是天一觀的道長寥院。 經常有香客問我,道長涛目,這世上最難降的妖魔是什么秸谢? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮霹肝,結果婚禮上估蹄,老公的妹妹穿的比我還像新娘。我一直安慰自己沫换,他們只是感情好臭蚁,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讯赏,像睡著了一般垮兑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上漱挎,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天系枪,我揣著相機與錄音,去河邊找鬼磕谅。 笑死私爷,一個胖子當著我的面吹牛,可吹牛的內容都是我干的膊夹。 我是一名探鬼主播衬浑,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼割疾!你這毒婦竟也來了嚎卫?” 一聲冷哼從身側響起嘉栓,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤宏榕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后侵佃,有當地人在樹林里發(fā)現了一具尸體麻昼,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年馋辈,在試婚紗的時候發(fā)現自己被綠了抚芦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖叉抡,靈堂內的尸體忽然破棺而出尔崔,到底是詐尸還是另有隱情,我是刑警寧澤褥民,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布季春,位于F島的核電站,受9級特大地震影響消返,放射性物質發(fā)生泄漏载弄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一撵颊、第九天 我趴在偏房一處隱蔽的房頂上張望宇攻。 院中可真熱鬧,春花似錦倡勇、人聲如沸逞刷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罕偎。三九已至,卻和暖如春囚衔,著一層夾襖步出監(jiān)牢的瞬間镇辉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工厅目, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留番枚,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓损敷,卻偏偏與公主長得像葫笼,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子拗馒,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容