Objective C轉Swift注意事項(一)合理使用結構體,列舉,extensions
前言
14年Swift剛出的時候開始學習的Swift,後來由於專案需要,一直寫App還是用的OC。最近打算把Swift重新撿起來,這個Objective C轉Swfit系列就當成是我的複習筆記,順便寫成部落格記錄下來吧。
這個系列不是講解Swift基礎,主要是講解OC(以下OC均指的是Objective C)轉過來的同學有些習慣要改變了,才能更好的使用Swift的很多優秀特性。
列舉
通常,你在Objective C中,用列舉NS_ENUM來定義有限的狀態,比如,假如我們要表示一個登入的結果
typedef NS_ENUM(NSInteger,LHLoginResult){
LHLoginResultSucceess, //成功
LHLoginResultFailure, //失敗
LHLoginResultError, //出錯了
};
其實,OC中,列舉更像一個加強版的整型
假如,把這個簡單的轉為Swift,那麼看起來是這樣子的。
enum LoginResult {
case Success
case Failure
case Error
}
如果是這麼寫列舉,你真的是暴殄天物了
Swift列舉是first-class types
,它有很多Swift class具有的特性
- Associated Values 關聯值
- 計算屬性
- 例項方法
- 建構函式
- 遵循協議
- 支援extensions
好了,那麼Swift中,這樣的一個“登入結果”應該如何用列舉來表示呢?
通常,失敗的時候,我們希望知道失敗的原因是啥,出錯的時候,我們希望知道錯誤的原因是啥。
利用Associated Values 關聯值,來實現這一點
於是,這個列舉變成了醬紫
enum LHLoginResult {
case Success
case Failure(message:String)
case Error(error:NSError)
}
等等,現在是三種結果,要是能提供一個介面,只返回給我一個Bool,告訴我登陸成功還是失敗就更好了
利用Extension和計算屬性,我們新增一個方便的介面
extension LoginResult{
var isSuccess:Bool{
switch self {
case .Success:
return true
default:
return false
}
}
}
通常,App需要Log出一些資訊,我們繼續完善這個列舉,讓它支援Log
用extension,讓這個列舉遵循協議CustomStringConvertible(Swift中的一個log相關的協議)
extension LoginResult:CustomStringConvertible{
var description: String {
switch self {
case .Success:
return "Success"
case let .Failure(message):
return message
case let .Error(error):
return error.localizedDescription
}
}
}
除此之外,Swift的列舉還支援RawValues
enum ASCIIControlCharacter: Character {
case Tab = "\t"
case LineFeed = "\n"
case CarriageReturn = "\r"
}
再深入一點,我們來看看Swift中一些預設使用列舉來定義的型別
Optional
public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible {
case None
case Some(Wrapped)
/// Construct a `nil` instance.
public init()
/// Construct a non-`nil` instance that stores `some`.
public init(_ some: Wrapped)
/// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`.
@warn_unused_result
public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U?
/// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
@warn_unused_result
public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U?
/// Create an instance initialized with `nil`.
public init(nilLiteral: ())
}
可見,Optional本身就是一個列舉,包含了兩種可能性:None(nil),Some(Wrapped)(這裡利用了Swift範型)。
結構體
除了列舉之外,結構體也是Objective C轉過來的同學比較容易忽略的一個數據結構。
Swift的結構體和C的結構體有很大區別,它有很多Class相關的特性
- 定義屬性來儲存值
- 定義方法來提供功能
- 定義subscripts,來實現用下標訪問[]
- 定義建構函式,來初始化
- 遵循某一個協議
- 支援extenstion
當然,有一些特性是它不具有的
- 繼承
- 型別轉換和runtime型別檢查
- deinit方法,來進行銷燬時候的資源釋放
- 引用計數,讓多個引用指向同一個例項。
比如,在OC中,你通常這樣寫一個Model
//標頭檔案
@interface LHPerson : NSObject<NSCopying,NSCoding>
@property (copy,nonatomic)NSString * name;
@property (assign,nonatomic)NSUInteger age;
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
+ (instancetype)personWithName:(NSString *)name age:(NSUInteger)age;
@end
//.m檔案
@implementation LHPerson
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age{
if (self = [super init]) {
_name = name;
_age = age;
}
return self;
}
+ (instancetype)personWithName:(NSString *)name age:(NSUInteger)age{
return [[self alloc] initWithName:name age:age];
}
#pragma mark - NSCoding
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
_name = [aDecoder decodeObjectForKey:@"name"];
_age = [aDecoder decodeIntegerForKey:@"age"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
if (_name != nil) [aCoder encodeObject:_name forKey:@"name"];
[aCoder encodeInteger:_age forKey:@"age"];
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone{
LHPerson * copyed = [[self.class allocWithZone:zone] init];
copyed->_age = self.age;
copyed->_name = self.name;
return copyed;
}
@end
難麼,同樣的Model,在Swift應該怎麼寫呢?
上面的Model更適合結構體來儲存
通常,你可以這麼寫
struct Person{
var name:String
var age:Int
init(name:String,age:Int){
self.name = name
self.age = age
}
}
為了自定義Log,我們可以讓結構體實現CustomStringConvertible
協議
struct Person:CustomStringConvertible{
var name:String
var age:Int
init(name:String,age:Int){
self.name = name
self.age = age
}
var description: String{
return "Name:\(name) Age:\(age)"
}
}
結構體相對於Class的優勢在於
- Struct是值型別,每次傳遞的時候,都會進行一次拷貝,也就是說,在多執行緒的環境下,它是執行緒安全的,當你把一個Struct作為引數傳遞給一個Class的時候,你不需要擔心這個Class會修改我原始的Struct
- Struct不需要考慮記憶體洩漏
通常,當以下一條或者多條滿足的時候,你可以優先考慮使用結構體
- 這個資料結構主要目的是用來封裝一系列相關的值的時候
- 當傳遞值的時候,希望傳遞的是拷貝的時候
- 這個資料結構本身儲存的資料也是值型別,也就是說傳遞的時候是值傳遞
- 這個資料結構不需要從其他地方繼承。
Array,Dictionary本質上都是結構體
public struct Array<Element> : CollectionType, MutableCollectionType, _DestructorSafeContainer {
public var startIndex: Int { get }
public var endIndex: Int { get }
public subscript (index: Int) -> Element
public subscript (subRange: Range<Int>) -> ArraySlice<Element>
}
//省略掉Array的Exetensions ...
Extensions
Swift的Extensions可以給class,struct,enum,protocol新增功能性的方法/屬性/subscripts。這和Objective C的Category很像。但是Swift Extension更加強力,並且不像Category,它不需要名字。
通過Extensions,你可以
- 增加計算屬性,或者計算型別的屬性
- 定義例項方法和型別方法
- 提供新的建構函式
- 定義和使用巢狀型別
- 定義subscripts
- 讓一個型別遵循一個協議
紅色的部分是個人覺得比較容易忽略的。
協議擴充套件
協議擴充套件是OC轉過來的最容易忽略的,Swift是一個很適合《面相協議程式設計的語言》,很多基本的型別。比如AnyObject和Any都是協議型別(事實上,在Swift的時候,不知不覺你已經面相協議程式設計了)。
@objc public protocol AnyObject {
}
public typealias Any = protocol<>
其中,協議擴充套件最靈活的地方是,支援where語句條件擴充套件。
比如,這是我寫Swift程式碼的一個自定義操作符SetUp
,用協議來定義的。
public protocol SetUp {}
extension SetUp where Self: AnyObject {
//Add @noescape to make sure that closure is sync and can not be stored
public func SetUp(@noescape closure: Self -> Void) -> Self {
closure(self)
return self
}
}
extension NSObject: SetUp {}
然後,我只是希望,當AnyObject遵循這個協議的時候具有具有SetUp
.接著,用第二次協議擴充套件,來讓NSObject遵循這個協議。於是,任何NSObject
的子類,你都可以這麼呼叫
let textfield = UITextField().SetUp {
$0.frame = CGRectMake(0, 0,200, 30)
$0.textAlignment = .Center
$0.font = UIFont.systemFontOfSize(14)
$0.center = view.center
$0.placeholder = "Input some text"
$0.borderStyle = UITextBorderStyle.RoundedRect
}
extensions也可以用來設計介面
系統的SequenceType
是Array遵循的協議,於是你可以這樣呼叫
let array = [1,2,3,4,5]
let array2 = array.filter({$0 > 1}).map({$0 * 2})//4 6 8 10
其中,這個map的定義如下
public func map<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]
我們可以自定義myMap來定義個自己的對映方法。
extension SequenceType{
public func myMap<T>(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]{
print("Map begin")
var result = [T]()
try self.forEach { (e) in
do{
let item = try transform(e)
result.append(item)
}catch let error{
throw error
}
}
print("Map end")
return result
}
}
let arr = [1,2,3]
let mapped = arr.myMap({"\($0)"})
print(mapped)
會發現Log
Map begin
Map end
["1", "2", "3"]
知名的Swift函式響應式程式設計開源庫RxSwift,就是利用了Extensions,來讓你定義自己的操作符。
extensions的常用用途
分離程式碼邏輯
把部分邏輯放倒extensions中,能夠讓程式碼更易於維護可擴充套件
class TableviewController: UITableViewController {
}
extension TableviewController{
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
}
}
再看看系統Array
public struct Array<Element> : CollectionType, MutableCollectionType, _DestructorSafeContainer {
//...
}
extension Array : ArrayLiteralConvertible {
//...
}
extension Array : _ArrayType {
//...
}
通過extension,把程式碼邏輯分離開.從而實現:《對擴充套件開放,對修改封閉》
擴充套件沒有原始碼(不易修改原始碼)的類
比如,你在OC中,的工具方法,可以這麼寫
Tips:這裡可以不寫字首lh_
extension UIScreen{
class var lh_width:CGFloat{
get{
return UIScreen.mainScreen().bounds.size.width
}
}
class var lh_height:CGFloat{
get{
return UIScreen.mainScreen().bounds.size.width
}
}
class var lh_bounds:CGRect{
get{
return UIScreen.mainScreen().bounds
}
}
}
總結
並不是什麼高深的東西,寫出來加深下印象,也分享給覺得有用的人。