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 : _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(@noescape f: (Wrapped) throws -> U) rethrows -> U?
    /// Returns `nil` if `self` is `nil`, `f(self!)` otherwise.
    @warn_unused_result
    public func flatMap(@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 : CollectionType, MutableCollectionType, _DestructorSafeContainer {
    public var startIndex: Int { get }
    public var endIndex: Int { get }
    public subscript (index: Int) -> Element
    public subscript (subRange: Range) -> ArraySlice
}

//省略掉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(@noescape transform: (Self.Generator.Element) throws -> T) rethrows -> [T]

我们可以自定义myMap来定义个自己的映射方法。

extension SequenceType{
    public func myMap(@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 : 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
        }
    }
}

总结

并不是什么高深的东西,写出来加深下印象,也分享给觉得有用的人。

你可能感兴趣的:(Swift)