章節(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))
這些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中隐解,此模式內置于稱為可失敗構造器的語言功能中鞍帝。
系統(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屬性:
- 具有可空屬性屬性(
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作為屬性導入。方法被導入并按照“ 使用方法”章節(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可以使用可空性注釋來指定參數類型完沪、屬性類型或返回類型可以有NULL
或nil
值。單個類型聲明可以使用_Nullable
和_Nonnull
修飾來進行靜態(tài)編譯時檢查嵌戈,單個屬性聲明可以使用nullable
覆积,nonnull
和null_resettable
修飾來進行檢查,或整個區(qū)域可使用NS_ASSUME_NONNULL_BEGIN
和NS_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
由于參數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.
協(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)证舟,您可以在閉包的捕獲列表里指定self
為unowned
:
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
類型為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
協(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 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運行時提供的動態(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
表達式來訪問下面顯示name
的Animal
類的屬性时捌。使用鍵路徑表達式創(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.countllama[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)
方便大家學習之用吏廉,如果翻譯存在錯誤,歡迎指正惰许。