1. 程式人生 > >Objective C轉Swift注意事項(一)合理使用結構體,列舉,extensions

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
        }
    }
}

總結

並不是什麼高深的東西,寫出來加深下印象,也分享給覺得有用的人。