iOS编程基础-Swift(四)-对象类型

来源:互联网 发布:linux dmalloc 编辑:程序博客网 时间:2024/05/21 12:50

Swift入门指南(iOS9 Programming Fundamentals With swift


第四章 对象类型


         第三章介绍了一些内建对象类型,不过还没有谈及对象类型本身(即 枚举 结构体 和 类);

         

         本章结构:

         1.介绍一下对象类型;

         2.详细介绍对象类型的3种风格:枚举、结构体与类;

         3.介绍Swift中用于增强对象类型灵活性的3种方式:协议、泛型与扩展;

         4.介绍3中保护类型;

         5.介绍3种集合类型;

         结束对Swift内建类型的介绍;

         

         4.1 对象类型声明与特性

         

         对象类型是通过一种对象类型的风格(enum struct class)、对象类型的名字(应该以一个大写字母开头)和一对花括号进行声明的;

         

         class Manny{

         

         }

         struct Manny{

         

         }

         enum Manny{

         

         }

         

         对象类型声明可以在任何地方:文件的顶部,在另一个对象类型声明顶部,或在函数体中;对象类型的作用域取决于它的位置:(可参见第一章)

         1.默认,声明在文件顶部的对象类型对于项目(模块)中的所有文件都可见;对象类型通常都会声明在这个地方;

         2.有时需要在其他类型的声明中声明一个类型,从而赋予他一个命名空间,这叫做嵌套类型;

         3.声明在函数体中的对象类型只会在外围花括号的作用域中存活,这种声明是合法的,但是不常见;

         

         任何对象类型声明的花括号中都可能包含:

         1.初始化器:

         初始化器是一个特殊的函数,它声明和调用方式与众不同,你可以通过它创建对象;

         2.属性:

         实例属性,静态/类属性:声明在对象类型顶部的变量;

         对于枚举和结构体,通过关键字static声明静态属性;对于类来说,通过关键字class声明类属性;

         这种属性属于对象类型,可通过类型访问,他只是一个值,与所属类型关联;

         3.方法:

         声明在对象类型顶部的函数就是方法;

         实例方法:在实例方法内部,self就是实例本身;

         静态方法:对于枚举、结构体来说,通过关键字static声明的;

         类方法:对于类来说,通过关键字class声明的;

         可以通过向类型发送消息来调用它,在静态/类方法内部,self就是类型;

         4.下标:

         下标是一种特殊类型的实例方法,可以通过向实例引用附加方括号来调用它;

         5.对象类型声明:

         对象类型声明还可以包含对象类型声明,即嵌套类型;外部对象类型是嵌套类型的命名空间(第一章中有举例);

         

         4.1.1 初始化器

         

         初始化器是一个函数,用来生成对象类型的一个实例;

         严格说,它是一个静态/类方法,因为它是通过对象类型调用的;

         调用时可以使用特殊语法:在类型名后直接跟一个圆括号,就好像类型本身的函数一样;

         

        let fibo = Dog()        print(fibo.name)

class Dog {    var name = ""    var license = 0        init() {            }    init(name:String) {        self.name = name    }    init(license:Int) {        self.license = license    }    init(name:String , license:Int) {        self.name = name;        self.license = license    }    }


         声明初始化器函数,需要使用关键字init,后跟一个参数列表,然后包含代码的花括号;

         一个对象可以有多个初始化器,由参数进行区分;默认参数名都是外化的(通过下划线可以阻止这一点);参数常用于设置实例属性的值;

 

        let fibo1 = Dog(name: "fibo1")        let fibo2 = Dog(name: "fibo2", license: 3)        print(fibo1.name,fibo2.name)

         我们无法做到不使用初始化器参数创建Dog实例;我们编写了初始化器,隐式初始化器就不复存在了;

         可以显示声明一个不带参数的初始化器;

         init(){

         

         }

         

         其实可以不需要折磨多,初始化器的函数参数可以有默认值,这样可以将所以代码放到单个初始化器中;

         我们用Cat类举例:

 

        let fima = Cat(name: "fibo3")        let fima1 = Cat()        print(fima.license,fima1.license)

class Cat {    var name:String    var license:Int    init(name:String = "", license:Int = 0) {        self.name = name;        self.license = license    }}

         通常,初始化器的目的是 强制调用者在实例化时提供所有必要的信息;

         与这种参数默认值设定的方式相反,我们强制要求每个Dog都必须要有一个有意义的名字与许可证号;

         

         1.Optional 属性

         

         有时,在初始化时并没有可赋值给实例属性的有意义的默认值;也许直到实例创建出来一段时间后才能获取到属性的初始值;

         这个问题合理的解决方案是:使用var将实例属性声明为Optional类型;nil不会提供真正的值,Optional var会自动初始化为nil;

         

         2.引用self

         

         除了设置实例属性,初始化器不能引用self,无论显示还是隐式;除非所有实例属性都已经完成初始化;

         类似这样,编译器会提示需要所有的属性均需初始化完成;

         

         init(name:String , license:Int) {

         self.name = name;

         meow()//

         self.license = license

         }

         

         func meow() -> Void {

         }

         

         3.委托初始化器

         

         对象类型的初始化器可以通过self.init(...)调用其他初始化器;调用其他初始化器的初始化器叫做委托初始化器;

         当一个初始化器委托另一个初始化器时,被委托的初始化器必须要完成实例的初始化;接下来委托初始化器才能使用初始化完毕的实例,用以再次设置被委托初始化器已经设定的var属性;

         注意:直到对被委托的初始化器调用完毕之后,委托初始化器才能引用self,继而设置属性(委托初始化器不能设置不可变属性);

         因为委托初始化器区别于初始化器,他无法设置不可变属性;

         

         不要递归委托!

         

         4.可失败初始化器

         

         初始化器可以返回一个包装新实例的Optional;返回nil表示失败,具备这种行为的叫做可失败初始化器;

         在关键字init后边防止一个?(隐式展开的用!);

         对于可失败初始化器,如果需要返回nil,请显式写明return nil

 

        let daixiang = Elephant()        print(daixiang?.name ?? "name nil")

class Elephant {    var name:String    var license:Int        init?(name:String = "", license:Int = 0) {        self.name = name;        self.license = license        if name.isEmpty {            return nil        }        if license < 0 {            return nil        }    }}

         按照惯例,Cocoa与OC会从初始化器中返回nil来表示失败;如果初始化真的可能失败,那么这种初始化器API已经被转换为Swift的可失败初始化器;(其实大多数OC初始化器都没有被直接桥接为可失败初始化器)

         

         4.1.2 属性

         

         属性是个变量,他声明在对象类型声明的的顶部;(这意味着第三章所介绍的关于变量的一切都适用于属性)

         属性拥有确定的类型,可以通过var或let声明;可以是存储变量,也可以是计算变量;也可以拥有setter观察者;实例属性也可以声明为lazy;

         

         存储实例属性必须要赋予一个初始值;但并非一定要通过声明中的赋值来实现;也可以是初始化器;(setter观察者在属性的初始化过程中是不会被调用的)

         

         初始化属性的代码不能获取实例属性,也不能调用实例方法:

         一般解决方法是将该属性作为计算属性,这是合法的,因为计算直到self存在后才执行;

         另一个解决办法是将whole声明为lazy,这也是合法的,因为直到self存在后对他的引用才会执行;

         与之类似,属性初始化器是无法调用实例方法的,不过,就是那属性可以,lazy属性也可以;

         

         变量初始化器可以包含多行代码,前提是将其写成定义与调用匿名函数;

         

         静态/类属性是通过类型访问的,其作用域是类型,这意味着它是全局唯一的;

         不变的静态/类属性可以作为一种非常便捷且有效的方式为代码提供命名空间下的常量;

         

         与实例属性不同,静态属性可以通过对其他静态属性的引用进行实例化,这是因为静态属性初始化器是延迟的;

         在静态/类代码中,self表示类型本身;当然,也可以直接使用类型名;

         有些情况,编译器无法使用self,不过都会有提示,你可以当成是系统的bug,按照编译器所说的修改即可;

         这里,如果(静态/类计算属性)初始值是通过定义与调用匿名函数所设置的,那就无法使用self;

         

         4.1.3 方法

         

         方法是函数,是声明在对象类型声明顶部的函数;(这意味着第二章介绍的关于函数的一切也都适用于方法)

         方法中对属性的引用如果省略self,会导致代码的可读性和可维护性变差;

         

         静态/类属性是通过类型访问的,self表示的就是类型;

 

 

        struct Greeting{            static let friendly = "hello world"            static let hostile = "go away"            static var ambivalent:String {                return self.friendly + "but" + self.hostile            }            static func beFriendly(){                print(self.friendly)            }        }        Greeting.beFriendly()

      

         静态/类方法不能引用“实例”,因为根本就没有实例存在;静态/类方法不能直接引用任何实例属性和是实例方法;

         实例方法却可以通过名字引用类型,也可以访问静态/类属性,调用静态/类方法(本章末会介绍实例方法引用类型的另一种方式);

         

         实例方法揭秘:

 

        class MyClassA {            var s = ""            func store(s:String) -> Void {                self.s = s            }        }        let dmA = MyClassA()        let dmB = MyClassA.store(dmA)        print(dmA.s)        dmB("huhu")        print(dmA.s)


         这里,store虽然是一个实例方法,但我们能以类似类方法的形式调用它:即通过将类实例作为参数;

         原因在于实例方法实际上是由两个函数构成的调制 静态/类方法:一个函数接收一个实例,另一个函数接收实例方法的参数;

         这样,dmB就成了第二个函数,调用他相当于调用实例m的store方法一样;

         

         4.1.4 下标

         

         下标是一种实例方法,不过调用方式比较特殊:在实例引用后面使用方括号,方括号可以包含传递给下标方法的参数;

         你可以通过该特性做任何想做的事情,不过他特别适合于通过键或索引号访问对象类型中的元素的场景;

         可以对字符串,字典,数组使用方括号,因为Swift中String,Dictionary与Array类型都声明了下标方法;

         

         声明下标方法的语法类似于函数声明和计算属性声明:

         下标类似于函数,因为他可以接收参数:当调用下标方法时,实参位于方括号中;

         下标类似计算属性,因为调用就好像对属性的引用:你可以获取其值,也可以对其赋值;

         

         我们声明一个结构体:对待整形的方式就像是字符串,通过在方括号中使用索引数的方式返回一个数字;

 

        struct Digit{            var number:Int            init(_ n:Int) {                self.number = n            }            subscript(ix:Int) -> Int {                get{                    let s = String(self.number)                    return Int( String( s[s.index(s.startIndex, offsetBy: ix)]))!//忽略了错误检查的代码                }                set{                    var s = String(self.number)                    let i = s.index(s.startIndex, offsetBy: ix)                    s.replaceSubrange(i...i, with: String(newValue))                    self.number = Int(s)!                }            }        }        var dmC = Digit(1919)        print(dmC[2])


         1.关键字subscribe后面有一个参数列表,指定什么参数可以出现在方括号中,且默认名字不是外化的;

         2.箭头运算符后面指定了传出(调用getter时)或传入(调用setter)时值类型;

         3.花括号中内容就像是计算属性的内容:

         你可以为getter提供get与花括号,为setter提供set与花括号;如果只有getter没有setter,那么单词get及后面的额花括号就可以省略;setter会将新值作为newValue,不过你可以通过在圆括号中单词set后边提供不同的名字来改变它;

 

        dmC[1] = 8        print(dmC)

        一个对象类型可以声明多个下标方法,前提是其签名不同;

         

         4.1.5 嵌套对象类型

         

         一个对象类型可以声明在另一个对象类型声明中,从而形成嵌套类型;

         嵌套对象类型,与一般的对象类型没有区别,不过从外部引用它的规则发生了变化;外部对象类型成为一个命名空间,必须要显示通过他才能访问到嵌套对象类型;

         借助命名空间,我们可以创建多个被嵌套的对象的同名类型,而不会在成名字冲突;

         Swift内建对象类型通常都会利用命名空间;比如,有一些结构体包含了Index结构体,而String结构体就是其中之一,它们之间不会造成名字冲突;

         

         借助Swift的隐私原则,我们可以隐藏嵌套类型;

         

         4.1.6 实例引用

         

         对对象类型的实例化是直接创建该类型全新实例的一种方式,这需要调用初始化器;不过,有时其他对象会创建对象并将其提供给你;

         比如,通过字面量赋值,或是函数返回值进行的变量初始化;这个过程并没有调用初始化器;

         

         如实例已存在,如何获取呢?

         获取引用总是从你已经具有引用的对象开始,通常这是个类;在iOS编程中,应用本身就是个实例,有一个类会持有一个对该实例的引用,他会在你需要时将其传递给你,这个类就是UIApplication,我们可以通过调用其shared属性获得对应用实例的引用;

 

        let vc = UIApplication.shared.keyWindow?.rootViewController        print(vc ?? "rootVC")

         获取应用主窗口之后,再找到根视图控制器属性;

         其实无需将实例消息链接为单独一行,使用多个let赋值会更具效率、更加清晰,也更易于调试;

         

         

         4.2 枚举

 枚举是一种对象类型,其实例表示不同的预定义值,可以将其看做已知可能的一个列表;

         Swift通过枚举来表示彼此可代替的一组常量;

         枚举声明中包含了若干case语句;每个case都是一个选择名;一个枚举实例只表示一个选择,即其中的一个case;

        

        

enum Filter{            case Albums            case Playlists            case Podcasts            case Books        }        let typeA = Filter.Albums        let typeB:Filter = .Albums        print(typeA,typeB)        if typeA == typeB {            print("==")        }

         该枚举并没有初始化器;(你可以为他编写)

         他提供了一个默认的初始化模式:使用枚举名,后跟点符号以及一个case;

         

         作为一种简写,如果类型提前就知道了,那就可以省略枚举的名字,不过签名还是要有一个点;(这种简写很常用)

         

         枚举声明中的代码可以在不使用点符号的情况下使用case名;枚举是个命名空间,声明中的代码位于该命名空间的下边,因此能够看到case名;

         

         相同case的枚举实例是相等的;

         

         4.2.1 带有固定值的Case

         

         在声明枚举时,可以添加类型声明;

         接下来所有case都会持有该类型的一个固定值(常量);

         如果类型是整形数字,那么值就会被隐式赋予,并且默认从0开始;

 

        enum FilterA:Int{            case Mannie            case Moe            case Jack        }


         如果类型为String,那么隐式赋予的值就是case名字的字符串表示;当然也可以显示赋值(无论类型是什么);

 

        enum FilterB:String {            case AlbumsB = "AlbumsB"            case PlaylistsB = "PlaylistsB"            case PodcastsB = "PodcastsB"            case BooksB = "BooksB"        }        let typeC = FilterB.AlbumsB        print(typeC.rawValue)


         这种方式附加到枚举上的类型只能是数字与字符串,赋的值必须是字面值;

         case所持有的值叫做其原生值;该枚举的一个实例只会有一个case,因此只有一个固定的原始值,并且可以通过rawValue属性获取;(注意 原生值必须唯一)

         

         与每个case关联的原生值在当前枚举中必须唯一;编译器会强制施加该规则;因此可以进行反向匹配:给定一个原生值,得到与之对应的case;

 

   

     let typeD = FilterB.init(rawValue: "BooksB")        print(typeD ?? "enum nil")

         这是一个可失败的初始化器,type是一个包装了FilterB的Optional;


       if typeD == .BooksB {            print("比较可选值,会自动用值进行比较,不用解包")        }

         4.2.2 带有类型值的case

         

         上一节介绍的原生值是固定的:给定的case会持有某个原生值;

         此外,你可以构建一个这样的case:其常量值是在实例创建时设置的;

         

         首先,不要为枚举声明任何类型,相反,向case的名字附加一个元组类型;通常该元组只会有一个类型;形式是圆括号中有一个类型名,其中可以声明任何类型;

         

  

      enum HDMError{            case HDMNumber(Int)            case HDMMessage(String)            case HDMDog(Dog)            case HDMFatal            case HDMFatalDul(n:Int,s:String)                    }        let error:HDMError = .HDMNumber(5)        let error1:HDMError = .HDMNumber(6)        let error2 = FilterB.AlbumsB        let error3 = FilterB.BooksB        //if error == error1 {          //  print("==")        //}        if error3 == error2 {            print("==")        }                let error4 = HDMError.HDMDog(Dog())        print(error4)

        

         上述代码的意思是:在实例化期间,带有.HDMNumber case的HDMError实例必须要赋予一个Int值;

         带有赋值的实例化实际上会调用一个初始化函数;初始化的过程如上;

         

         这里的附加值叫做关联值,这里提供的是一个元组,因此可以包含字面值或值引用;(试了一下:Dog对象也是可以的)

         而且,我们看到带有类型值的case是不能比较的(或者说需要和值一起进行比较,又或者只能比较值);

         

         元组可以包含多个值,可以提供名字,也可以不提供;

         

         声明了关联值的case实际上是一个初始化函数,这样就可以捕获到对该函数的引用并在后面使用它;

 

        let fatalMaker = HDMError.HDMFatalDul        let err = fatalMaker(n: 10,s: "error")        print(err)


         Optional实际上是一个带有两个case的枚举:.Nonoe(没有关联值,并且等于nil)与.Some(将包装作为关联值);

         

         4.2.3 枚举初始化器

         

         显式的枚举初始化器必须要实现与默认初始化相同的工作:他必须返回该枚举特定的一个case,为了做到这一点,请将self设定给case;

 

      enum FilterH:String {            case FilterH_A = "FilterH_A"            case FilterH_B = "FilterH_B"            case FilterH_C = "FilterH_C"            case FilterH_D = "FilterH_D"            static var cases:[FilterH] = [FilterH_A,FilterH_B,FilterH_C,FilterH_D]            init!( _ ix:Int){                if !(0...3).contains(ix) {                    return nil                }                self = FilterH.cases[ix]            }            init!(_ rawValue:String) {                self.init(rawValue:rawValue)
//通过原生值进行逆向匹配的初始化器,并不是一种隐式初始化器,不会因为自定义了init初始化器 而失效,也可以理解为枚举不具有该特性,这一点请区别于其他的对象类型            }            mutating func advance(){                var ix:Int! = FilterH.cases.index(of: self)                ix = (ix + 1)%4                self = FilterH.cases[ix]            }        }                let type1 = FilterH.FilterH_A       //默认        let type2 = FilterH.init(rawValue: "FilterH_B")!//逆向、可失败        let type3 = FilterH.init(2)         //可失败、优化                print(type1,type2,type3 ?? "nil_enum")


        为了使该枚举能够通过一个String原生值进行初始化而无须调用rawValue,我们做一下改进;

         因此有了如下的初始化方式:

         

      let type4 = FilterH("FilterH_D")    //逆向、可失败、优化        print(type4 ?? "nil_enum")


         4.2.4 枚举属性

         

         枚举可以拥有实例属性和静态属性,不过有一个限制:枚举的实例属性不能是存储属性;

         这是有意义的:因为如果相同case的两个实例拥有不同的存储实例属性值,那么他们彼此之间就不相等了,有悖于枚举的本质和目的(类似的限制,之前也出现过,这也解释了之前的 带有类型值的case不能比较的问题);

         

         如果枚举实例属性是个带有Setter的计算属性,那么其他代码就可以为该属性赋值了;

         不过,代码中对枚举实例的引用必须是变量var;

 

 

       enum FilterI:String {            case FilterI_A = "FilterI_A"            case FilterI_B = "FilterI_B"            case FilterI_C = "FilterI_C"            case FilterI_D = "FilterI_D"            var query:String {                set{                    print(newValue)                }                get{                    var s = ""                    switch self {                    case .FilterI_A:                        s = "FilterI_A_SET"                    default:                        s = ""                    }                    print(s)                    return s                }            }        }

       

         4.2.5 枚举方法

         

         枚举可以有实例方法(包括下标)和静态方法;

         

         在纸牌游戏中,每张牌分为矩形,椭圆,菱形,将绘制代码抽象为一个枚举,他会将自身绘制为一个矩形,椭圆,菱形,取决于case的不同;

         

         修改枚举自身的枚举实例方法应该被标记为mutating;

         比如一个实例方法可能会为self的实例属性赋值;虽然是个计算属性,但也必须标记为mutating;

         枚举实例方法甚至可以修改self的case;可变的实例方法的调用者必须要有一个对该实例的变量引用(var)而非常量引用(let);

         

      var type = FilterH.FilterH_A        type.advance()        print(type)//FilterH_B
  


         4.2.6 为何使用枚举

         

         枚举是一个拥有状态名的Switch;


注:可能还是有一个地方有些模糊,就是关于带有类型值的case间的比较,由于带有了类型值,这种比较是不被允许的,具体使用的时候可以是使用Switch进行匹配,然后取出case关联的类型值即可;

switch error {        case .HDMNumber(let n):            print(n)        default:            print("")        }


类似上述这种方式,将关联在元组中关联值取出来进行使用;


         4.3 结构体

     

         枚举的case数量固定,实际上是一种精简特殊的对象;类则是另一个极端,能力很强大;折中的话,结构体就是很好的选择;

         

         在Swift头文件所声明的大量对象类型中,只有4个是类,而且不大可能会用到;Swift本身提供的几乎所有的内建对象类型都是结构体;

         

         4.3.1 结构体初始化器、属性和方法

         

         如果一个结构体没有或不需要显式初始化器(结构体没有存储属性或在声明中为所有的存储属性都赋值了时,就可以不需要显式初始化器),那么他会自动拥有一个不带参数的隐式初始化器init();

         如果添加了自定义的显式初始化器,那么隐式初始化器就不复存在了;

         

         拥有存储属性,并且没有显示初始化器的结构体会自动获得来自实例属性的隐式初始化器,这叫做成员初始化器;

         

         由于成员初始化器的存在,也就是说默认所有的存储属性都会被赋上默认值;

         但如果添加了自定义的显式初始化器,那么成员初始化器就不复存在了;

         所以这种就必须实现契约:要么在声明中,要么在所有初始化器中完成对所有存储属性的初始化;

         委托初始化器也适用于结构体;

         

         如果想设置结构体实例的属性,需要用var来声明实例的引用;

         

         结构体的实例方法,如果设置了某个属性,那么必须将该方法标记为mutating,调用者对该结构体实例的引用也必须是一个变量;

         mutating实例方法甚至可以用别的实例替换掉当前实例,只需将self设置为相同结构体的不同实例即可(下标Setter总是mutating,可以不用显式标记);

         

         4.3.2 将结构体作为命名空间

         

         可以将退化的结构体作为常量的命名空间;之所以称“退化的”,是因为它只要静态成员,我们不会通过该对象类型创建任何实例;

         拥有静态成员的结构体非常适合定义这些常量字符串,继而将这些名字统一到一个命名空间中;

         

         如果结构体声明了静态成员,且其值是相同的结构体类型,那么在需要该结构体类型实例的情况下,提供结构体静态成员时就可以省略结构体的名字,就好像该结构体是一个枚举一样;

        struct Thing {            var rawValue:Int = 0            static var oneThing:Thing = Thing(rawValue:1)            static var twoThing:Thing = Thing(rawValue:2)        }        print(Thing.oneThing)        let thing:Thing = .oneThing        print(thing)        Thing.oneThing = Thing(rawValue: 3)        print(Thing.oneThing)        print(thing)

        

         Thing #1(rawValue: 1)

         Thing #1(rawValue: 1)

         Thing #1(rawValue: 3)

         Thing #1(rawValue: 1)

         

         上面只是一个示例,很多使用场景其实是 OC枚举通过这种结构体桥接到Swift;

         

         4.4 类

         

         大体和结构体相似,以下是区别:

         1.引用类型

         类是引用类型(区分于枚举和结构体的值类型);

         1)可变性

         类实例是可变的,实例的引用即便是一个let,也可以通过该引用修改实例属性的值;

         类的实例方法绝对不能标记为mutaing;

         2)多引用

         如果给定的类实例被赋予多个变量或作为参数传递给函数,那么你就拥有了对相同对象的多个引用;

         

         2.继承

         类可以拥有子类;

         

         注:

         在OC中。类是唯一一种对象类型;一些内建的Swift结构体类型会桥接到OC的类类型,不过自定义的结构体类型却做不到这一点;

         因此,使用Swift进行iOS编程时,使用类而非结构体的一个主要原因就是它能够与OC和Cocoa互换;

         

         4.4.1 值类型与引用类型

         

         枚举和结构体是值类型;类是引用类型;

         值类型是不可变的;这意味着你无法修改值类型实例属性的值;看起来是修改,但实际上是不行的;

         

         我们在结构体中声明一个var属性,实例化之后修改该属性的值,Swift的赋值语法使我们相信修改实例的属性是可行的;

         但实际上,我们并未修改实例属性,而是创建了一个不同的实例并替换掉之前那个;

         

         一般来说,当修改一个实例值类型时,你实际上会通过另一个实例替换掉当前这个实例,这说明如果该实例的引用是通过let声明的,那么就无法修改值类型实例以及其属性;

         这也正是设置实例属性的结构体或枚举的实例方法要被显示标记为mutating关键字的原因,如果方法被调用了,那么它会替换掉这个实例,这样该方法只能在使用var声明的引用上进行调用,let则不行;(可以简单理解为mutating标记的方法能够替换掉当前的实例,如果不用mutating标记,则编译将无法通过);

         

     struct DogStruct {            var number = 4            mutating func changeNumer(n:Int) -> Void {                self.number = n            }        }

         

         类是引用类型,如果一个类的实例属性可以被修改,那么显然要用var声明;类实例的引用可以不是var声明的;

         在修改类实例变量属性的时候,类实例变量的Setter观察者是不会被调用的(因为类实例的引用并没有发生变化);

         

         值类型和引用类型的差别还体现在函数调用的参数上:

 

       func chageNumberForSomeTypeStruct(d:DogStruct){            var d = d            d.number = 5        }        class DogClass {            var number = 4        }        func chageNumberForSomeTypeClass(d:DogClass){            d.number = 5        }

        //这里的两个函数其实是可以遵循函数重载的,因为参数类型不同,但由于这里的函数也是变量,导致了变量名相同,导致冲特无法通过编译

       

         如果没有 var d = d 这一行,将无法编译通过(结构体传过来的参数是一个副本常量,需要用var 变量接受下,再进行操作);操作类的则不需要;

         

         值类型和引用类型存在这些差别的深层次原因在于:

         对于引用类型,在对实例的引用与实力本身之间实际上存在一个隐藏的间接层次;引用实际上引用的是实例的指针;这又引申出了另一个重要的隐喻:在将类实例赋值给变量或作为参数传递给函数时,你可以使用同一个对象的多个引用;

         但结构体和枚举不同;在将结构体/枚举实例赋给变量,传递函数、或从函数返回时,真正赋值或传递的本质是该实例的一个新副本(类则是引用);

         

         类的这种引用特性,在多线程中会产生问题,因为同一个对象可能会被多个引用持有和修改;

         引用类型的实例传递都是非常快速且高效的,因为整个过程中都不会产生新数据;此外,类实例更长的声明周期对于其功能性和完整性也至关重要;

         

         递归引用:

         值类型和引用类型的另一个主要差别在于值类型从结构上来说是不能递归的:值类型的实例属性不能是相同类型的实例;

 

//        struct DogDigui {//            var dog:DogDigui?//        }

        

         上述是无法通过编译的,但如果是类就没问题了;这是值类型与引用类型内存管理上的不同导致的;

         

         枚举case的关联值可以是该枚举的实例,前提是case(或整个枚举)被标记为indirect:


         enum Node {            case None(Int)            indirect case Left(Int,Node)            indirect case Right(Int,Node)            indirect case Both(Int,Node,Node)        }
        

         4.4.2 子类与父类

         

         两个类彼此之间可以形成子类与父类的关系;

         一个类可以有多个子类,但一个类只能有一个直接父类;(这里的直接指的是父类本身也可以有父类)

         对于swift语言本身来说,并不要求一个类必须要有父类,因此,Swift程序中可能会有很多类没有父类,会有很多独立的层次化子类树,每棵树都从不同的基类延伸出来;

         

         要想将一个类声明为另一个类的子类,在类声明的类名后加上一个冒号:和父类的名字即可;

         子类化之后的类可以继承父类的属性和方法,子类还可以声明自己的方法,或是重新定义从父类继承下来的方法(重写);

         重写的本质原则在于:

         如果子类重写了从父类继承下来的方法,那么在向该子类实例发送消息时,被调用的方法是子类所声明的哪一个;

         

         在Swift中,当重写从父类继承下来的东西时,你需要在声明前显示使用关键字override;

         

         子类与父类函数同名不一定就是重写,只有相同函数才是重写,所谓相同函数指的是名字相同和签名相同;

         子类中访问父类可以使用关键字super;

         下标在子父类中的关系同方法;

         

         除了方法,子类还可以继承父类的属性,也可以声明自己的附加属性,还可以重写继承下来的属性;

         可以在类声明前加上关键字final防止类被继承,也可以在类成员声明前加上关键字final防止它被子类重写;

         

         4.4.3 类初始化器

         

         类实例的初始化要比结构体或枚举实例的初始化复杂的多,这是因为类存在继承;

         初始化器的主要工作是确保所有属性都有初值;

         初始化器还可以做一些对于实例的初始化状态与完整性来说必不可少的工作;

         除了初始化子类自身属性并执行初始化器任务,我们必须要确保在初始化子类时,父类的属性也被初始化了;

         

         1.类初始化器分类:

         

         1)隐式初始化器:

         类没有存储属性,或是存储属性都作为声明的一部分进行初始化,没有显示初始化器,有一个隐式的初始化器init();

         2)指定初始化器:

         默认,类初始化器是一个指定初始化器;如果类中有存储属性没有在声明中完成初始化,那么这个类至少要有一个指定初始化器;当类被实例化时,一定有一个指定初始化器被调用,并且确保所有存储属性都被初始化;

         指定初始化器不可以委托给相同类的其他初始化器;指定初始化器不能使用self.init(...);

         3)便捷初始化器:

         便捷初始化器使用关键字convenience标记;他是个委托初始化器,必须调用self.init(...);

         便捷初始化器必须调用相同类的一个指定初始化器,否则就必须调用相同类的另外一个便捷初始化器。这个便捷初始化器链最后要调用相同类的一个指定初始化器;

         

         注:

         除了初始化属性,只有当类的所有属性都初始化完毕后,指定初始化器才能使用self;

         便捷初始化器是一个委托初始化器,因此只有在直接或简介地调用指定初始化器之后,他才能使用self;

         

         2.子类初始化器

         

         子类的初始化器的原则有些变化:

         1)无声明的初始化器:

         子类没有声明自己的初始化器,会从父类继承;

         2)只有便捷初始化器:

         调用从父类继承的指定初始化器或便捷初始化器;

         3)指定初始化器:

         如果子类声明了自己的指定初始化器,那么整个规则就会发生变化:父类的初始化器都不会被继承下来;

         显示的指定初始化器的存在阻止了初始化器的继承;

         

         现在,子类中的每个指定初始化器都有一个额外的要求:他必须调用父类的一个指定初始化器(super.init(...));

         (注:指定初始化器不可以调用便捷初始化器,便捷初始化器中才可以调用指定初始化器,——别乱用委托)

         

         子类的指定初始化器必须按照如下顺序调用执行:

         (1)必须确保该类(子类)的所有属性都被初始化;

         (2)必须调用super.init(...),它所调用的初始化器必须是个指定初始化器;

         

         满足上述两条,该初始化器才可以使用self调用实例方法或访问继承属性;

         

         4)重写初始化器:

         子类重写父类初始化器,需遵循:

         (1)签名与父亲便捷初始化器匹配的初始化器必须是个便捷初始化器,无需标记为override;

         (2)签名与父亲指定初始化器匹配的初始化器可以是指定初始化器,也可以是便捷初始化器,但必须标记为override;

         在重写的指定初始化器中可以通过super.init(...)调用被重写的父类指定初始化器;

         

     class DogH{            var name:String            var license:Int            init(name:String,license:Int) {                self.name = name                self.license = license            }            convenience init(license:Int) {                self.init(name: "Fibo", license: license)            }        }                class NoisyDogH:DogH {            init(name:String) {                super.init(name: name, license: 1)            }        }        let ndi = NoisyDogH(name: "Rover")        print(ndi.name)


         一般来说,如果子类有指定初始化器,那就不会继承任何初始化器;

         不过,如果子类重写了父亲所有的指定初始化器,那么子类就会继承父类的便捷初始化器;

         

         5)可失败初始化器:

         只有完成了自己的前部初始化任务后(所有子类属性的初始化 + super.init()),可失败指定初始化器才能够调用return nil;

         针对重写与委托的目的,返回隐式展开Optional的可失败初始化器(init!)就像是常规的初始化器(init)一样;

         对于返回常规Optional(init?)的可失败初始化器则有一些额外的限制:

         (1)init可以重写init?,反之则不行;

         (2)init?可以调用init;

         (3)init调用init?的话,需要强制解包;

         

         子类初始化器中只能做初始化自己属性以及调用其他初始化器的事情,这些做完才能调用self进行其他操作;

         

         

         3.必备初始化器

         

         类初始化器前面可以加上关键字required,这意味着子类不可以省略他;

         这种情况下,如果子类实现了自己的指定初始化器,阻止了继承,那他就必须重写该初始化器;

         

        

 class DogD {            var name :String            required init(name:String){                self.name = name            }        }        class NoisyDogD:DogD {            var obedient = false            init(obedient:Bool) {                self.obedient = obedient                super.init(name: "Fiobo")//子类的指定初始化器需要调用父类的指定初始化器            }            required init(name: String) {                super.init(name: name)            }        }

         注意:被重写的必备初始化器并没有被标记为override,但却被标记为required,这样就保证了无论子类层次有多深都可以满足需求;

         

         4.4.4 类析构器

         

         只有类才会有析构器,是个通过关键字deinit声明的函数,后跟花括号,里面是函数体;

         你永远不会自己调用这个函数,当类的实例自动消亡时由运行时调用;

         如果一个类有父类,那么子类的析构器会在父类的析构器调用前调用;

         第五章介绍内存管理时会使用;

         

         4.4.5 类属性与方法

         

         子类可以重写继承下来的属性;需要同名、同类型,且要标记为override;

         需遵循的规则如下:

         1)如果父类属性是可写的(存储属性或带有setter的计算属性),那么子类在重写时可以添加对该属性的setter观察者;

         2)子类可以使用计算变量重写,此时:

         (1)父类的是存储属性的话,那么子类的计算属性重写就必须要有getter与setter;

         (2)父类的属性是计算属性的话,那么子类的计算属性重写就必须重新实现父类实现的所有访问器;

         如果,父类属性是只读的(只有getter),那么重写可以添加setter;

         

         重写属性的函数可以通过super关键字引用(读或写)继承下来的属性;

         

         类可以有静态成员,标记为static,和结构体和枚举一样;

         还可以有类成员,标记为class;

         两者都可以由子类继承,两者区别:

         1)静态属性/方法无法重写,static就好像class final的同义词一样;类成员可以被重写为类成员或静态成员;

         2)静态属性可以是存储属性,类属性只能是计算属性;因此子类重写的静态属性(重写的是父类的类属性)不能是存储属性(override标记);


         4.5 多态

若某计算机语言有类型与子类型层次,那么他必须要解决这样一个问题:

         对于对象类型与声明的指向该对象的引用类型之间的关系,以及这种层次体系意味着什么;

         Swift遵循着多态原则:(正是多态的作用才使得基于对象的语言彻底演变为完善的面向对象语言)

         1)替代:

         在需要某个类型时,我们可以使用该类型的子类型;

         2)内在一致性:

         对象类型的关键在于内部特性,而与对象是如何被引用的毫无关系;

         

        if ((1 > 0) == true){            class Dog {                            }            class NoisyDog:Dog{                            }            let d:Dog = NoisyDog()            print(d)//NoisyDog        }

        

         内在一致性法则表示,在底层d现在就是个NoisyDog;

         给内在一致性的子父类对象发送消息,会根据内在实际类型进行响应;-可以此证明内在一致性;

         

         内在一致性法则表明在发送消息时,重要的事情并不是如何通过引用来判断消息接收者的类型,而是接收者的实际类型到底是什么;

         

         多态对self含义的影响:

         关键字self指的是实际实例,其含义取决于实际实例的类型,即便单词self出现在父类代码中亦如此;

         

        if ((1 > 0) == true){                        class Dog {                func bark() {                    print("woof")                }                func speak() {                    self.bark()                }            }            class NoisyDog : Dog{                override func bark() {                    super.bark()                    super.bark()                }            }            let d = NoisyDog()            d.speak()//woof \n woof        }

        

         speak方法中的self位于Dog类的方法实现中,不过,重要的不是单词self在哪,而是他表示什么含义(内在一致性告诉我们,当前实例是一个NoisyDog);

         归功于多态,你可以充分利用子类为已有的类增加功能并做更多的定制;

         

         举一个多态的场景:

         在实际情况中,你会声明UIViewController的子类并重写这些方法来完成适合于特定应用的任务,这多Cocoa不会造成任何影响,因为替代法则在发挥作用,在Cocoa期望接收或要调用UIViewController时,他可以接收你自己定义的UIViewController子类,这么做不会产生任何问题;当Cocoa调用子类中的UIViewController的方法时,真正调用的实际上是子类重写的版本;

         

         多态速度会慢些,需要动态派发,这意味着运行时要思考向类实例发送的消息到底表示什么;而结构体无须动态派发;

         此外,可以将类或类成员声明为final或private,以及打开全模式优化来减少动态分发的使用;

         

         4.6 类型转换

         

         编译器允许发送给某给对象引用的消息是该引用类型所允许的那些消息,包括继承下来的;由于多态的内在一致性法则,对象可以接收到编译器不允许发送的消息,此时编译无法通过(子类声明的方法教给父类引用去调用,内在实际调用者为子类实例);

         为做到这一点,可以通过类型装换,可以使用关键字as,后跟真正的类型名;

         

         将父类转换为子类,称作向下类型转换;在进行向下类型装换时,需要使用'as!';

         as! 运算符的感叹号会提醒你强制编译器进行装换;他还有警告作用:代码可能会崩溃;因为你可能对编译器撒谎,而编译器有放松了警惕;

         

         为了防止这种错误的一种方式是你可以在运行时测试实例类型,使用is关键字,判断通过后在转换:

 

        if ((1 > 0) == true){            class NoisyDog{                func isQuiet() {                    print("quiet")                }            }            let d = NoisyDog()            if d is NoisyDog {                print("类型判断通过")            }                        (d as? NoisyDog)?.isQuiet()//quiet        }

        

         另一种方式是使用Swift的as?运算符,让也会进行向下类型转换,不过提供了失败的选项;

         通过as?运算符获取到一个包装了NoisyDog的Optional;我们可以通过可选链(可选展开)展开Optional并发送消息(如上);

         

         第三章中讲过,对Optional使用比较运算符会自动应用到该Optional所包装的对象上,as!as?is与之类似;

         如果is左边的是一个Optional,那么Swift就会认为它是包装在Optional中的值;

         

        if ((1 > 0) == true){            class NoisyDog{                            }            let d:NoisyDog? = NoisyDog()            if d is NoisyDog {                print("类型判断通过")            }        }

        

         is会做两件事:他会检查Optional是否为nil,如果不是,那么就会继续检查被包装的值是否是我们指定的类型;

         

         除了向下的类型装换,还有一种情况会用到类型装换,那就是在进行Swift与OC值交换时(两个类型相同),比如String与NSString,这种并不是因为其中一个是另一个的子类,而是因为它们之间可以彼此桥接,它们本质上是相同的类型;

         这种并不是向下的类型转换,也没啥不安全,使用as运算符即可,不需要使用感叹号;


        let s = "hello"        NSLog("%d",(s as NSString).length);

     

         Swift会进行自动转换到OC;

         

         4.7 类型引用

         

         Swift实例拥有类型,Swift针对这一目的提供了type(of:)方法,可以通过该方法访问其类型,这样调用类属性就有了一种新方法;

 

        class HClassA {            class var toSay: String {                return "woof"            }            func typyExp(what:HClassA.Type)  {            }        }        let classA = HClassA()        print( type(of: classA).toSay)        classA.typyExp(what: HClassA.self)        classA.typyExp(what: type(of: classA))

       

         某些情况下,你会将对象类型作为值进行传递,你需要知道:

         1)声明接收某个对象类型,请使用点符号分隔类型名与关键字Type;

         2)将对象类型作为值,请使用类型名后点符号加self,或用type(of:)方法取出类型;

         

         典型场景就是函数是个实例工厂:给定一个类型,他会创建该类型的实例,可能还会做一些处理,然后返回;(由于子类即父类,工厂方法对应的类型参数的初始化方法需要被标记为required,否则编译器无法确定该初始化器是否会被每个子类型实现)

         

         self/Self也会在类型引用中表现出多态:

         1)self用在实例代码中表示对多态语义下的当前实例;

         用在静态/类代码中表示多态语义下的类型,self.init(...)会实例化该类型;

         2)Self 在方法声明中,如果指定了返回类型,那么它表示多态化的类型或实例的类型;

         

         4.8 协议


 协议是一种表示不相关类型共性的方式;

         我们需要一种类型可以以某种方式透过透过类继承体系,将不相关的类集成到一起;

         

         重要且强大的对象可以是结构体而非类。不过结构体并不存在子父类的层次关系,也就是没有继承;

         但其实结构体也可以向类一样拥有和表达正常的共性特征——通过协议;

         Swift头文件中定义了70多个协议;可与OC的协议交换;

         

         协议是一种对象类型,不过并没有协议对象——你无法实例化协议;

         协议声明的只是一些属性和方法列表而已;属性没有值。方法没有代码;

         其想法是“真实”的对象类型可以声明它属于某个协议类型,这叫做遵循协议;

         使用协议的对象类型会遵守一个契约:它会实现协议所列出的属性与方法;

         

         任何类型(枚举 结构体 类 甚至是另一个协议)都可以使用该协议;需要做的是在声明中的名字后面加上一个冒号,后跟协议名;如果使用者是个拥有父类的类,那么父类后面还需要加上一个逗号,协议则位于该逗号后面;

         

         注:Swift2.0支持协议扩展,协议可以声明方法并提供实现;

         

         4.8.1 为何使用协议

         

         我们定义一个协议Filter,声明方法fly;

         协议是一种类型;

         因此,我们可以在需要类型的时候使用Filer;

         

protocol Flier {    func fly()}

        func tellToFly( flier :Flier) {            flier.fly()        }

        

         上面代码体现了协议的精髓,协议是一种类型,因此适用于多态;协议赋予我们表述父类与子类的另一种方式,根据替换法则,这里的Filer可以是任何对象类型的实例,枚举、结构退、类,只要它使用了Flier协议即可,他一定有fly方法;

         不过反过来:拥有fly方法的对象不一定就是Flier,因为他不一定遵循了协议;

         

         Swift提供的最有用的协议之一是CustomStringConvertible;要求实现一个description String属相,用于表示当前实例;

 

        enum FilterProto :String,CustomStringConvertible {            case Albus = "Albus"            case Playlises = "Playlises"            case Podcasts = "Podcasts"            var description: String {                return self.rawValue            }        }        print(FilterProto.Albus)

        

         现在向print传递一个Filter或将其插入字符串中,其description将会被自动打印出来;

         一个类可以使用多个协议,多个协议之间用逗号分隔;

         

         4.8.2 协议类型测试与转换

         

         协议是一种类型,协议的使用者是其“子类型”,这里使用了多态;

         对于真实对象类型的那些运算符也可以用于声明为协议类型的对象;

         比如,Flier被Bird使用,就可以通过运算符is测试某个Flier是否为Bird;

 

        class Bird:Flier {            func fly() {                print("Bird can fly!")            }        }        func isBird(f:Flier) -> Bool {            return f is Bird        }
        

         与之类似,as!与as? 可用于将生命为协议类型的对象向下转换为其真正的类型,这很重要,因为使用协议的对象可以接收协议无法接收的消息;

         

         4.8.3 声明协议

         

         只能在文件顶部声明协议,使用关键字protocol,后跟协议名;

         作为一种对象类型,协议名首字母应该大写;然后以一对花括号,里面包含如下内容:

         1)属相

         协议中的属性声明包含了var(不是let),属性名、冒号、类型,以及包含单词get或get set 的一对花括号;

         {get}:使用者对该属性的实现至少是可读的;

         {get set}:使用者对该属性的实现可读、可写;

         

protocol ProtoDemo1 {    var proto1:String {        get    }    var proto2:String {        get set    }}

        class ProtoClass:ProtoDemo1 {            var proto1: String {                get {                    return "hua"                }            }            var proto2: String {                get {                    return "hua"                }                set {                                }            }        }        let protoc = ProtoClass()        print(protoc.proto1,protoc.proto2)

        

         要想声明静态属性/类属性,请在前面加上关键字static,类使用者可以将其实现为类属性;

         

         2)方法:

         任何对象函数类型都是合法的,包括init与下标(声明下标可以包含get或get set);

         要想声明静态/类方法,请在前面加上关键字static,类使用者可以将其实现为类方法;

         

         如果方法(由枚举或结构体实现的)想要声明为mutating,那么协议就必须指定mutating指令;

         如果协议没有指定mutating,那么使用者将无法添加,不过,如果协议指定了mutating,那么使用者可以将其省略;

         

         3)类型别名:

         协议可以通过声明类型别名为声明中的类型指定局部同义词;

         

         4)协议使用

         协议本身还可以使用一个或多个协议,语法与你想象的一样,声明中的协议名后面是冒号,后面跟它所使用的协议列表,中间逗号分隔;

         事实上这种方式创建了一个二级类型层次;

         

         处于清晰的目的,使用了另一个协议的协议可以重复被使用的协议花括号中的内容(当然 他已经隐式的被做了);

         

         注:

         如果协议的唯一目的是将其他协议组合起来,可以通过即时创建组合协议以避免声明协议 protocol< ... , ... >,其中尖括号的内容是逗号分隔的协议列表;

         

         4.8.4 可选协议成员

         

         为了与OC兼容,需要与OC显示桥接,方式是在声明前加上@objc属性(在协议声明前,或具体的成员声明前);

         被@objc标记的协议,成员可以声明为可选,表示该成员不必须被使用者实现;

         参见protodemo2;

         只有类才可以使用这种协议,并且必须符合如下两种情况之一才能使用:

         1)类是NSObject的子类;(iOS中委托类使用UIApplicationDelegate协议中的可选方法时,就是这种,因为委托类是NSObject的子类)

         2)类中可选成员实现时被标记为@objc特性;

 

@objc protocol ProtoDemo2 {    @objc optional var song:String{get}    @objc optional func sing()}

        class BirdA:ProtoDemo2 {            var song: String = "haha"            func sing() {                print("Bird song!")            }        }        let birdA = BirdA()        birdA.sing()        print(type(of: birdA.song))//若song没被实现 则birdA是无法使用这个属性的        print(birdA.song)//String                func testOpProto(p:ProtoDemo2){            print(type(of: p.song))//Optional<String>            print(p.song ?? "error")//haha        }        testOpProto(p: birdA)        print(type(of: birdA.song))//String                let birdB:ProtoDemo2 = birdA as ProtoDemo2        print(type(of: birdB.song))//Optional<String>

        

         这里实际上测试发现,即便上述两个条件不满足:如上例,既不是NSObject子类,也没有对成员进行@objc标记;

         但仍可以使用该协议(仅仅是一个类,这个是必要的);

         协议声明时@objc-optional是一起搭配使用的;鉴于与OC的兼容,还是推荐遵循上述两条原则;

         

         注:

         虽然,ProtoDemo2协议有可选属性声明,但只要实现的类没有实现(因为可选嘛),就不能调用具体的属性;

         对于实现者类型来讲,只要实现了相应的属性,就可以使用(不再是可选);

         对于协议类型来讲,可选属性是可以使用的,对应的是一个可选值;

         实现者类型与协议类型之间是可以使用as转换的;

         

        birdB.sing?()

         对于sing这样的可选方法来讲:方法本身会被自动变成其所声明类型的Optional版本;效果相当于只有当f实现了sing时才向其发送sing消息,如果未实现,那就什么都不会发生;

         如果可选方法返回一个值,那么他会被包装到Optional中;

         

         4.8.5 类协议

         

         名字后面的冒号后使用关键字class声明的协议为类协议;

         表示该协议只能由类对象类型使用;

         

         如果协议已经被标记为@objc,那就无须使用class;@objc特性隐含表示这是个类协议;

         

protocol ProtoDemo3:class {    func sing()}

       class BirdC:ProtoDemo3 {            func sing() {                print("sing!")            }        }
        

        

         声明类协议的典型目的在于利用专属类的内存管理特性;

         关键字weak标识delegate属性将会使用特殊的内存管理,只有类实例可以使用这种特殊的内存管理;

         声明为类协议就是要告诉编译器这个协议是给类实例用的;

         

         4.8.6 隐式必备初始化器

         

         若协议声明了一个初始化器,同时一个类使用了该协议;那么该类及其子类必须实现这个初始化器;

         这样在协议中声明的初始化器就是隐式必备的,但要求类需要显示满足这个要求,即将相应的初始化器标记为required;

         如果将当前类标记为final,那就没必要将init标记为required了;

         

         前面我们知道:父类实现的被标记为required的初始化器会被子类继承,但是如果子类声明了自己的初始化器,阻止了继承,就需要进行重写该初始化器;

         NSCoding协议声明了init(coder:)初始化器,就存在这种问题,如果遵循该协议的类声明了自己的初始化器,就一定要实现这个init(coder:)初始化器(非final类的话,还是需要标记为required);

         

         4.8.7 字面值转换

         

         之所以能通过字面量的方式进行变量的初始化,是因为Swift提供了字面量转换协议:(他们声明在Swift的头文件中)

         ·NilLiteralConvertible;

         ·BooleanLiteralConvertible;

         ·IntegerLiteralConvertible;

         ·FloatLiteralConvertible;

         ·StringLiteralConvertible;

         ·ExtendedGraphemeClusterLiteralConvertible;

         ·UnicodeScalarLiteralConvertible;

         ·ArrayLiteralConvertible;

         ·DictionaryLiteralConvertible;

         

         我们自定义一个类,遵循IntegerLiteralConvertible,从而使用字面量转换协议进行初始化:

         这里注意下,在Swift4中这些协议名 被重新命名了,比如IntegerLiteralConvertible -> ExpressibleByIntegerLiteral;

 

        class Nest:ExpressibleByIntegerLiteral {            typealias IntegerLiteralType = Int                        var eggCount:Int = 0            init() {                            }            required init(integerLiteral value: Nest.IntegerLiteralType) {                self.eggCount = value            }        }                let nest:Nest = 4        print(nest,nest.eggCount)

         4.9 泛型


泛型是一种类型占位符,实际的类型会在稍后进行填充;

         重要的是理解泛型并没有放松Swift严格的类型;特别地,泛型并没有将类型解析推迟到运行时;

         在使用泛型时,代码仍然需要指定真实的类型;

         

         占位符就是泛型,只不过,使用时,会被解析为实际的特定类型;

         

         Optional就是最好的例子:

         

         enum Optional<Wrapped>{

             case Nome

             case Some(Wrapped)

             init(_ some:Wrapped)

             //……

         }

         

         语法上:

             在声明中,我们使用了一个假的类型(类型占位符),叫作Wrapped;它是一个真实且单一的类型,当我们说Wrapped时,指的就是一个特定的类型;

         Optional中的初始化器,接受的参数类型就是Wrapped的类型;

         

         4.9.1 泛型声明

         

         什么地方可以声明泛型?

         

         protocol FlierA {

             associatedtype Other

             func fly()

             func flockTogetherWith(f:Self)

             func matchWith(f:Other)

         }

         

         1)使用Self的泛型协议:

             Self就是一个占位符,表示使用者的类型(在协议中,使用Self会将协议转换为泛型);

         

         看一下FilerA协议的方法:func flockTogetherWith(f:Self);

         这表示,如果一个类Bird遵循了Filer协议,那么实现该方法时就需要将其f参数声明为Bird;

         2)使用空类型别名的泛型协议:

             协议可以声明类型别名,而不必定义类型别名表示什么;(即,typealias语句并不会包含等号);

         这会将协议转换为泛型:别名的名字(也叫做关联类型)是个占位符,实际对应的关键字是associatedtype;

         

         使用者会在泛型使用类型别名的地方声明特定的类型,从而解析出占位符;

 

protocol FlierA {    associatedtype Other    func fly()    func flockTogetherWith(f:Self)    func matchWith(f:Other)}

        class FlierAClass :FlierA {            typealias Other = Bird                        func fly() {                            }            func flockTogetherWith(f: FlierAClass) {                            }            func matchWith(f:Other) {//直接指定一个类型,就不必要定义other别名了                            }        }


        

         我们定义了一个类,遵循了FlierA协议:需要定义别名(typealias),实现相应的方法;注意Self和Other声明位置的类型替换;

         3)泛型函数:

         函数声明可以对其参数,返回类型以及在函数体中使用泛型占位符;请在函数名后面的尖括号中声明占位符的名字:

 

        class FlierB {            func flyBing<T>(t:T) -> T {                return t            }        }

        

         调用者会在函数声明中占位符出现的位置使用特定的类型,从而解析出占位符;

         4)泛型对象类型:

         对象类型声明可以在花括号中使用泛型占位符类型;(请在对象类型名后的尖括号中声明占位符名字)

         该对象类型的使用者会在对象类型声明中占位符出现的地方使用特定的类型,从而解析出占位符;

         

         对于使用了尖括号语法的泛型函数与对象类型,尖括号中可以包含多个占位符名,中间用逗号分隔;

         

         4.9.2 类型约束

         

         限制用于解析特定占位符的类型,叫做类型限制;

         最简单的类型限制形式是其首次出现时,在占位符名后面加上一个冒号和一个类型名(冒号后面的类型名可以是类名或是协议名);

         

         我们举几个应用类型约束的场景:

         我们定义了两个协议FlierC FlierCSuper,大家可能会觉得疑惑,为啥有个Super的,这是因为冒号后面指定的是类型约束,而协议不能将自身作为约束类型,因而通过一个被遵循的协议达到这一目的(姑且叫做父协议,不过请注意,两者的关系实际上是一个遵循了另外一个,是一个层级关系,只不过和子父类关系有点像);

         现在,我们定义的对象类型,遵循FlierC之后实现的方法,参数就可以是任何一个遵循FlierC协议的对象类型了;

 

protocol FlierCSuper {    }protocol FlierC :FlierCSuper  {    associatedtype Other : FlierCSuper    func flockTogetherWith(f:Other)    }

        class FlierCClass :FlierC {            func flockTogetherWith(f: FlierCClass) {                            }        }

        

         我们看到,参数类型可以是任何一个遵循了当前协议的类型,而不必要as为协议类型;

         

         我们在定义一个myMin方法,获得多个参数中的最小值:

 

    func myMin<T:Comparable>(things:T...) -> T {        var minemum = things[0]        for ix in 0..<things.count {            if things[ix] < minemum {                minemum = things[ix]            }        }        return minemum    }

        print( self.myMin(things: 1,2,6,2,4,1))

        

         因为涉及到比较,参数类型需要遵循Comparable协议;

         

         泛型协议(只有两种:声明中使用了Self或拥有关联类型的协议)只能用在泛型类型当中,并且作为类型限制;

         什么意思呢?对比下面这两个方法就清楚了:

         Flier是一个泛型协议;

         func flockTowTogether(f1:Flier,f2:Flier){}//编译会出错

         func flockTowTogether<T1:Flier,T2:Flier>(f1:T1,f2:T2){}//正确

         

         4.9.3 显示特化

         

         目前为止所有泛型的使用者都是通过推断来解析占位符类型的;

         另一种是手工解析类型,叫做显示特化;

         某些情况下,如果占位符类型无法推断得出,那么就需要强制显示特化;

         

         有以下两种形式的显示特化:

         1)拥有关联类型协议的泛型协议:

         协议的使用者,可以通过typealias声明手工解析协议的关联类型,方式是使用协议别名与显示类型赋值;(前面有示例,这里就不举了)

         2)泛型对象类型:

         泛型对象类型的使用者可以通过相同的尖括号语法手工解析出对象的占位符类型;

         

        class DogT<T>{            var name :T?        }        let d = DogT<String>()        d.name = "haha"        print(d.name ?? "NIL")

        

         注意:不能显示特化泛型函数;但是可以使用非泛型函数;

         啥是非泛型函数?

             就是使用了泛型占位符(比如T),但是函数声明中并没有使用<>尖括号语法;

         

         如果继承泛型类,你可以这样做:

 

        class NoisyDogT<T>:DogT<T> {                    }        //或者        class NoisyDog:DogT<String>{                    }

        

         

         4.9.4 关联类型链

         

         如果具有关联类型的泛型协议使用了泛型占位符,我们可以通过对占位符名使用点符号将关联类型名连接起来,从而指定类型;

         这句话不太好理解,不过别急,我们几个例子试试:

             假设一个游戏,士兵和弓箭手彼此为敌,我们通过将Soldier结构体和Archer结构体纳入拥有Enemy关联类型的Fighter协议中来表示这一点;Enemy本身又被限制为一个Fighter;

 

protocol SuperFighter {    associatedtype Weapon:Wieldable}protocol Fighter:SuperFighter {    associatedtype Enemy:SuperFighter    func steal(weapon:Self.Enemy.Weapon,from:Self.Enemy) -> Void}protocol Wieldable {    }

        struct Soldier:Fighter {            typealias Enemy = Archer            typealias Weapon = Sword                        func steal(weapon: Bow, from: Archer) {                            }        }                struct Archer:Fighter {            typealias Enemy = Soldier            typealias Weapon = Bow                        func steal(weapon: Sword, from: Soldier) {                            }        }        //现在创建一个泛型结构体,来表示这些战士对面的营地:        struct Camp<T:Fighter> {            var spy:T.Enemy?        }

        

         假设一个营地可以容纳一个来自对方类型的间谍,我们可以通过将关联类型名链接到占位符名来清楚的表示这一点:

         

         结果就是,针对特定的Camp,如果T被解析为Soldier,那么T.Enemy就表示Archer;

 

        var c = Camp<Soldier>()        c.spy = Archer()

        

         使用更长的关联类型名链也是可以的,特别是当泛型协议有一个关联类型,这个关联类型本身又被强制约束为一个拥有关联类型的泛型协议时更是如此,而且往往也更能说明问题;

         我们为每一类的Filter赋予一个武器,并置于Wieldable协议下

 

        struct Sword:Wieldable{                    }        struct Bow:Wieldable{                    }

        

         我们为Fighter协议添加了一个可以从敌方偷取武器的方法

         func steal(weapon:Self.Enemy.Weapon,from:Self.Enemy) -> Void;

         再读一下之前的代码(在上一示例基础上添加的):我们会看到,从士兵处偷剑,从弓箭手处头弓的协议方法;

         

         Swift头文件大量使用了关联类型链;

         SequenceType泛型协议,有一个关联类型Generator,他们约束为泛型Generator协议的使用者,反过来他会有一个关联类型Element;

         

         4.9.5 附加约束

         

         对可解析的类型做进一步的限制;

         

         在泛型协议中,类型别名约束中的冒号与类型声明中的冒号是一个意思:

         类型声明中的冒号之后,如果是逗号分隔的多个协议,表示当前类型需要遵循这些协议;

         类型别名约束中的冒号之后的多个协议,则表示关联类型只能被解析为满足这些协议的类型;

         看下 Generic这个协议就明白了;

         

protocol FlierG {    }protocol WalkerG {    }protocol Generic {    associatedtype T : FlierG , WalkerG //关联类型T只能被解析为使用FlierG和WalkerG协议的类型    associatedtype U : Dog , FlierG     //关联类型U只能被解析为Dog(及其子类),并使用了Flier协议的类型;}


         where子句:

         1)在泛型函数或对象类型的尖括号中,这种语法非法;可以附加一个where子句,其中包含一个或多个逗号分隔的对所声明的占位符的附加约束;

         

         如:func flyAndWalk<T where T:FlierG , T:WalkerG>(f:T){}

         

         2)where子句还可以对已经包含了占位符的泛型协议的关联类型进行附加约束,方法是使用关联类型链;

         比如:

         protocal Filer {

             associate Other

         }

         func flockTogether<T:Flier where T.Other:Equatable>(f:T){}

         那么作为参数的实例,就需要遵循Flier协议同时,所声明的关联类型Other的类型还需要满足Equatable协议;

         

        

         3)除了冒号,还可以使用==号。后面跟一个类型:此时要求,关联类型链最后的类型必须是这个精确的类型,而不能仅仅是协议使用者或子类;

         ==运算符右侧的类型本身可以是个关联类型链,两个链中末尾被解析出的类型必须要相同;

         

         Swift头文件大量使用了这种==的where的子句:

         如: mutating func appendContents<S:Sequence where S.Generator.Element == Character>(newElements:S)

         Generator是Sequence的使用者,他有一个关联类型Element,他需要满足是Character类型;也就是说,传入的参数需要是一个字符序列;("!" as Charater,单个字符串类型是可以做as类型转换的)

         

         再如:mutating func appenContentsOf<S : SequenceType where S.Generator.Element == Element>(newElements:S)

         实参序列的元素类型必须要与现有数组元素类型相同;


         4.10 扩展

详见下一篇《iOS编程基础-Swift(四)-对象类型(续)》


原创粉丝点击