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

總結

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


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com