章節導航:
Swift開發指南:使用Swift與Cocoa和Objective-C(Swift 4) - 1.入門
Swift開發指南:使用Swift與Cocoa和Objective-C(Swift 4) - 2.互通性
與Objective-C API進行交互
互操作性
是能夠在任何一個方向上與Swift和Objective-C進行接口,讓您訪問并使用以其他語言的文件中的一些代碼。當您開始將Swift集成到應用程序開發工作流程中時,了解如何利用互操作性來重新定義、改進和增強編寫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語法之間的區別更為明顯。
在Objective-C中,您可以執行以下操作:
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))
這些UITableView
和UITextField
對象是您在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中,此模式內置于稱為可失敗構造器的語言功能中。
系統框架中的許多Objective-C初始化程序已被審計,可指示構造是否可以失敗。您可以在你的Objective-C類中使用nullability annotations
指出構造方法是否可以失敗,如可空性和可選項章節所述。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屬性:
- 具有可空屬性屬性(
nonnull
,nullable
和null
和resettable
)的屬性作為Swift屬性導入,具有可選或非可選類型,如可空項和可選項所述。 - 具有
readonly
屬性屬性的屬性將導入為具有getter({ get })
的Swift計算屬性。 - 具有
weak
屬性屬性的屬性將導入為標有weak
關鍵字(weak var)
的Swift屬性。 - 除
weak
外與所有權有關的屬性(即,assign
,copy
,strong
,或unsafe
_unretained
)被導入為合適的Swift屬性存儲。 -
class
屬性導入為Swift類型屬性。 - 原子屬性(
atomic
和nonatomic
)不會反映在相應的Swift屬性聲明中,但是從Swift訪問導入的屬性時,Objective-C實現的原子屬性仍然保持不變。 - Swift忽略 Accessor屬性(
getter=
和setter=
)。
您可以使用點語法訪問Swift中的Objective-C對象上的屬性,使用不帶括號的屬性名稱。
例如,您可以使用以下代碼設置對象的屬性textColor
和text
屬性UITextField
:
myTextField.textColor = .darkGray
myTextField.text = "Hello world"
返回值并且不帶參數的Objective-C方法可以像使用點語法的Objective-C屬性一樣調用。但是,由Swift作為實例方法導入,因為只有Objective-C @property
聲明由Swift作為屬性導入。方法被導入并按照“ 使用方法”章節中的描述進行調用。
使用方法
您可以使用點語法從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
但是,如果強制降級失敗,則會觸發運行時錯誤:
let myDate = lastRefreshDate as! String // Error
動態方法查找
Swift還包括一種AnyObject
表示某種對象的類型,并具有動態查找任何@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中,嘗試調用不存在的方法會觸發無法識別的選擇器錯誤。
例如,以下代碼編譯時沒有編譯器警告,但會在運行時觸發錯誤:
myObject.character(at: 5)
// crash, myObject doesn't respond to that method
Swift使用可選項來防范這種不安全的行為。當您調用AnyObject
類型值的方法時,該方法調用的行為就像一個隱式解包的可選項。您可以使用與協議中可選方法相同的可選鏈接語法來可選地調用AnyObject
的方法。
例如,在下面列舉的代碼中,不執行第一行和第二行,因為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
章節
Objective-C可以使用可空性注釋來指定參數類型、屬性類型或返回類型可以有NULL
或nil
值。單個類型聲明可以使用_Nullable
和_Nonnull
修飾來進行靜態編譯時檢查,單個屬性聲明可以使用nullable
,nonnull
和null_resettable
修飾來進行檢查,或整個區域可使用NS_ASSUME_NONNULL_BEGIN
和NS_ASSUME_NONNULL_END
宏來設置可空性檢查。如果沒有為類型提供可空性信息,則Swift不能區分可選項和不可選項引用,并將其導入為隱式解包可選項。
- 聲明為
nonnullable
或有_Nonnull
修飾、或處在整個靜態編譯檢查區域的類型,會被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系統框架(包括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
由于參數valueFromSwift
是id
類型的,它以Swift的Any
類型導入下面的Swift代碼。但是,由于Any
在預期之中時傳遞可選項并不常見,所以傳遞給logSomeValue(_:)
類方法的可選項被顯式轉換為Any
類型,這會使編譯警告靜默。
let someValue: String? = "Bridge me, please."
let nilValue: String? = nilOptionalBridging.logSomeValue(someValue as Any) // Bridge me, please.
OptionalBridging.logSomeValue(nilValue as Any) // Received an NSNull value.
協議限制類
由一個或多個協議限定的Objective-C類由Swift作為協議組合類型導入。例如,給定以下引用視圖控制器的Objective-C屬性:
@property UIViewController< UITableViewDataSource, UITableViewDelegate> * myController;
以下是Swift導入的方式:
var myController: UIViewController & UITableViewDataSource & UITableViewDelegate
Objective-C協議限制的元類由Swift作為協議元類型導入。例如,給定以下Objective-C方法對指定的類執行操作:
- (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泛型參數化類指定了協議限定條件,則導入的Swift類具有一個約束,要求該類型符合指定的協議。對于非特異化的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中定義的行為。您可以從系統框架或您自己的自定義類型之一定義類型上的擴展。只需導入相應的模塊,并使用與Objective-C中使用的名稱相同的名稱來引用類,結構或枚舉。
例如,您可以根據UIBezierPath
提供的邊長和起點,擴展類以創建具有等邊三角形的簡單貝塞爾路徑。
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()
}
}
您可以使用擴展來添加屬性(包括類和靜態屬性)。但是,必須計算這些屬性; 擴展不能將存儲的屬性添加到類,結構或枚舉中。
此示例將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
您也可以使用擴展來添加協議一致性而不對其進行子類化。如果在Swift中定義了協議,那么您也可以將它添加到結構或枚舉中,無論是在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中變量的默認行為。
避免獲取自己時產生強引用循環
在Objective-C中,如果您需要在block中獲取self
,那么考慮內存管理的含義就顯得很重要。
block保留對任何獲取的對象的強引用,包括self
。如果self
保持對block的強引用,例如拷貝屬性,這將創建一個強引用循環。為了避免這種情況,你可以讓block獲取一個弱引用self
:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
};
像Objective-C中的block一樣,Swift中的閉包也保持對任何獲取的對象的強引用,包括self
。為了防止強引用循環,您可以在閉包的捕獲列表里指定self
為unowned
:
self.closure = { [unowned self] in
self.doSomething()
}
欲了解更多信息,請參見 The Swift Programming Language (Swift 4) 的Resolving Strong Reference Cycles for Closures 章節。
對象比較
您可以在Swift中的兩個對象之間進行兩種截然不同的比較。第一種,平等性(==)
,比較對象的內容。第二種,一致性(===)
確定常量或變量是否引用相同的對象實例。
Swift提供了==
和===
操作符的默認實現,并為從NSObject
類派生的對象采用Equatable
協議。==
操作符的默認實現調用isEqual:
方法,===
運算符的默認實現檢查指針一致性。您不應該重載從Objective-C導入的類型的平等性或一致性運算符。
由NSObject
類提供的isEqual:
的基本實現等同于通過指針相等性的身份檢查。您可以在一個子類覆蓋isEqual:
方法,使Swift和Objective-C API基于對象的內容而不是其指針一致性。有關比較邏輯的詳細信息,請參閱 *** Cocoa Core Competencies*** 的 Object comparison 章節。
注意
Swift自動提供平等性和一致性運算符的邏輯互補實現(!=
和!==
)。這些不應該被重載。
哈希
Swift將聲明為NSDictionary
的沒有指定鍵類型的Objective-C對象導入為Key
類型為AnyHashable
的Dictionary
類型。類似的,不具有類型限定的NSSet
類型被導入為Element
類型為AnyHashable
的Set
類型。如果NSDictionary
或NSSet
聲明分別對其鍵或對象類型進行參數化,則使用該類型。例如,給定以下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
協議。該AnyHashable
類型是從任何Hashable
類型隱式轉換的,您可以使用as?
和as!
運算符將AnyHashable
轉換為更具體的類型。
有關更多信息,請參閱AnyHashable章節。
Swift類型兼容性
當從Objective-C類創建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聲明。您可以使用它來解決橋接方法的循環性,并允許由Objective-C導入的類的方法重載。如果通過不能在Objective-C中表示的Swift方法覆蓋Objective-C方法,例如將參數指定為變量,則該方法必須標記為@nonobjc
。
需要動態調度
可以從Objective-C調用的Swift API必須通過動態調度才能使用。然而,當從Swift代碼調用這些API時,動態調度的可用性并不能阻止Swift編譯器選擇更有效的調度方法。
您使用@objc
屬性及dynamic
修飾符要求通過Objective-C運行時動態調度成員的訪問。要求這種動態調度是很沒有必要的。然而,對于使用Objective-C運行時的API,比如鍵值觀察者(KVO)或者 method_exchangeImplementations
方法,這一類在運行時需要動態替換具體實現的情況,動態調度是很有必要的。
用dynamic
修飾符標記的聲明也必須用@objc
屬性顯式標記,除非@objc
屬性被聲明的上下文隱式添加。有關什么時候@objc
隱式添加屬性的信息,請參閱 The Swift Programming Language (Swift 4) 的 Declaration Attributes 章節。
選擇器
在Objective-C中,選擇器是一種引用Objective-C方法名稱的類型。在Swift中,Objective-C選擇器由Selector結構表示,可以使用#selector
表達式構造。要為可以從Objective-C調用的方法創建一個選擇器,請傳遞方法的名稱,例如#selector(MyViewController.tappedButton(_:))
。要為屬性的Objective-C getter
或setter
方法構造一個選擇器,請傳遞以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運行時提供的動態方法解析,否則強烈建議不要使用這些API。例如,如果你需要在其界面中實現使用目標動作設計模式的類,那么這樣時恰當的,如同NSResponder
所做的。而在大多數情況下,將對象轉化為AnyObject
以及在方法調用時使用可選鏈接更加安全和方便,如同id兼容性章節所述。
因為通過執行選擇器返回的值的類型和所有權在編譯時無法確定,所以同步執行的選擇器的方法,例如perform(_:)
,返回一個將隱式解包的可選項指到一個AnyObject
實例上的非托管指針(Unmanaged< AnyObject>!)。相反,在特定線程上執行選擇器或延遲之后的方法,例如 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(_:)
,默認情況下會引發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中,您可以使用鍵路徑表達式創建訪問屬性的關鍵路徑。例如,您可以使用\Animal.namekey-path
表達式來訪問下面顯示name
的Animal
類的屬性。使用鍵路徑表達式創建的關鍵路徑包括有關其引用的屬性的類型信息。將實例的關鍵路徑應用于實例會產生與直接訪問該實例屬性相同類型的值。鍵路徑表達式接受屬性引用和鏈接屬性引用,例如\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.countllama[keyPath: nameAccessor]
// "Llama"
llama[keyPath: nameCountAccessor]
// "5"
在Swift中,你也可以使用#KeyPath
字符串表達式來創建可以在如value(forKey:)
和value(forKeyPath:)
之類的KVC方法中使用的編譯檢查鍵和鍵路徑,以及類似addObserver(_:forKeyPath:options:context:)
的KVO方法。#keyPath
字符串表達式允許鏈式方法或屬性引用。它還支持通過鏈中的可選值鏈接,例如#keyPath(Person.bestFriend.name)
。與使用鍵路徑表達式創建的關鍵路徑不同,使用#keyPath
字符串表達式創建的關鍵路徑不會傳遞有關其引用到接受關鍵路徑的API的屬性或方法的類型信息。
注意
#keyPath
字符串表達式的語法類似于#selector
表達的語法,如選擇器章節所述。
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)
方便大家學習之用,如果翻譯存在錯誤,歡迎指正。