1. 程式人生 > >Swift開發指南:使用Swift與Cocoa和Objective-C(Swift 4)

Swift開發指南:使用Swift與Cocoa和Objective-C(Swift 4)

與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))

這些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中,此模式內置於稱為可失敗構造器的語言功能中。

系統框架中的許多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屬性:

  • 具有可空屬性屬性(nonnullnullablenullresettable)的屬性作為Swift屬性匯入,具有可選或非可選型別,如可空項和可選項所述。
  • 具有readonly屬性屬性的屬性將匯入為具有getter({ get })的Swift計算屬性。
  • 具有weak屬性屬性的屬性將匯入為標有weak關鍵字(weak var)的Swift屬性。
  • weak外與所有權有關的屬性(即,assigncopystrong,或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作為屬性匯入。方法被匯入並按照“ 使用方法”章節中的描述進行呼叫。


使用方法

您可以使用點語法從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可以使用可空性註釋來指定引數型別、屬性型別或返回型別可以有NULLnil值。單個型別宣告可以使用_Nullable_Nonnull修飾來進行靜態編譯時檢查,單個屬性宣告可以使用nullablenonnullnull_resettable修飾來進行檢查,或整個區域可使用NS_ASSUME_NONNULL_BEGINNS_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

由於引數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.

協議限制類

由一個或多個協議限定的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。為了防止強引用迴圈,您可以在閉包的捕獲列表裡指定selfunowned

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型別為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協議。該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 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執行時提供的動態方法解析,否則強烈建議不要使用這些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表示式來訪問下面顯示nameAnimal類的屬性。使用鍵路徑表示式建立的關鍵路徑包括有關其引用的屬性的型別資訊。將例項的關鍵路徑應用於例項會產生與直接訪問該例項屬性相同型別的值。鍵路徑表示式接受屬性引用和連結屬性引用,例如\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字串表示式來建立可以在如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)
方便大家學習之用,如果翻譯存在錯誤,歡迎指正。

相關推薦

Swift開發指南使用SwiftCocoaObjective-C(Swift 4)

與Objective-C API進行互動 互操作性是能夠在任何一個方向上與Swift和Objective-C進行介面,讓您訪問並使用以其他語言的檔案中的一些程式碼。當您開始將Swift整合到應用程式開發工作流程中時,瞭解如何利用互操作性來重新定義、改進和增

Swift WKWebView(二)iOSjs互動

在上一篇中我們介紹了Swift下WKWebView的基本使用方法,下面總結一下iOS與js互動的實現,最終的頁面效果如下圖所示: 其中,js有關程式碼如下: function navButtonAction(name,age){

《iOS開發指南從零基礎到App Store上架》第2版第3版的區別

新浪微博:東門兜 第3章 UIView與檢視 新書中添加了“3.3 動作與輸出口”。 新書中去掉了 “3.6 螢幕滾動控制元件ScrollView” 放入第4章 “3.10 屏幕布局” 放入第4章 “3.11 選擇器” 放入第5章 “3.12 集合

Serverless 應用開發指南基於 Serverless Lambda 的微信公共平臺

Serverless 在事件驅動方面具有天然的優勢,其中之一就是聊天機器人。可要做聊天機器人不是一件容易的事,微信和 QQ 都只能用 Hack 的方式進行。 於是,便想到微信公眾號是不是一個更好的選擇。當用戶輸入一個關鍵詞時,做出相應的回覆。總體上來說,他們之間是差不多的。這個時候,就可以開始嘗試一個在

C++開發EOS基礎指南叠代器Lambda表達式

i++ 深入學習 數字 variable java工程師 結束 有意義 pen 需要 讓我們來談談叠代器,它是一個非常有用的工具,在整個EOS代碼庫中大量使用。如果您來自JavaScript背景,您可能已經熟悉叠代器,就像它們用於循環一樣。叠代器的關鍵概念是提供一種更好的方

Using Swift with Cocoa and Objective-C下載

target cocoa 下載地址 obj swift nbsp 地址 bject uil 《Using Swift with Cocoa and Objective-C Building App 》 下載地址 http://download.csdn.net/

SwiftObjective-C混合編程——Swift調用OC

分享 發現 load 另一個 == 方法 代碼 swift 應用 在iOS應用的開發中。Swift必將代替OC,兩者的趨勢是“短期共存,長期代替”。但曾經有太多的代碼是用OC語言完畢的,而Swift的開發也從 OC中繼承了非常多的特性。兩者也是有非常多的類

Swift 4 Objective-C 混合編程(一) 快速起步

命名方式 import 編譯器 選擇性 工程 Swift 4 和 Objective-C 在同一個工程裏的混搭編程的方法你可以在 xcode 裏同時使用 Swift 和 Objective-C(以下簡稱OC)來寫代碼,混搭編程的好處很多,比如允許大量代碼的復用,在性能和開發效率之間找到平衡

『幹貨』分享你最喜歡的技巧提示(Xcode,objective-c,swift,c...等等)

%d 維護 函數名 self med interface oci pla track 親愛的讀者們,你們好 !年底將近,分享從過去一年你最喜歡的技巧和建議作為禮物送給新手們。提交你的最喜歡的迅速或objc瑣事,實用的提示,意外的發現,實用的解決方法,沒用

Ionic開發1安裝新建項目

onclick 安卓 rdo start 開發工具 布局 情況 技術分享 成了 我們為什麽要選用Ionic(Hybrid)呢? 不必說那些花裏胡哨的話,我曾經是搞Native Android開發的,兩者的區別還是有體會的 比如:我要做一個手機qq好友列表類似得布局,只是布局

編程語言對比分析PythonJavaJavaScript(圖)

最大 python 服務 dev 破壞 fff 對比分析 可能 分析 編程語言對比分析:Python與Java和JavaScript(圖):憑什麽說“Python 太慢,Java 太笨拙,我討厭 JavaScript”?[圖]編程語言生而為何?我們人類從原始社會就是用語言表

快速開發趣事小白妹紙的漫漫擼碼路(1)

快速開發 快速開發平臺 快速開發框架 最近,活兒又來了,真是屋漏偏逢連陰雨,經過半年的洗禮能用得上的開發就我一個人了,怎麽辦呢,唉呀媽呀,腦殼疼腦殼疼...

Flutter學習指南互動、手勢動畫

Flutter學習指南 系列文章 UI佈局和控制元件 熟悉Dart語言 編寫第一個應用 開發環境搭建 在這一篇文章中,我們首先介紹手勢事件的處理和頁面跳轉的基礎知識,然後通過實現一個 echo 客戶端的前端頁面來加強學習;最後我們再學習內建的動畫 Widget 以及如何自定義動畫效果。 手勢處理

Linux 桌面玩家指南14. 數值計算符號計算

特別說明:要在我的隨筆後寫評論的小夥伴們請注意了,我的部落格開啟了 MathJax 數學公式支援,MathJax 使用$標記數學公式的開始和結束。如果某條評論中出現了兩個$,MathJax 會將兩個$之間的內容按照數學公式進行排版,從而導致評論區格式混亂。如果大家的評論中用到了$,但是又不是為了使用數學

Flutter學習指南檔案、儲存網路

Flutter學習指南 互動、手勢和動畫 UI佈局和控制元件 熟悉Dart語言 編寫第一個應用 開發環境搭建 本篇文章我們先學習 Flutter IO 相關的基礎知識,然後在 Flutter學習指南:互動、手勢和動畫 的基礎上,繼續開發一個 echo 客戶端。由於日常開發中 HTTP 比 socket

軟體開發模式瀑布敏捷

瀑布和敏捷不是什麼新概念,這裡只是個人在團隊合作中不得不去思考而做的歸納和總結,同時記錄自己曾經踩過的坑,新瓶裝舊酒,希望對你有所啟發。 瀑布模式   瀑布模型是比較傳統一種開發模式,特別是在2B的傳統企業,包括ERP,MES,WMS,CRM,OA,IBMS等系統當中可以經常見到他

軟件開發模式瀑布敏捷

減少 有時 能力 blog alt ima 瀑布模型 線上 時間差 瀑布和敏捷不是什麽新概念,這裏只是個人在團隊合作中不得不去思考而做的歸納和總結,同時記錄自己曾經踩過的坑,新瓶裝舊酒,希望對你有所啟發。 瀑布模式   瀑布模型是比較傳統一種開發模式,特

Linux 桌面玩家指南08. 使用 GCC GNU Binutils 編寫能在 x86 真實模式執行的 16 位程式碼

特別說明:要在我的隨筆後寫評論的小夥伴們請注意了,我的部落格開啟了 MathJax 數學公式支援,MathJax 使用$標記數學公式的開始和結束。如果某條評論中出現了兩個$,MathJax 會將兩個$之間的內容按照數學公式進行排版,從而導致評論區格式混亂。如果大家的評論中用到了$,但是又不是為了使用數學公式

Spring 10springMybatisjdbc結合

需要jar包 ※ Spring與jdbc結合     jdbc程式設計不變,主要是Connection物件的維護,即配置並使用資料來源     1)<!-- 基於jdk的規範資料來源 -->      <bean name="dataSour

《零基礎入門學習Python》(40)類物件一些物件相關的BIF

知識點: 1.issubclass(class,classinfo)#class是classinfo的子類則返回True,相反則返回False 注意: 1.這種檢查是非嚴肅性的檢查,他會把自身當成自身的子類 2.classinfo可以是類物件組成的元組,只要cla