swift type操作符_swift重写操作符 - CSDN
  • 关于Swift3.0中的type(of:)函数

    千次阅读 2017-02-02 13:09:02
    其实Apple在Swift 2.x的时候就引入了typeof函数,用于获取某一对象的元类型。在Swift 3.0中,由于反射机制大幅度修改,Mirror现在也用不了了,所以type(of:)这个函数是为数不多的关于Swift中的反射接口。

    其实Apple在Swift 2.x的时候就引入了typeof函数,用于获取某一对象的元类型。在Swift 3.0中,该函数被重新命名为type(of:)。由于反射机制大幅度修改,Mirror现在也用不了了,所以type(of:)这个函数是为数不多的关于Swift中的反射接口。

    type(of:)函数相当于什么呢?如果我们用过Objective-C的话,它就相当于NSObject中的class实例方法,返回当前对象所对应类的元类型(metatype of the class)。我们通过此元类型可以作为该类类方法的消息接收者。在Swift中也一样,Swift 3.0中,我们可以用类的元类型来访问该类的类属性与类方法。这里插句题外话,Apple在Swift 3.0中已经引入了Self关键字,但是我们现在还不能用它,它的作用就相当于Objective-C中的class实例方法,用于直接获取当前类的元类型,然后可以用它直接访问类的类属性与类方法,但是具体怎么实现,Swift核心团队正在讨论,这里涉及到许多问题,包括在类体内是否能直接使用等等,所以有可能的话Self将作为每个结构体、枚举、类的属性来使用——比如,self.Self.classMethod(),而不是直接使用Self.classMethod()。

    当然,就目前而言,如果我们想在一个实例方法中方便访问当前类的类属性和类方法,那么可以直接使用type(of: self)即可,这可能比直接用ClassName.classMethod()要更通用化一些,或更简洁一些(如果你的类名比较长的话)。

    下面给出一段代码例子:

    class MyClass {
        
        fileprivate static let aa = 100
        
        required init() {
            print("Hello, world")
        }
        
        func method() {
            
            // 这里的a是MyClass的元类型(metatype of MyClass)
            let a = type(of: self)
            
            // 这里直接通过MyClass的元类型对象a来访问MyClass的类成员aa
            var value = a.aa
            
            print("The value is: \(value)");
            
            // 这里的b是Int的类型(metatype of Int)
            let b = type(of: a.aa)
            
            // 如果要通过一个元类型来创建一个对象实例,那么必须显式地调用该结构体或类的构造方法
            value = b.init(20)
            print("The integer is: \(value)")
            
            // 一个结构体或类的元类型可以直接用该结构体或类名访问Type属性即可表示。
            // 但这里要注意的是,像这里的Int.Type只能作为对象的类型进行声明,而不能作为一个表达式来使用。
            // 所以,它也不能作为type(of:)函数的实参进行传递
            let c: Int.Type = type(of: type(of: self).aa)
            value = c.init("1234")!
            print("The value is: \(value)")
        }
    }
    
    // 这一句会输出Hello, world
    var type = type(of: MyClass())
    // 这里通过type类型显式调用init构造方法也会直接输出一次Hello, world
    var mc = type.init()
    
    // 这里直接将MyClass类的元类型给type
    type = MyClass.self
    mc = type.init()
    mc.method()
    上述代码,各位可以直接用Xcode创建一个macOS的Cocoa Application来运行。当然也可以把上述的MyClass单独截出来运行。
    此外,各位可以观察到,Swift中的type(of:)函数与C语言中的typeof()操作符不一样,type(of:)函数就是一个普通的函数,其实参表达式会在运行时进行计算。所以,如果我们要直接获取某个类型的元类型,方便的话,尽可能直接用该类型访问其self类型属性来获得。

    展开全文
  • 类型转换在 Swift 中使用 is 和 as 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。 你也可以用它来检查一个类是否实现了某个协议,就像在 检验协议的一致性部分讲述的...

    类型转换可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。

    类型转换在 Swift 中使用 is 和 as 操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。

    你也可以用它来检查一个类是否实现了某个协议,就像在 检验协议的一致性部分讲述的一样。

    定义一个类层次作为例子

    你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了几个这些类实例的数组,作为类型转换的例子。

    第一个代码片段定义了一个新的基础类 MediaItem。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 String 类型的 name 属性,和一个 init name 初始化器。(假定所有的媒体项都有个名称。)

    class MediaItem {
        var name: String
        init(name: String) {
            self.name = name
        }
    }
    

    下一个代码段定义了 MediaItem 的两个子类。第一个子类 Movie 封装了与电影相关的额外信息,在父类(或者说基类)的基础上增加了一个 director(导演)属性,和相应的初始化器。第二个子类 Song,在父类的基础上增加了一个 artist(艺术家)属性,和相应的初始化器:

    class Movie: MediaItem {
        var director: String
        init(name: String, director: String) {
            self.director = director
            super.init(name: name)
        }
    }
    
    class Song: MediaItem {
        var artist: String
        init(name: String, artist: String) {
            self.artist = artist
            super.init(name: name)
        }
    }
    

    最后一个代码段创建了一个数组常量 library,包含两个 Movie 实例和三个 Song 实例。library 的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift的类型检测器能够推理出 Movie 和 Song有共同的父类 MediaItem,所以它推断出 [MediaItem] 类作为 library 的类型。

    let library = [
        Movie(name: "Casablanca", director: "Michael Curtiz"),
        Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
        Movie(name: "Citizen Kane", director: "Orson Welles"),
        Song(name: "The One And Only", artist: "Chesney Hawkes"),
        Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
    ]
    // the type of "library" is inferred to be [MediaItem]
    

    在幕后 library 里存储的媒体项依然是 Movie 和 Song 类型的。但是,若你迭代它,依次取出的实例会是 MediaItem 类型的,而不是 Movie 和 Song 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。

    检查类型(Checking Type)

    用类型检查操作符(is)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回true,否则返回 false

    下面的例子定义了两个变量,movieCount 和 songCount,用来计算数组 library 中 Movie 和 Song 类型的实例数量。

    var movieCount = 0
    var songCount = 0
    
    for item in library {
        if item is Movie {
            ++movieCount
        } else if item is Song {
            ++songCount
        }
    }
    
    print("Media library contains \(movieCount) movies and \(songCount) songs")
    // prints "Media library contains 2 movies and 3 songs"
    

    示例迭代了数组 library 中的所有项。每一次,for-in 循环设置 item 为数组中的下一个MediaItem

    若当前 MediaItem 是一个 Movie 类型的实例,item is Movie 返回 true,相反返回 false。同样的,item is Song 检查item是否为 Song 类型的实例。在循环结束后,movieCount 和 songCount 的值就是被找到属于各自的类型的实例数量。

    向下转型(Downcasting)

    某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试向下转到它的子类型,用类型转换操作符(as? 或 as!)

    因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式(conditional form) as? 返回一个你试图向下转成的类型的可选值(optional value)。强制形式 as! 把试图向下转型和强制解包(force-unwraps)结果作为一个混合动作。

    当你不确定向下转型可以成功时,用类型转换的条件形式(as?)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 nil。这使你能够检查向下转型是否成功。

    只有你可以确定向下转型一定会成功时,才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。

    下面的例子,迭代了 library 里的每一个 MediaItem,并打印出适当的描述。要这样做,item 需要真正作为 Movie 或 Song 的类型来使用,不仅仅是作为 MediaItem。为了能够在描述中使用 Movie 或 Song的 director 或 artist 属性,这是必要的。

    在这个示例中,数组中的每一个 item 可能是 Movie 或 Song。事前你不知道每个 item 的真实类型,所以这里使用条件形式的类型转换(as?)去检查循环里的每次下转。

    for item in library {
        if let movie = item as? Movie {
            print("Movie: '\(movie.name)', dir. \(movie.director)")
        } else if let song = item as? Song {
            print("Song: '\(song.name)', by \(song.artist)")
        }
    }
    
    // Movie: 'Casablanca', dir. Michael Curtiz
    // Song: 'Blue Suede Shoes', by Elvis Presley
    // Movie: 'Citizen Kane', dir. Orson Welles
    // Song: 'The One And Only', by Chesney Hawkes
    // Song: 'Never Gonna Give You Up', by Rick Astley
    

    示例首先试图将 item 下转为 Movie。因为 item 是一个 MediaItem 类型的实例,它可能是一个Movie;同样,它也可能是一个 Song,或者仅仅是基类 MediaItem。因为不确定,as?形式在试图下转时将返回一个可选值。item as? Movie 的返回值是 Movie? 或 “可选 Movie”类型。

    当向下转型为 Movie 应用在两个 Song 实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 Movie 真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“if let movie = item as? Movie”,可以这样解读:

    “尝试将 item 转为 Movie 类型。若成功,设置一个新的临时常量 movie 来存储返回的可选 Movie

    若向下转型成功,然后 movie 的属性将用于打印一个 Movie 实例的描述,包括它的导演的名字 director。相近的原理被用来检测 Song 实例,当 Song 被找到时则打印它的描述(包含 artist 的名字)。

    注意:
    转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转换成的类来使用。

    AnyAnyObject的类型转换

    Swift为不确定类型提供了两种特殊类型别名:

    • AnyObject可以代表任何class类型的实例。
    • Any可以表示任何类型,包括方法类型(function types)。

    注意:
    只有当你明确的需要它的行为和功能时才使用AnyAnyObject。在你的代码里使用你期望的明确的类型总是更好的。

    AnyObject类型

    当在工作中使用 Cocoa APIs,我们一般会接收一个[AnyObject]类型的数组,或者说“一个任何对象类型的数组”。这是因为 Objective-C 没有明确的类型化数组。但是,你常常可以从 API 提供的信息中清晰地确定数组中对象的类型。

    在这些情况下,你可以使用强制形式的类型转换(as)来下转在数组中的每一项到比 AnyObject 更明确的类型,不需要可选解析(optional unwrapping)。

    下面的示例定义了一个 [AnyObject] 类型的数组并填入三个Movie类型的实例:

    let someObjects: [AnyObject] = [
        Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
        Movie(name: "Moon", director: "Duncan Jones"),
        Movie(name: "Alien", director: "Ridley Scott")
    ]
    

    因为知道这个数组只包含 Movie 实例,你可以直接用(as!)下转并解包到不可选的Movie类型:

    for object in someObjects {
        let movie = object as! Movie
        print("Movie: '\(movie.name)', dir. \(movie.director)")
    }
    // Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
    // Movie: 'Moon', dir. Duncan Jones
    // Movie: 'Alien', dir. Ridley Scott
    

    为了变为一个更短的形式,下转someObjects数组为[Movie]类型来代替下转数组中每一项的方式。

    for movie in someObjects as! [Movie] {
        print("Movie: '\(movie.name)', dir. \(movie.director)")
    }
    // Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
    // Movie: 'Moon', dir. Duncan Jones
    // Movie: 'Alien', dir. Ridley Scott
    

    Any类型

    这里有个示例,使用 Any 类型来和混合的不同类型一起工作,包括方法类型和非 class 类型。它创建了一个可以存储Any类型的数组 things

    var things = [Any]()
    
    things.append(0)
    things.append(0.0)
    things.append(42)
    things.append(3.14159)
    things.append("hello")
    things.append((3.0, 5.0))
    things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
    things.append({ (name: String) -> String in "Hello, \(name)" })
    

    things 数组包含两个 Int 值,2个 Double 值,1个 String 值,一个元组 (Double, Double) ,电影“Ghostbusters”,和一个获取 String 值并返回另一个 String 值的闭包表达式。

    你可以在 switch 表达式的cases中使用 is 和 as 操作符来发觉只知道是 Any 或 AnyObject 的常量或变量的类型。下面的示例迭代 things 数组中的每一项的并用switch语句查找每一项的类型。这几种switch 语句的情形绑定它们匹配的值到一个规定类型的常量,让它们的值可以被打印:

    for thing in things {
        switch thing {
        case 0 as Int:
            print("zero as an Int")
        case 0 as Double:
            print("zero as a Double")
        case let someInt as Int:
            print("an integer value of \(someInt)")
        case let someDouble as Double where someDouble > 0:
            print("a positive double value of \(someDouble)")
        case is Double:
            print("some other double value that I don't want to print")
        case let someString as String:
            print("a string value of \"\(someString)\"")
        case let (x, y) as (Double, Double):
            print("an (x, y) point at \(x), \(y)")
        case let movie as Movie:
            print("a movie called '\(movie.name)', dir. \(movie.director)")
        case let stringConverter as String -> String:
            print(stringConverter("Michael"))
        default:
            print("something else")
        }
    }
    
    // zero as an Int
    // zero as a Double
    // an integer value of 42
    // a positive double value of 3.14159
    // a string value of "hello"
    // an (x, y) point at 3.0, 5.0
    // a movie called 'Ghostbusters', dir. Ivan Reitman
    // Hello, Michael
    

    注意:
    在一个switch语句的case中使用强制形式的类型转换操作符(as, 而不是 as?)来检查和转换到一个明确的类型。在 switch case 语句的内容中这种检查总是安全的。

    展开全文
  • 再议Swift操作符重载

    2019-08-10 14:38:09
    正所谓“能力越大责任越大”,这句话用来形容操作符重载最合适不过了。它可以令你的代码更加简洁,也可以让 一个函数调用变得又臭又长。而对于那些没怎么读过你的代码的人来说,操作符的使用同时也会让代码的可读性...

    今天我们来谈一谈Swift中的操作 符重载,这一功能非常实用,但是也相当有风险。正所谓“能力越大责任越大”,这句话用来形容操作符重载最合适不过了。它可以令你的代码更加简洁,也可以让 一个函数调用变得又臭又长。而对于那些没怎么读过你的代码的人来说,操作符的使用同时也会让代码的可读性大打折扣。

    谨 慎引入,按需使用。比如在连接两个字串的时候你就可以通过重载加法来实现。甚至于你仅在屏幕上输入一个加号,就能响应一个网络链接、播放一段音乐或者完成 你能实现的其他任何功能。然而过于复杂的功能对编码来说简直就是灾难,合理的做法就是仅在必要时重载操作符,不做任何多余的事。如果你非要做点奇怪的事, 那就为网络响应这样的功能取一个明显又合适的函数名。

    无论你何时想采用运算符重载,你都需要考虑一下是否值得重载它而不是用一个函数调用。函数调用或许颇费周折,但更能确切的表达它的功能(如果函数名恰到好处的话)。假如你要频繁做类似于加减乘除赋值比较的操作,那么运算符重载的确是个很合适的解决方案。

    运算符重载

    在Swift里面你应该就像平时使用运算符那样来使用重载算符。虽然仍然有些不足的地方,不过下面的这个例子足够说明重载运算符的使用了。这段代码计算的是两个NSDate对象间的差值,使用了“laterDate–initialDate” 的形式:

    func - (left: NSDate, right: NSDate) -> NSDateComponents
    {
        let mostUnits: NSCalendarUnit = .YearCalendarUnit | .MonthCalendarUnit | .DayCalendarUnit | .HourCalendarUnit | .MinuteCalendarUnit | .SecondCalendarUnit
        
        let components = NSCalendar.currentCalendar().components(mostUnits, fromDate: right, toDate: left, options:nil)
        
        return components
    }

    在这个函数中,第一行看上去很多,然而mostUnits表示的不过是我们所关心的日期单元(NSCalendarUnit)的清 单,在这里我选择关注的是年、月、日、时、分、秒。值得注意的是这里并非只有这些日期单元的Flag,可选的还有很多,如果我想知道两个日期之间相隔的周 数我一样可以用别的Flag来替换掉上面这些,使用这些不同Flag可以满足我们对日期计算的大部分需求。往下的一行中,使用了当前时间 NSCalendar.currentCalendar()的components()方法创建了一个NSDateComponents对象,之后在方法 最后返回了这个对象components.

    在两个值之间的运算成为中缀运算,运算重载还有后缀运算和前缀运算两种。比如++i和i++分别就是前缀和后缀运算。

    如你所见,Swift中所谓重载不过是挂羊头卖狗肉,实际上也只是一个函数调用而已,只不过函数名变成了符号。我们指定了表达式中左右参数的类型,并且还指定了计算结果返回的数据类型必须是一个NSDateComponents对象,下面是代码:

    let initialDate = NSDate()
    let components = NSDateComponents()
    components.year = 1
    components.month = 5
    components.minute = 42
    let laterDate = NSCalendar.currentCalendar().dateByAddingComponents(components, toDate: initialDate, options: nil)!
    //Test the overloaded operator
    let difference = laterDate - initialDate
    //Results
    println(difference.year)    //Outputs: "1"
    println(difference.month)   //Outputs: "5"
    println(difference.day)     //Outputs: "0"
    println(difference.hour)    //Outputs: "0"
    println(difference.minute)  //Outputs: "42"
    println(difference.second)  //Outputs: "0"

    这里有一些设置,不过大部分很好理解。我们先 初始化一个日期数据,它的值就是这个NSDate对象的创建日期,另外还有一个NSDateComponents对象包含了它自己的年月和分钟的属性值。 之后使用NSCalendar的dateByAddingComponents方法设置laterDate为1年5个月又42分钟之后的时间。然后测试重 载后的新功能“-”操作符,你会看到最后通过减法输出的结果刚好是我们设置的时间差。

    我认为这样的重载还是有点用的,我们可以像平时使用数 学减法那样来计算。然而,这里隐藏某些假定的条件:首先我已经提到过关于NSCalendarUnit的Flag只是使用了一些而非全部;其次,我在重载 函数里明确使用了NSCalendar的当期日期(currentCalendar)在这里没有显示。最后它把计算结果difference的属性值都设 为了0,但我们其实并没有想使用其他的属性。不过就我所知,某些人也许会想到封装所有的属性来解决。

    如果使用了普通的函数调用来代替重载运 算符,你可以很清楚使用了哪种格式的日期,选中哪些Flag,以及在diffrence里面可以发现哪些属性被设置过。这就是为什么我说重载运算符只是 “有点用”。原因就是它对代码的读者而言,想要知道函数中究竟隐藏了哪些假定条件的话只能扒开重载运算符的源码看了。

    Equatable协议

    Swift中的重载运算符里面还是有一些比较不错的地方。你可以通过重载"=="算符来实现对自定义类的比较。假如你想实现这一功能你就需要使用Equatable协议,Equatable协议主要应用于泛型编程(有关Swift的泛型编程可以参考这里)。如果你按照Equatable协议重载了"=="操作符,那么你无需再重载"!="操作符(因为“!=”的重载就是"=="的逻辑否)。

    重载"=="的方式与其他的中缀运算的重载方式一致,就像我们在上面所提到的"-"重载一样:

    //Globally scoped operator function
    func == (left: Temperature, right: Temperature) -> Bool
    {
        if left.C == right.C
        {
            return true
        }
        else
        {
            return false
        }
    }
    //Custom type itself
    struct Temperature:Equatable
    {
        let C: Double
    }
    //Test
    let tempOne = Temperature(C: 15)
    let tempTwo = Temperature(C: 35)
    let tempThree = Temperature(C: 15)
    let OneTwoEquality = (tempOne == tempTwo)       //Stores:   false
    let OneThreeEquality = (tempOne == tempThree)   //Stores:   true

    现在我们可以使用“==”重载运算来比较两个Temperature实例了。如果要重载一个算符那么它必须要有一个对应的全局函数,甚至定义在类型之外。

    Swift 中关于运算重载还有一个很不错的地方就是Comparable协议。如同遵照Equatable协议一样你在照着Comparable协议实现的时候你需 要重载一个“<”方法。另外如果你重载了"=="和">"运算符之后编译器会为你计算出其它几个运算符"!="、"<"、"& lt;="和">="的判定。可以看这里的一个例子,它同样也是Equatable协议的例子。

    总结

    本文中所有代码的测试环境均为Xcode 6.0.1。

    前面我讨论了一下NSDate的减法,是因为它在被调用时隐藏了不少的假定条件。在这里提醒一下:当你在考虑编码中是否要实现重载运算符的时候可以想想这个示例。如果你想在代码里一直使用相同的unitFlag来重载实现日期的减法,你需要多写一些代码来考虑多种情况。

    如果你想重载内建类型以及不想修改(或者无法修改)的类型的运算符重载,你可以在扩展里面添加这个重载函数。且这个重载函数需要为全局作用域,并放在你随便哪个扩展存放的文件里面。

    有一些运算符是不能重载的,最为明显的就是赋值运算"="(一个等号)。就算你重载了,想一想赋值运算的使用频率,我不会对产生的错误有任何兴趣的。这里有Tammo Freese同学的一篇文章,你可以了解一下哪些运算符是不可以被重载的。

    转载于:https://www.cnblogs.com/Free-Thinker/p/5068521.html

    展开全文
  • 探索:测试 Swift 中的 ErrorType

    千次阅读 2015-09-10 06:25:48
    原文链接:Testing Swift’s ErrorType: An Exploration 译者:mmoaay 在本篇中,我们对 Swift 新错误类型的本质进行探究,观察并测试错误处理实现的可能性和限制。最后我们以一个说明样例、以及一些有用的资源...

    原文链接:Testing Swift’s ErrorType: An Exploration

    译者:mmoaay

    在本篇中,我们对 Swift 新错误类型的本质进行探究,观察并测试错误处理实现的可能性和限制。最后我们以一个说明样例、以及一些有用的资源结尾


    如何实现 ErrorType 协议


    如果跳转到 Swift 标准库中 ErrorType 定义的位置,我们就会发现它并没有包含明显的要求。

    protocol ErrorType {
    }

    然而,当我们试着去实现 ErrorType 时,很快就会发现为了满足这个协议至少有一些东西是必须的。比如,如果以枚举的方式实现它,一切OK。

    enum MyErrorEnum : ErrorType {
    }

    但是如果以结构体的方式实现它,问题来了。

    struct MyErrorStruct : ErrorType {
    }

    我们最初的想法可能是,也许 ErrorType 是一种特殊类型,编译器以特殊的方式来对它进行支持,而且只能用 Swift 原生的枚举来实现。但随后你又会想起 NSError 也满足这个协议,所以它不可能有那么特殊。所以我们下一步的尝试就是:通过一个 NSObject 的派生类实现这个协议

    @objc class MyErrorClass: ErrorType {
    }

    不幸滴是,仍然不行。

    更新:从 Xcode 7 beta 5 版本开始,我们可能不需要花费其他精力就可以为结构体和类实现 ErrorType 协议。所以下面的解决方法也不再需要了,但是仍然留作参考。

    允许结构体和类实现 ErrorType 协议。(21867608)

    怎么会这样?


    通过 LLDB 进一步调查发现这个协议有一些隐藏的要求。

    (lldb) type lookup ErrorType
    protocol ErrorType {
      var _domain: Swift.String { get }
      var _code: Swift.Int { get }
    }

    这样一来 NSError 满足这个定义的原因就很明白了:它有这些属性,在 ivars 的支持下,不用动态查找就可以被 Swift 访问。还有一点不明白的是为什么 Swift 的一等公民(first class)枚举可以自动满足这个协议。也许其内部仍然存在一些魔法?

    如果我们用我们新获得的知识再去实现结构体和类,一切就OK了。

    struct MyErrorStruct : ErrorType {
      let _domain: String
      let _code: Int
    }
    
    class MyErrorClass : ErrorType {
      let _domain: String
      let _code: Int
    
      init(domain: String, code: Int) {
        _domain = domain
        _code = code
      }
    }

    捕获其他被抛出的错误


    历史上,Apple 的框架中的 NSErrorPointer 模式在错误处理中起到了重要作用。在 Objective-C 的 API 与 Swift 完美衔接的情况下,这些已经变得更加简单。确定域的错误会以枚举的方式暴露出来,这样就可以简单滴在不使用“魔法数字“的情况下捕获它们。但是如果你需要捕获一个没有暴露出来的错误,该怎么办呢?

    假设我们需要反序列化一个 JSON 串,但是不确定它是不是有效的。我们将使用 FoundationNSJSONSerialization 来做这件事情。当我们传给它一个异常的 JSON 串时,它会抛出一个错误码为 3840 的错误。

    当然,你可以用通用的错误来捕获它,然后手动检查 _domain_code 域,但是我们有更优雅的替代方案。

    let json : NSString = "{"
    let data = json.dataUsingEncoding(NSUTF8StringEncoding)
    do {
        let object : AnyObject = try
         NSJSONSerialization.JSONObjectWithData(data!, options: [])
        print(object)
    } catch let error {
        if   error._domain == NSCocoaErrorDomain
          && error._code   == 3840 {
            print("Invalid format")
        } else {
            throw error
        }
    }

    另外一个替代方案就是我们引入一个通用的错误结构体,这个结构体通过我们之前发现的方法满足 ErrorType 协议。当我们为它实现模式匹配操作符 ~= 时,我们就可以在 do … catch 分支中使用它。

    struct Error : ErrorType {
        let domain: String
        let code: Int
    
        var _domain: String {
            return domain
        }
        var _code: Int {
            return code
        }
    }
    
    func ~=(lhs: Error, rhs: ErrorType) -> Bool {
        return lhs._domain == rhs._domain
            && rhs._code   == rhs._code
    }
    
    let json : NSString = "{"
    let data = json.dataUsingEncoding(NSUTF8StringEncoding)
    do {
        let object : AnyObject = try
         NSJSONSerialization.JSONObjectWithData(data!, options: [])
        print(object)
    } catch Error(domain: NSCocoaErrorDomain, code: 3840) {
        print("Invalid format")
    }

    但在当前情况下,还可以用 NSCocoaError,这个辅助类包含大量定义了各种错误的静态方法。

    这里所产生的叫做 NSCocoaError.PropertyListReadCorruptError 错误,虽然不是那么明显,但是它确实是有我们需要的错误码的。不管你是通过标准库还是第三方框架捕获错误,如果有像这样的东西,你就需要依赖给定的常数而不是自己再去定义一次。

    let json : NSString = "{"
    let data = json.dataUsingEncoding(NSUTF8StringEncoding)
    do {
        let object : AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
        print(object)
    } catch NSCocoaErrorDomain {
        print("Invalid format")
    }

    自定义错误处理的编写规范


    所以下一步做什么呢?在用 Swift 的错误处理给我们的代码加料之后,不管我们是替换所有那些让人分心的 NSError 指针赋值,还是退一步到功能范式中的 Result 类型, 我们都需要确保我们所预期的错误会被正确抛出。边界值永远是测试时最有趣的场景,我们想要确认所有的保护措施都是到位的,而且在适当的时候会抛出相应的错误。

    现在我们对这个错误类型在底层的工作方式有了一些基本的认识,同时对如何在测试时让它遵循我们的意愿也有了一些想法。所以我们来展示一个小的测试用例:我们有一个银行 App,然后我们想在业务逻辑里面为现实活动建模型。我们创建了代表银行帐号的结构体 Account,它包含一个接口,这个接口暴露了一个方法用来在预算范围内进行交易。

    public enum Error : ErrorType {
        case TransactionExceedsFunds
        case NonPositiveTransactionNotAllowed(amount: Int)
    }
    
    public struct Account {
        var fund: Int
    
        public mutating func withdraw(amount: Int) throws {
            guard amount < fund else {
                throw Error.TransactionExceedsFunds
            }
            guard amount > 0 else {
                throw Error.NonPositiveTransactionNotAllowed(amount: amount)
            }
            fund -= amount
        }
    }
    
    class AccountTests {
        func testPreventNegativeWithdrawals() {
            var account = Account(fund: 100)
            do {
                try account.withdraw(-10)
                XCTFail("Withdrawal of negative amount succeeded, 
                but was expected to fail.")
            } catch Error.NonPositiveTransactionNotAllowed(let amount) {
                XCTAssertEqual(amount, -10)
            } catch {
                XCTFail("Catched error \"\(error)\", 
                but not the expected: \"\(Error.NonPositiveTransactionNotAllowed)\"")
            }
        }
    
        func testPreventExceedingTransactions() {
            var account = Account(fund: 100)
            do {
                try account.withdraw(101)
                XCTFail("Withdrawal of amount exceeding funds succeeded, 
                but was expected to fail.")
            } catch Error.TransactionExceedsFunds {
                // 预期结果
            } catch {
                XCTFail("Catched error \"\(error)\", 
                but not the expected: \"\(Error.TransactionExceedsFunds)\"")
            }
        }
    }

    现在假想我们有更多的方法和更多的错误场景。在以测试为导向的开发方式下,我们想对它们都进行测试,从而保证所有的错误都被正确滴抛出来——我们当然不想把钱转到错误的地方去!理想情况下,我们不想在所有的测试代码中都重复这个 do-catch 。实现一个抽象,我们可以把它放到一个高阶函数中。

    /// 为 ErrorType 实现模式匹配
    public func ~=(lhs: ErrorType, rhs: ErrorType) -> Bool {
        return lhs._domain == rhs._domain
            && lhs._code   == rhs._code
    }
    
    func AssertThrow<R>(expectedError: ErrorType, @autoclosure _ closure: () throws -> R) -> () {
        do {
            try closure()
            XCTFail("Expected error \"\(expectedError)\", "
                + "but closure succeeded.")
        } catch expectedError {
            // 预期结果.
        } catch {
            XCTFail("Catched error \"\(error)\", "
                + "but not from the expected type "
                + "\"\(expectedError)\".")
        }
    }

    这段代码可以这样使用:

    class AccountTests : XCTestCase {
        func testPreventExceedingTransactions() {
            var account = Account(fund: 100)
            AssertThrow(Error.TransactionExceedsFunds, try account.withdraw(101))
        }
    
        func testPreventNegativeWithdrawals() {
            var account = Account(fund: 100)
            AssertThrow(Error.NonPositiveTransactionNotAllowed(amount: -10), try account.withdraw(-20))
        }
    }

    但你可能会发现, 预期出现的参数化错误 NonPositiveTransactionNotAllowed 比这里所用到的参数要多个 amount。我们该如何对错误场景和它们相关的值做出强有力的假设呢? 首先,我们可以为错误类型实现 Equatable 协议, 然后在相等操作符的实现中添加对相关场景的参数个数的检查。

    /// 对我们的错误类型进行扩展然后实现 `Equatable`。
    /// 这必须是对每一个具体的类型来做的,
    /// 而不是为 `ErrorType` 统一实现。
    extension Error : Equatable {}
    
    /// 为协议 `Equatable` 以 required 的方式实现 `==` 操作符。
    public func ==(lhs: Error, rhs: Error) -> Bool {
        switch (lhs, rhs) {
        case (.NonPositiveTransactionNotAllowed(let l), .NonPositiveTransactionNotAllowed(let r)):
            return l == r
        default:
            // 我们需要在默认场景,为各种组合场景返回 false。
            // 通过根据 domain 和 code 进行比较的方式,我们可以保证
            // 一旦我们添加了其他的错误场景,如果这个场景有相应的值
            // 我只需要回到并修改 Equatable 的实现即可
            return lhs._domain == rhs._domain
                && lhs._code   == rhs._code
        }
    }

    下一步就是让 AssertThrow 知道有合理的错误。你可能会想,我们可以扩展已存在的 AssertThrow 实现,只是简单检查一下预期的错误是否合理。但是不幸滴是根本没用:

    “Equatable” 协议只能被当作泛型约束,因为它需要满足 Self 或者关联类型的必要条件

    相反,我们可以通过多一个泛型参数做首参的方式重载 AssertThrow

    func AssertThrow<R, E where E: ErrorType, E: Equatable>(expectedError: E, @autoclosure _ closure: () throws -> R) -> () {
        do {
            try closure()
            XCTFail("Expected error \"\(expectedError)\", "
                + "but closure succeeded.")
        } catch let error as E {
            XCTAssertEqual(error, expectedError,
                "Catched error is from expected type, "
                    + "but not the expected case.")
        } catch {
            XCTFail("Catched error \"\(error)\", "
                + "but not the expected error "
                + "\"\(expectedError)\".")
        }
    }

    然后跟预期一样我们的测试最终返回了失败。

    注意后者的断言实现就对错误的类型进行了强有力的假设。

    不要使用“捕获其他被抛出的错误”下面的方法,因为跟目前的方法相比,它不能匹配类型。很有可能这种错误超出了我们的控制了。

    一些有用的资源


    在 Realm,我们使用 XCTest 和我们自产的 XCTestCase 子类并结合一些 预测器,这样刚好可以满足我们的特殊需求。值得高兴的是,如果要使用这些代码,你不需要拷贝-粘帖,也不需要重新造轮子。错误预测器在 GitHub 的 CatchingFire 项目中都有,如果你不是 XCTest 预测器风格的大粉丝,那么你可能会更喜欢类似 Nimble 的测试框架,它们也可以提供测试支持。

    要开心滴测试哦~

    展开全文
  • swift学习笔记1 操作符

    2017-04-18 13:39:15
    注:英文部分来自官方文档基础操作符赋值操作符 If the right side of the assignment is a tuple with multiple values, its elements can be decomposed into multiple constants or variables at once: 如果...
  • 在学习Swift 3的过程中整理了一些笔记,如果想看其他相关文章可前往《Swift 3必看》系列目录 在之前的版本中,dynamicType是一个...它的行为更像一个全局的操作符,像sizeof这样。所以在3中原有的dynamicType被...
  • 【iOS】Swift LAZY 修饰和 LAZY 方法

    千次阅读 2015-10-28 15:11:57
    延时加载或者说延时初始化是很常用的优化方法,在构建和生成新的对象的时候,内存分配会在运行时耗费不少时间,如果有一些对象的属性和内容非常复杂的话,这个时间更是不可忽略。另外,有些情况下我们并不会立即用到...
  • 类型转换在Java和Swift中非常的相似,...检查类型(Checking Type)用类型检查操作符(is)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 true,否则返回 false。Java中使用instanceof
  • 操作符的使用操作符我们都学过,用过;这里说一下在swift中的新的特性Swift 支持大部分标准 C 语言的运算符,且改进许多特性来减少常规编码错误; 1、赋值符(=)不返回值,以防止把想要判断相等运算符(==)的地方...
  • Swift 5.1中苹果终于千呼万唤始出来的祭出Combine框架,带着一层神秘的面纱,它来啦! Combine框架颠覆了以往苹果的开发模式,它用发布者(Publishers),订阅者(Subscribers)以及两者之间重要的纽带:订阅...
  • Swift 5.1 (2) - 运算符

    2019-08-27 10:13:55
    运算符的术语: 操作符分为一元,二元,三元。 一元运算符主要操作一个单一的目标(比如:-a)。一元前缀运算符可以直接出现在它们的目标前面(比如:!b),一元后缀运算符直接出现在它们目标之后(比如:c!)。 二元...
  • 类型转换 在 Swift 中使用 is 和 as 操作符实现,当然也包括后面加叹号 ! 的强制展开和后面加问号 ? 的可选类型。这两个操作符提供了一种简单达意的方式去检查值的类型或是转换它的类型。 定义一个类层次作为例子 ...
  • 这篇文章主要讲解苹果Swift官方指南的第二章前四节的要点内容,如果想看完整的英文文档可以去苹果开发者页面下载。 Basic 声明常量let 声明变量var 注释依旧使用"//" "/**/", 注意这里"/**/"在Swift可以嵌套使用...
  • Swift3.0 类型检查

    千次阅读 2016-10-09 10:47:08
    类型检查在 Swift 中使用is 和 as操作符实现。这两个操作符提供了一种简单达意的方式去检查值的类型或者转换它的类型。你也可以用来检查一个类是否实现了某个协议,就像在 Protocols Checking for
  • * 今天我们来谈一谈Swift中的操作符重载,这一功能非常实用,但是也相当有风险。正所谓“能力越大责任越大”,这句话用来形容操作符重载最合适不过了。它可以令你的代码更加简洁,也可以让一个函数调用变得又臭又长...
  • Swift关键字总结下篇

    千次阅读 2019-06-03 16:53:35
    Swift 中有多少关键字? 在Swift官方文档的词汇结构中, 有非常多的关键字, 它们被用于声明中、语句中、表达式中、类中、模式中, 还有以数字符号#开头的关键字, 以及特定上下文环境使用的关键字。本文中涉及的代码...
  • Swift知识点

    2019-11-30 23:57:48
    Swift应用于iOS macOS 和watchOS TVOS 敏捷 灵巧而不失简洁 ...操作符有一元操作符 二元操作符 三元操作符 赋值运算符 a = b 意为用b的值来初始化或更新a的值。 元组型变量可以一次给多个变量赋值 var (d, e, ...
1 2 3 4 5 ... 20
收藏数 3,438
精华内容 1,375
关键字:

swift type操作符