Swift with Cocoa and Object-C(第二部分)

Lightweight Generics(輕量級泛型)###

OC類型聲明用輕量級泛型規(guī)范等同于Swift內(nèi)容的限制類型溯祸。例如,下面OC的屬性聲明:

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

轉(zhuǎn)化為Swift:

var dates: [Date]
var cacheData: Cache<AnyObject, DiscardableContent>
var supportedLocales: [String: [Locale]]

在OC中規(guī)范類類等同于Swift帶相同類型參數(shù)通用類馁害。所有OC泛型類型等同于Swift符合(T: AnyObject)的類型約束。如果OC的泛型對類限定,那么轉(zhuǎn)化的Swift類有約束,也約束了子類的類型秕脓。如果OC泛型進行協(xié)議限定,那么轉(zhuǎn)化為Swift類有約束儒搭,要求轉(zhuǎn)化為這個協(xié)議類型吠架。對于一個沒有具體說明的OC類型,Swift對類的類型約束表明通用規(guī)范搂鲫。例如傍药,下列OC類和類別聲明:

@interface List<T: ID<NSCopying>> : NSObject
- (List<T> *)listByAppendingItemsInList:(List<T> *)otherList;
@end

@interface ListContainer : NSObject
- (List<NSValue *> *)listOfValue;
@end

@interface ListContainer (ObjectList)
- (List *)listOfObjects;
@end

轉(zhuǎn)換為Swift:

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

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

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

Extensions(擴展)###

Swift的擴展(extension)類似OC的類別(category)。擴展用來增加類魂仍、結(jié)構(gòu)體拐辽,和枚舉的行為。你可以定義一個系統(tǒng)類型或者自定義類型的擴展擦酌。導(dǎo)入相關(guān)的模塊俱诸,然后通過相同名字關(guān)聯(lián)類、結(jié)構(gòu)體或者枚舉赊舶。

例如睁搭,你可以擴展UIBezierPath類,基于提供的參數(shù)邊長和起始點來制作一個等邊三角形的貝塞爾路徑笼平,

extension UIBezierPath {
    convenience init(triangleSideLenght: 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)屬性).然而园骆,這些屬性必須計算過;擴展不能對類寓调,結(jié)構(gòu)體或者枚舉增加存儲屬性锌唾。

這個例子擴展了CGRect結(jié)構(gòu)體,主要包含了計算area屬性:

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

你不可以用擴展來否決在OC類型里存在的方法或?qū)傩浴?/p>

Closures(閉包)###

OC的blocks會自動導(dǎo)入為Swift的閉包捶牢。例如鸠珠,下面是一個OC中的block變量:

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

在Swift中的形式為:

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

Swift的閉包和OC的blocks是一樣的巍耗,所以你可以把一個 Swift 閉包傳遞給一個把 block 作為參數(shù)的 Objective-C 函數(shù)。Swift 閉包與函數(shù)具有互通的類型渐排,所以你甚至可以傳遞 Swift 函數(shù)的名字炬太。

閉包與 blocks 語義上想通但是在一個地方不同:變量是可以直接改變的,而不是像 block 那樣會拷貝變量驯耻。換句話說亲族,Swift 中變量的默認(rèn)行為與 Objective-C 中 __block 變量一致。

Avoiding Strong Reference Cycles When Capturing self(用Self時如何避免循環(huán)引用)###

在OC中可缚,如果你需要在block中用self霎迫,考慮內(nèi)存管理是很重要的。

Blocks給代碼塊中的對象維持強引用,包括self帘靡。如果self在block中維持強引用知给,就像一個屬性copy,這將造成強循環(huán)引用描姚。為了避免這種情況涩赢,你可以用對self進行弱引用:

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

在Swift中,閉包也會給代碼塊中的對象進行強引用轩勘,包括self筒扒。為了避免強引用,你可以在閉包中標(biāo)注selfunowned:

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

Object Comparison(對象比較)###

在Swift中有兩種方式類比較兩個對象绊寻。第一種花墩,使用(==),判斷兩個對象的內(nèi)容。第二種澄步,使用(===)冰蘑,判斷常量或變量是否為同一個實例對象。

Swift已經(jīng)實現(xiàn)了=====的操作驮俗。調(diào)用isEqual:方法來實現(xiàn)==操作懂缕,并且實現(xiàn)===操作是檢查指針相等。

NSObject 類僅僅做了身份的比較王凑,所以你需要在你自己的類中重新實現(xiàn) isEqual: 方法搪柑。因為你可以直接傳遞 Swift 對象給 Objective-C 的 API,你也應(yīng)該為這些對象實現(xiàn)自定義的isEqual:方法索烹,如果你希望比較兩個對象的內(nèi)容是否相同而不是僅僅比較他們是不是由相同的對象派生工碾。

Hashing###

Swift采用Hashable協(xié)議,并且規(guī)定NSObject基類默認(rèn)實現(xiàn)hashValue屬性百姓。hashValue屬性的實現(xiàn)調(diào)用了hash屬性

NSObject的子類自定義實現(xiàn)isEquals:方法也應(yīng)實現(xiàn)hash屬性渊额。

Swift Type Compatibility(Swift類型兼容性)###

當(dāng)你定義了一個繼承Objective-C 類的 Swift 類,這個類和他的成員屬性,方法旬迹,下標(biāo)火惊,和初始化-它們與OC是兼容的。下面的這些不包括Swift的特性:

  • Generics
  • 元組(Tuples)
  • 在Swift中不帶Intraw value類型的枚舉
  • 在Swift中定義的結(jié)構(gòu)體
  • 在Swift中高級函數(shù)
  • 在Swift中全局變量
  • Typealiases defined in Swift
  • Swift-style variadics
  • Nested types
  • Curried functions

Swift Apis被翻譯成OC類似于如何將OC APIs翻譯為Swift奔垦,但反過來:

  • Swift optional types are annotated as __nullable.
  • Swift nonoptional types are annotated as __nonnull.
  • Swift constant stored properties and computed properties become read-only Objective-C properties.
  • Swift variable stored properties become read-write Objective-C properties.
  • Swift type properties become Objective-C properties with the class property attribute.
  • Swift type methods become Objective-C class methods.
  • Swift initializers and instance methods become Objective-C instance methods.
  • Swift methods that throw errors become Objective-C methods with an NSError ** parameter. If the Swift method has no parameters, AndReturnError: is appended to the Objective-C method name, otherwise error: is appended. If a Swift method does not specify a return type, the corresponding Objective-C method has a BOOL return type. If the Swift method returns a nonoptional type, the corresponding Objective-C method has an optional return type.

例如屹耐,下列是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)化為OC為:

@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

Configuring Swift Interfaces in Objective-C###

在一些情況下,你需要在OC中使用Swift的API.你可以用@objc(name)來改變類名椿猎,屬性惶岭,方法,枚舉類型犯眠。

例如按灶,如果你Swift特性在OC中不支持,你可以在OC中使用可替代的筐咧。如果你為Swift函數(shù)提供OC名鸯旁,用OC的語法。無論哪個選擇器的參數(shù)嗜浮,記得加(:)羡亩。

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

當(dāng)你在Swift類中使用@objc(name)時,這個類在OC中沒有命名空間危融。這種特性可以在OC轉(zhuǎn)移到Swift時候可以用到。因為歸檔存儲類雷袋,你可以用@objc(name)特性來具體說明OC中相同的類吉殃,以至于之前的歸檔可以使用心得Swift類進行接檔。

Requiring Dynamic Dispatch###

當(dāng)Swift在OC的運行時機制下楷怒,對屬性蛋勺、方法、下標(biāo)和初始化不能保證動態(tài)分發(fā)鸠删。在OC的運行時抱完,Swift編譯器會虛擬或內(nèi)聯(lián)成員訪問對你的代碼進行優(yōu)化。

在OC運行時刃泡,你可以動態(tài)修改你訪問的成員叫動態(tài)分發(fā)巧娱。在OC運行時,KVO或method_exchangeImplementations函數(shù)還是很有必要用這種API的烘贴,
它可以在運行的時候動態(tài)替換一個方法的實現(xiàn)禁添。如果Swift編譯

注意:用dynamic聲明的不能用@nonobjc標(biāo)記屬性。

Selectors###

一個Objective-C 選擇器類型指向一個Objective-C的方法名桨踪。在Swift中老翘, Objective-C 的選擇器被Selector結(jié)構(gòu)體替代,可以用#selector創(chuàng)建。

#selector(MyViewController.tappedButton(sender:))

創(chuàng)建OC屬性的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: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
        let action = #selector(MyViewController.tappedButton)
        myButton.addTarget(self, action: action, forControlEvents: .touchUpInside)
    }
    
    func tappedButton(sender: UIButton?) {
        print("tapped button")
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
}

Unsafe Invocation of Objective-C Methods(OC方法的安全隱患)###

你可以用perform(_:)方法調(diào)用OC的方法墓怀。用這個選擇器調(diào)用方法存在安全隱患,因為編譯器不能保證結(jié)果卫键,甚至不能保證對象響應(yīng)方法捺疼。因此強烈反對使用這些APIs,除非你的代碼基于OC的運行時機制動態(tài)調(diào)用方法永罚。例如啤呼,如果你需要使用target-action設(shè)計模式實現(xiàn)類,用這些可能是合適的呢袱。

perform(:)方法官扣,返回一個AnyObject實例(Unmanaged<AnyObject>!),因為perform選擇器不能再編譯的時候確定返回值類型。像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!"

嘗試調(diào)用一個對象沒有實現(xiàn)的方法會造成對象接收消息并調(diào)用doesNotRecognizeSelector(_:),它會默認(rèn)的拋出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)

Keys and Key Paths###

在OC中卖陵,key是對象特定屬性的id標(biāo)識。一個key path是點分割的keys的字符串张峰,它把對象的一些列屬性進行了串連泪蔫。keys 和 key paths在KVC中經(jīng)常使用,是一個用id標(biāo)識間接訪問對象屬性的機制喘批。keys 和 key paths 也可以用在KVO中撩荣,當(dāng)另外一個對象的屬性改變的時候能夠通知對象的一個機制。

在Swift中饶深,你可以用#keyPath表達(dá)式來讓編譯器檢查keys 和 key paths餐曹,它可以用在KVC方法中,像value(forKey:)value(forKeyPath:)敌厘,KVO方法像addObserver(_:forKeyPath:options:context:).這個#keyPath表達(dá)式允許鏈方法或者屬性引用台猴,像#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"]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市俱两,隨后出現(xiàn)的幾起案子饱狂,更是在濱河造成了極大的恐慌,老刑警劉巖锋华,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嗡官,死亡現(xiàn)場離奇詭異,居然都是意外死亡毯焕,警方通過查閱死者的電腦和手機衍腥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門磺樱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人婆咸,你說我怎么就攤上這事竹捉。” “怎么了尚骄?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵块差,是天一觀的道長。 經(jīng)常有香客問我倔丈,道長憨闰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任需五,我火速辦了婚禮鹉动,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宏邮。我一直安慰自己泽示,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布蜜氨。 她就那樣靜靜地躺著械筛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪飒炎。 梳的紋絲不亂的頭發(fā)上埋哟,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音厌丑,去河邊找鬼定欧。 笑死,一個胖子當(dāng)著我的面吹牛怒竿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播扩氢,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼耕驰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了录豺?” 一聲冷哼從身側(cè)響起朦肘,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎双饥,沒想到半個月后媒抠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡咏花,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年趴生,在試婚紗的時候發(fā)現(xiàn)自己被綠了阀趴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡苍匆,死狀恐怖刘急,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浸踩,我是刑警寧澤叔汁,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站检碗,受9級特大地震影響据块,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜折剃,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一另假、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧微驶,春花似錦浪谴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至扶檐,卻和暖如春凶杖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背款筑。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工智蝠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奈梳。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓杈湾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親攘须。 傳聞我的和親對象是個殘疾皇子漆撞,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容