Swift中文教程(16)自动引用计数

转载自letsswift.com

Swift使用自动引用计数(ARC)来管理应用程序的内存使用。这表示内存管理已经是Swift的一部分,在大多数情况下,你并不需要考虑内存的管理。当实例并不再被需要时,ARC会自动释放这些实例所使用的内存。

但是,少数情况下,你必须提供部分代码的额外信息给ARC,这样它才能够帮你管理这部分内存。本章阐述了这些情况并且展示如何使用ARC来管理应用程序的内存。

注意
引用计数仅仅作用于类实例上。结构和枚举是值类型,而非引用类型,所以不能被引用存储和传递。

1、ARC怎样工作
每当你创建一个类的实例,ARC分配一个内存块来存储这个实例的信息,包含了类型信息和实例的属性值信息。

另外当实例不再被使用时,ARC会释放实例所占用的内存,这些内存可以再次被使用。

但是,如果ARC释放了正在被使用的实例,就不能再访问实例属性,或者调用实例的方法了。直接访问这个实例可能造成应用程序的崩溃。

为了保证需要实例时实例是存在的,ARC对每个类实例,都追踪有多少属性、常量、变量指向这些实例。当有活动引用指向它时,ARC是不会释放这个实例的。

为实现这点,当你将类实例赋值给属性、常量或变量时,指向实例的一个强引用(strong reference)将会被构造出来。被称为强引用是因为它稳定地持有这个实例,当这个强引用存在是,实例就不能够被释放。

2、ARC实例
下面的例子展示了ARC是怎样工作的。定义一个简单的类Person,包含一个存储常量属性name:

12345678910
classPerson{
    let name:String
    init(name:String){
        self.name = name
        println("\(name) is being initialized")
    }
    deinit {
        println("\(name) is being deinitialized")
    }}

Person类有一个初始化方法来设置属性name并打印一条信息表明这个初始化过程。还有一个析构方法打印实例被释放的信息。

下面的代码定义了是三个Person?类型的变量,随后的代码中,这些变量用来设置一个Person实例的多重引用。因为这些变量是可选类型(Person?),它们自动被初始化为nil,并且不应用任何Person实例。

123
var reference1:Person?var reference2:Person?var reference3:Person?

现在你可以创建一个Person实例并赋值给其中一个变量:

12
reference1 =Person(name:"John Appleseed")// prints "John Appleseed is being initialized"

注意这条信息:““John Appleseed is being initialized”,指出类Person的构造器已经被调用。

因为新的Person实例被赋值给变量reference1,因此这是一个强引用。由于有一个强引用的存在,ARC保证了Person实例在内存中不被释放掉。

如果你将这个Person实例赋值给更多的变量,就建立了相应数量的强引用:

12
reference2 = reference1
reference3 = reference1

现在有三个强引用指向这个Person实例了。

如果你将nil赋值给其中两个变量从而切断了这两个强引用(包含原始引用),还有一个强引用是存在的,因此Person实例不被释放。

12
reference1 =nil
reference2 =nil

直到第三个强引用被破坏之后,ARC才释放这个Person实例,因此之后你就不能在使用这个实例了:

1
reference3 nil

3、类实例间的强引用循环

在上面的例子中,ARC跟踪指向Person实例的引用并保证只在Person实例不再被使用后才释放。

但是,写出一个类的实例没有强引用指向它这样的代码是可能的。试想,如果两个类实例都有一个强引用指向对方,这样的情况就是强引用循环。

通过在类之间定义弱的(weak)或无主的(unowned)引用可以解决强引用循环这个问题。这些方法在“解决类实例间的强引用循环“(“Resolving Strong Reference Cycles Between Class Instances”)描述。但是,在学习怎样解决这个问题前,先来理解这样的循环是怎样造成的。

下面的例子描述了强引用循环是怎样无意中被造成的。定义了两个类Person和Apartment,建立了公寓和它的住户间的关系

123456789101112
classPerson{
    let name:String
    init(name:String){self.name = name }
    var apartment:Apartment?
    deinit { println("\(name) is being deinitialized")}}classApartment{
    let number:Int
    init(number:Int){self.number = number }
    var tenant:Person?
    deinit { println("Apartment #\(number) is being deinitialized")}}

每个Person实例有一个String类型的属性name和一个初值为nil的可选属性apartment。之所以apartment是可选属性,因为一个住户可能并没有一个公寓。

同样,每个Apartment实例有一个Int类型的属性number和一个初值为nil的可选属性tenant,一个公寓(apartment)并不总是有人居住,所以tenant是可选属性。

两个类都定义了析构方法,打印表明类实例被析构的语句,这告诉你Person和Apartment实例是否如愿的被释放掉了。

下面的代码定义了两个可选类型变量john和number73,将用来设置之后的Apartment和Person实例。两个变量都被初始化为nil:

12
var john:Person?var number73:Apartment?

下面创建两个Person实例和Apartment实例赋值给上面的变量:

12
john =Person(name:"John Appleseed")
number73 =Apartment(number:73)

下面的图表明在创建这两个实例并赋值后的样子,变量john有一个指向Person实例的强引用,变量number73有一个指向Apartment实例的强引用:
image: ../Art/referenceCycle01_2x.png

现在你可以将这两个实例连接起来,使得一个住户与一个公寓一一对应起来,注意感叹号用来解开(?unwrap)并访问john和number73中的可选变量,因此属性可以被设置:

12
john!.apartment = number73
number73!.tenant = john

下图是连接后的强引用管理图示:
image: ../Art/referenceCycle02_2x.png

不幸的是,这样做造成了两个实例间的强引用循环。因此,当你破坏john和number73变量间的强引用、时,引用计数并没有减少到0,ARC也不会释放实例:

12
john =nil
number73 =nil

当你将两个变量设置为nil时,各自的析构方法都不会被调用到。强引用循环防止了Person和Apartment实例被释放造成的内存泄漏。

下图是设置john和number73为nil后的情况:
image: ../Art/referenceCycle03_2x.png

Person实例和Apartment之间的强引用并没有被破坏掉。

4、解决类实例之间的强引用循环

Swift提供了两种方法解决类实例属性间的强引用循环:弱引用和无主(unowned)引用。

弱引用和无主引用使得一个引用循环中实例并不需要强引用就可以指向循环中的其他实例。互相引用的实例就不用形成一个强引用循环。

当在生命周期的某些时刻引用可能变为nil时使用弱引用。相反,当引用在初始化期间被设置后不再为nil时使用无主引用。

弱引用

弱引用并不保持对所指对象的强烈持有,因此并不阻止ARC对引用实例的回收。这个特性保证了引用不成为强引用循环的一部分。指明引用为弱引用是在生命属性或变量时在其前面加上关键字weak。

使用弱引用不管在生命周期的某时刻是否有值。如果引用一直有值,使用无主引用(见无主引用节)。在上例中,公寓不可能一直都有住户,所以应该使用弱引用,来打破强引用循环。

注意
弱引用必须声明为变量,指明它们的值在运行期可以改变。弱引用不能被声明为常量。

因为弱引用可以不含有值,所以必须声明弱引用为可选类型。因为可选类型使得Swift中的不含有值成为可能。

因为弱引用的这个特性,所以当弱引用指向实例时实例仍然可以被释放。实例释放后,ARC将弱引用的值设置为nil。你可以想其他可选类型一样检查弱引用的值,你也不会使用所指向实例不存在的的引用。

与上面的例子不同,下例声明了Apartment的属性tenant为弱引用:

123456789101112
classPerson{
    let name:String
    init(name:String){self.name = name }
    var apartment:Apartment?
    deinit { println("\(name) is being deinitialized")}}classApartment{
    let number:Int
    init(number:Int){self.number = number }
    weak var tenant:Person?
    deinit { println("Apartment #\(number) is being deinitialized")}}

依然像前例一样创建两个变量(john和number73)和它们之间的强引用:

123456
var john:Person?var number73:Apartment?
john =Person(name:"John Appleseed")
number73 =Apartment(number:73)
john!.apartment = number73
number73!.tenant = john

下图是两个实例之间是怎样引用的:
image: ../Art/weakReference01_2x.png

Person实例仍然有一个到实例Apartment的强引用,相反实例Apartment实例只有一个到Person实例弱引用。意味着当你破坏john变量持有的强引用时,到Person实例的强引用就不存在了。
image: ../Art/weakReference02_2x.png

因为没有到Person实例的强引用,实例可以释放:

1
john nil

仅存的强引用是从变量number73到Apartment实例的强引用,当你破坏它时,这个强引用就不存在了:
image: ../Art/weakReference03_2x.png

因此可以释放Person:

1
number73 nil

上面两段代码展示了Person实例和Aparment实例的析构,当两个变量分别被设置为nil时,就打印各自的析构提示信息,展示了引用循环被打破了。

无主引用

和弱引用一样,无主引用也并不持有实例的强引用。但和弱引用不同的是,无主引用通常都有一个值。因此,无主引用并不定义成可选类型。指明为无主引用是在属性或变量声明的时候在之前加上关键字unowned。

因为无主引用非可选类型,所以每当使用无主引用时不必解开(unwrap?)它。无主引用通常可以直接访问。但是当无主引用所指实例被释放时,ARC并不能将引用值设置为nil,因为非可选类型不能设置为nil。

注意
在无主引用指向实例被释放后,如果你像访问这个无主引用,将会触发一个运行期错误(仅当能够确认一个引用一直指向一个实例时才使用无主引用)。在Swift中这种情况也会造成应用程序的崩溃,会有一些不可预知的行为发生,尽管你可能已经采取了一些预防措施

接下来的例子定义了两个类,Customer和CreditCard,表示一个银行客户和信用卡。这两个类的属性各自互相存储对方类实例。这种关系存在着潜在的强引用循环。

这两个类之间的关系稍微和前面的Person和Apartment有些不同。在此例中,一个客户可能有也可能没有一个信用卡,但是一个信用卡必须由一个客户持有。因此,类Customer有一个可选的card熟悉,而类CreditCard有一个非可选customer属性。

另外,创建CreditCard实例时必须必须向其构造器传递一个值number和一个customer实例。这保证了信用卡实例总有一个客户与之联系在一起。

因为信用卡总由一个用户持有,所以定义customer属性为无主引用,来防止强引用循环。

1234567891011121314151617
classCustomer{
    let name:String
    var card:CreditCard?
    init(name:String){
        self.name = name
    }
    deinit { println("\(name) is being deinitialized")}}classCreditCard{
    let number:Int
    unowned let customer:Customer
    init(number:Int, customer:Customer){
        self.number = number
        self.customer = customer
    }
    deinit { println("Card #\(number) is being deinitialized")}}

下面的代码段定义了Customer类型可选变量john,用来存储一个特定用户的引用,这个变量初值为nil:

1
var john:Customer?

现在可以创建一个Customer实例,并初始化一个新的CreditCard实例来设置customer实例的card属性:

12
john =Customer(name:"John Appleseed")
john!.card =CreditCard(number:1234_5678_9012_3456, customer: john!)

下图是引用间的连接管理:
image: ../Art/unownedReference01_2x.png

Customer实例有一个到CreditCard实例的强引用,CreaditCard实例有一个到Customer实例无主引用。

因为无主引用的存在,当你破坏变量john持有的强引用时,就再也没有到Customer实例的强引用了。

image: ../Art/unownedReference02_2x.png

因为没有到Customer实例的强引用,实例被释放了。之后,到CreditCard实例的强引用也不存在了,因此这个实例也被释放了:

123
john =nil// prints "John Appleseed is being deinitialized"// prints "Card #1234567890123456 is being deinitialized"

上面的代码段显示了变量john设置为nil后Customer实例和CreditCard实例被析构的信息。

无主引用和隐式拆箱可选属性
上面的弱引用和无主引用例子是更多常见场景中的两个,表面打破强引用循环是必要的。

例子Person和Apartment显示了当互相引用的两个属性被设置为nil时可能造成强引用循环。这种情况可以使用弱引用来解决。

例子Customer和CreditCard显示了一个属性可以设置为nil,而另一个不可以为nil时可能造成的强引用循环。这种情况可以使用无主引用解决。

但是,有第三种情况,两个属性都一直有值,并且都不可以被设置为nil。这种情况下,通常联合一个类种的无主属性和一个类种的隐式装箱可选属性(implicitly unwrapped optional property)。

这保证了两个属性都可以被直接访问,并且防止了引用循环。这一节展示了如何进行这样的设置。

下面的例子定义了两个类,Coutry和City,两者的属性都存放另外一个类的实例。在数据模型中,每个国家都有一个首都,而每个城市都属于一个国家。代码如下:

12345678910111213141516
classCountry{
    let name:String
    let capitalCity:City!
    init(name:String, capitalName:String){
        self.name = name
        self.capitalCity =City(name: capitalName, country:self)
    }}classCity{
    let name:String
    unowned let country:Country
    init(name:String, country:Country){
        self.name = name
        self.country = country
    }}

为表达这样的关系,City的构造器有一个参数为Country实例,并将他存为country属性。

City的构造器也在Country的构造器中被调用。但是直到一个新的Country实例被完整地初始化,Country构造器不能传递self给City的构造器(在两阶段初始化“Two-Phase Initialization”节描述)

为处理这样的情况,声明Country的属性capitalCity属性为隐式拆箱可选属性,通过在类型注解后加检测符号实现(City!)。这表明capitalCity属性像其他可选属性一样有一个默认值nil,但是可以不需要对值拆箱就可以访问(隐式拆箱选项(implicitly unwrapped optionals)描述)

因为capitalCity有一个默认值nil,所以当在Country的构造器中设置了name属性的值后,一个新的Country实例就完全地被初始化了,这表明Country构造器已经可以传递隐式self属性,从而设置capitalCity属性值。

这表明你可以在一个单独的语句中创建Country和CIty实例,不会造成强引用循环,并且可以不用使用检测符号(!)解包可选值来直接访问capitalCity属性:

123
var country =Country(name:"Canada", capitalName:"Ottawa")
println("\(country.name)'s capital city is called \(country.capitalCity.name)")// prints "Canada's capital city is called Ottawa"

上面的例子中,隐式解包选项的使用表示分阶段初始化是可行的。当初始化完成时,capitalCity属性可以像一个非可选值那样访问,并且不会造成强引用循环。

5、闭包的强引用循环

前面你知道了当两个类实例持有对方的强引用时强引用循环是怎样被创建的。你也知道了怎样使用弱引用和无主引用来破坏强引用循环。

当将一个闭包赋值给一个类实例的属性,并且闭包体捕获这个实例时,也可能存在一个强引用循环。捕获实例是因为闭包体访问了实例的属性,就像self.someProperty,或者调用了实例的方法,就像self.someMethod()。不管哪种情况,这都造成闭包捕获self,造成强引用循环。

这个强引用循环的存在是因为闭包和类一样都是引用类型。当你将闭包赋值给属性时,就给这个闭包赋值了一个引用。本质上和前面的问题相同-两个强引用都互相地指向对方。但是,与两个类实例不同,这里是一个类与一个闭包。

Swift为这个问题提供了一个优美的解决方法,就是闭包捕获列表。但是,在学习怎样通过闭包捕获列表破坏强引用循环以前,有必要了解这样的循环是怎样造成的。

下面的例子展示了当使用闭包引用self时强引用循环是怎样造成当。定义了一个名为HTMLElement的类,建模了HTML文档中的一个单独的元素:

123456789101112131415161718
classHTMLElement{
    let name:String
    let text:String?
    @lazyvar asHTML:()->String={
        if let text =self.text {
            return"<\(self.name)>\(text)"
        }else{
            return"<\(self.name) />"
        }
    }
    init(name:String, text:String?=nil){
        self.name = name
        self.text = text
    }
    deinit {
        println("\(name) is being deinitialized")
    }}

这个HTMLElement类定义了一个表示元素(例如“p“,”br“)名称的属性name,和一个可选属性text,表示要在页面上渲染的html元素的字符串的值

另外,还定义了一个懒惰属性asHTML。这个属性引用一个闭包,这个闭包结合name与text形成一个html代码字符串。这个属性类型是()-> String,表示一个函数不需要任何参数,返回一个字符串值。

默认地,asHTML属性赋值为返回HTML标签字符串的闭包。这个标签包含了可选的text值。对一个段落而言,闭包返回”<p>some text</p>”或者”<p />”,取决其中的text属性为“some text”还是nil。

asHTML属性的命名和使用都和实例方法类似,但是,因为它是一个闭包属性,如果想渲染特定的html元素,你可以使用另外一个闭包来代替asHTML属性的默认值。

这个HTMLElement类提供单一的构造器,传递一个name和一个text参数。定义了一个析构器,打印HTMLElement实例的析构信息。

下面是如何使用HTMLElement类来创建和打印一个新的实例:

123
var paragraph:HTMLElement?=HTMLElement(name:"p", text:"hello, world")
println(paragraph!.asHTML())// prints "hello, world"

不幸的是,上面所写的HTMLElemnt类的实现会在HTMLElement实例和闭包所使用的默认asHTML值之间造成强引用循环,下面是其图示:
image: ../Art/closureReferenceCycle01_2x.png

实例的asHTML属性持有其闭包的一个强引用,但是因为闭包在其类内引用self(self.name和self.text方式),闭包捕获类本身,意味着它也持有到HTMLElement实例的引用。强引用循环就这样建立了。(关于闭包捕获值的更多信息,参见CapturingValues)

如果设置paragraph变量值为nil,破坏了到HTMLElement实例的强引用,实例和其闭包都不会被析构,因为强引用循环:

1
paragraph nil

注意HTMLElement析构器中的提示信息不会被打印,表示HTMLElement实例并没有被析构。

6、解决闭包的强引用循环
通过定义捕获列表为闭包的一部分可以解决闭包和类实例之间的强引用循环。捕获列表定义了在闭包体内何时捕获一个或多个引用类型的规则。像解决两个类实例之间的强引用循环一样,你声明每个捕获引用为弱引用或者无主引用。究竟选择哪种定义取决于代码中其他部分间的关系

定义捕获列表
捕获列表中的每个元素由一对weak/unowned关键字和类实例(self或someInstance)的引用所组成。这些对由方括号括起来并由都好分隔。

将捕获列表放在闭包参数列表和返回类型(如果提供)的前面:

1234
@lazyvar someClosure:(Int,String)->String={
    [unowned self](index:Int, stringToProcess:String)->Stringin
    // closure body goes here}

如果闭包没有包含参数列表和返回值,它们可以从上下文中推断出来的话,将捕获列表放在闭包的前面,后面跟着关键字in:

1234
@lazyvar someClosure:()->String={
    [unowned self]in
    // closure body goes here}

弱引用和无主引用
当闭包和实例之间总是引用对方并且同时释放时,定义闭包捕获列表为无主引用。
当捕获引用可能为nil,定义捕获列表为弱引用。弱引用通常是可选类型,并且在实例释放后被设置为nil。这使得你可以在闭包体内检查实例是否存在。

在例子HTMLElement中,可以使用无主引用来解决强引用循环问题,下面是其代码:

12345678910111213141516171819
classHTMLElement{
    let name:String
    let text:String?
    @lazyvar asHTML:()->String={
        [unowned self]in
        if let text =self.text {
            return"<\(self.name)>\(text)"
        }else{
            return"<\(self.name) />"
        }
    }
    init(name:String, text:String?=nil){
        self.name = name
        self.text = text
    }
    deinit {
        println("\(name) is being deinitialized")
    }}

这个HTMLELement实现在之前的基础上在asHTML闭包中加上了捕获列表。这里,捕获列表是[unowned self],表示作为无主引用来捕获自己而不是强引用。

你可以像之间一样创建和打印HTMLElement实例:

123
var paragraph:HTMLElement?=HTMLElement(name:"p", text:"hello, world")
println(paragraph!.asHTML())// prints "hello, world"

下图是使用捕获列表后的引用图示:
image: ../Art/closureReferenceCycle02_2x.png

此时,闭包捕获自身是一个无主引用,并不持有捕获HTMLelement实例的强引用。如果你设置paragraph的强引用为nil,HTMLElement实例就被释放了,可以从析构信息中看出来:

12
paragraph =nil// prints "p is being deinitialized"

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、约书亚、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/automatic-reference-counting,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(15)析构

转载自letsswift.com

在一个类的实例被释放之前,析构函数会被调用。用关键字deinit来定义析构函数,类似于初始化函数用init来定义。析构函数只适用于class类型。

1、析构过程原理
Swift 会自动释放不再需要的实例以释放资源。如自动引用计数那一章描述,Swift 通过自动引用计数(ARC)处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前关闭该文件。

在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号:

deinit {
// 执行析构过程
}
析构函数是在实例释放发生前一步被自动调用。不允许主动调用自己的析构函数。子类继承了父类的析构函数,并且在子类析构函数实现的最后,父类的析构函数被自动调用。即使子类没有提供自己的析构函数,父类的析构函数也总是被调用。

因为直到实例的析构函数被调用时,实例才会被释放,所以析构函数可以访问所有请求实例的属性,并且根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件的名称)。

2、析构器操作
这里是一个析构函数操作的例子。这个例子是一个简单的游戏,定义了两种新类型,Bank和Player。Bank结构体管理一个虚拟货币的流通,在这个流通中Bank永远不可能拥有超过 10,000 的硬币。在这个游戏中有且只能有一个Bank存在,因此Bank由带有静态属性和静态方法的结构体实现,从而存储和管理其当前的状态。

struct Bank {
static var coinsInBank = 10_000
static func vendCoins(var numberOfCoinsToVend: Int) -> Int {
numberOfCoinsToVend = min(numberOfCoinsToVend, coinsInBank)
coinsInBank -= numberOfCoinsToVend
return numberOfCoinsToVend
}
static func receiveCoins(coins: Int) {
coinsInBank += coins
}
}
Bank根据它的coinsInBank属性来跟踪当前它拥有的硬币数量。银行还提供两个方法——vendCoins和receiveCoins——用来处理硬币的分发和收集。

vendCoins方法在 bank 分发硬币之前检查是否有足够的硬币。如果没有足够多的硬币,Bank返回一个比请求时小的数字(如果没有硬币留在 bank 中就返回 0)。vendCoins方法声明numberOfCoinsToVend为一个变量参数,这样就可以在方法体的内部修改数字,而不需要定义一个新的变量。vendCoins方法返回一个整型值,表明了提供的硬币的实际数目。

receiveCoins方法只是将 bank 的硬币存储和接收到的硬币数目相加,再保存回 bank。

Player类描述了游戏中的一个玩家。每一个 player 在任何时刻都有一定数量的硬币存储在他们的钱包中。这通过 player 的coinsInPurse属性来体现:

class Player {
var coinsInPurse: Int
init(coins: Int) {
coinsInPurse = Bank.vendCoins(coins)
}
func winCoins(coins: Int) {
coinsInPurse += Bank.vendCoins(coins)
}
deinit {
Bank.receiveCoins(coinsInPurse)
}
}
每个Player实例都由一个指定数目硬币组成的启动额度初始化,这些硬币在 bank 初始化的过程中得到。如果没有足够的硬币可用,Player实例可能收到比指定数目少的硬币。

Player类定义了一个winCoins方法,该方法从银行获取一定数量的硬币,并把它们添加到玩家的钱包。Player类还实现了一个析构函数,这个析构函数在Player实例释放前一步被调用。这里析构函数只是将玩家的所有硬币都返回给银行:

var playerOne: Player? = Player(coins: 100)
println(“A new player has joined the game with (playerOne!.coinsInPurse) coins”)
// 输出 “A new player has joined the game with 100     coins”
println(“There are now (Bank.coinsInBank) coins left     in the bank”)
// 输出 “There are now 9900 coins left in the bank”
一个新的Player实例随着一个 100 个硬币(如果有)的请求而被创建。这个Player实例存储在一个名为playerOne的可选Player变量中。这里使用一个可选变量,是因为玩家可以随时离开游戏。设置为可选使得你可以跟踪当前是否有玩家在游戏中。

因为playerOne是可选的,所以由一个感叹号(!)来修饰,每当其winCoins方法被调用时,coinsInPurse属性被访问并打印出它的默认硬币数目。

playerOne!.winCoins(2_000)
println(“PlayerOne won 2000 coins & now has \    (playerOne!.coinsInPurse) coins”)
// 输出 “PlayerOne won 2000 coins & now has 2100 coins”
println(“The bank now only has (Bank.coinsInBank) coins left”)
// 输出 “The bank now only has 7900 coins left”
这里,player 已经赢得了 2,000 硬币。player 的钱包现在有 2,100 硬币,bank 只剩余 7,900 硬币。

playerOne = nil
println(“PlayerOne has left the game”)
// 输出 “PlayerOne has left the game”
println(“The bank now has (Bank.coinsInBank) coins”)
// 输出 “The bank now has 10000 coins”
玩家现在已经离开了游戏。这表明是要将可选的playerOne变量设置为nil,意思是“没有Player实例”。当这种情况发生的时候,playerOne变量对Player实例的引用被破坏了。没有其它属性或者变量引用Player实例,因此为了清空它占用的内存从而释放它。在这发生前一步,其析构函数被自动调用,其硬币被返回到银行。

本文部分原文来自于http://www.swiftguide.cn/翻译小组的译文,共同校对中。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/deinitialization,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(14)初始化

转载自letsswift.com

初始化是类,结构体和枚举类型实例化的准备阶段。这个阶段设置这个实例存储的属性的初始化数值和做一些使用实例之前的准备以及必须要做的其他一些设置工作。

通过定义构造器(initializers)实现这个实例化过程,也就是创建一个新的具体实例的特殊方法。和Objective-C不一样的是,Swift的构造器没有返回值。它们主要充当的角色是确保这个实例在使用之前能正确的初始化。

类实例也能实现一个析构器(deinitializer),在类实例销毁之前做一些清理工作。更多的关于析构器(deinitializer)的内容可以参考Deinitialization。

1、存储属性的初始化
类和结构体必须在它们被创建时把它们所有的属性设置为合理的值。存储属性不能为不确定状态

你可以在构造方法里面给一个属性设置一个初始值,或者在定义的时候给属性设置一个默认值,这个行为将会在接下来的章节描述。
注意:当你对给一个属性分配一个默认值的时候,它会调用它相对应的初始化方法,这个值是对属性直接设置的,不会通知它对应的观察者

构造器
构造器是创建一个具体类型实例的方法。最简单的构造器就是一个没有任何参数实例方法,写作init。

在下面的例子定义了一个叫Fahrenheit(华氏度)的新结构体,来储存转换成华氏度的温度。Fahrenheit结构体,有一个属性,叫temperature(温度),它的类型为Double(双精度浮点数):

123456789
structFahrenheit{
    var temperature:Double
    init(){
        temperature =32.0
    }}var f =Fahrenheit()
println("The default temperature is \(f.temperature)° Fahrenheit")// prints "The default temperature is 32.0° Fahrenheit"

这个结构体定义了一个单一的构造方法init,它没有任何参数,它储存的温度属性初始化为32.0度。(水在华氏度的温度情况下的冰点)。
属性的默认值
如上所述,你可以在构造器中设置它自己的储存属性的初始化值。或者在属性声明时,指定属性的默认值,你指定一个默认的属性值,会被分配到它定义的初始值。
注意:如果一个属性常常使用同样的初始化值 ,提供一个默认值会比在初始化使用一个默认值会更好。
同样的结果,但是默认值与属性的初始化在它定义地时候就紧紧地捆绑在一起。很简单地就能构造器更简洁,和可以让你从默认值中推断出这个属性的类型。默认值也能让你优化默认构造器和继承构造器变得更容易,在本章会稍候描述。

你可以在上面的Fahrenheit(华氏度)结构体定义时,给temperature(温度)属性提供默认值。

123
structFahrenheit{
    var temperature =32.0}

2、自定义初始化(Customizing Initialization)
你可以根据输入的参数来自定义初始化过程和可选的属性类型,或者在初始化的时候修改静态属性。在这章节将会详细叙述。

初始化参数
你可以在构造器定义的时候提供一部分参数,在自定义初始化过程中定义变量的类型和名称。
初始化参和函数或者方法参数一样有着同样的功能。

在下面的例子中,定义了一个结构体Celsius。储存了转换成摄氏度的温度,Celsius结构体实现了从不同的温度初始化结构体的两个方法,init(fromFahrenheit:) 和init(fromKelvin:)。

12345678910111213
structCelsius{
    var temperatureInCelsius:Double=0.0
    init(fromFahrenheit fahrenheit:Double){
        temperatureInCelsius =(fahrenheit -32.0)/1.8
    }
    init(fromKelvin kelvin:Double){
        temperatureInCelsius = kelvin -273.15
    }}
let boilingPointOfWater =Celsius(fromFahrenheit:212.0)// boilingPointOfWater.temperatureInCelsius is 100.0
let freezingPointOfWater =Celsius(fromKelvin:273.15)// freezingPointOfWater.temperatureInCelsius is 0.0

第一个构造器只有一个初始化参数,形参(External Parameter Names)fromFahrenheit,和实参(Local Parameter Names)fahrenheit。第二个构造器有一个单一的初始化参数,形参(External Parameter Names)fromKenvin,和实参(Local Parameter Names)kelvin。两个构造器都把单一的参数转换为摄氏度和储存到一个temperatureInCelsius的属性.

实参名(Local Parameter Names)和形参名(External Parameter Names)
和函数参数和方法参数一样,初始化参数拥有在构造器函数体使用的实参,和在调用时使用的形参.
然而,和函数或者方法不同,构造器在圆括号前面没有一个识别函数名称。因此,构造器参数的名称和类型,在被调用的时候,很大程度上扮演一个被识别的重要角色。为此,在构造器中,当你没有提供形参名时,Swift就会为每一个参数提供一个自动的形参名。这个形参名和实参名相同,就像和之前你写的每一个初始化参数的hash符号一样。
注意:如果你在构造器中没有定义形参,提供一个下横线(_)作为区分形参和上面说描述的重写默认行为。

在下面的例子 ,定义了一个结构体Color,拥有三个静态属性red,green和blue。这些属性储存了从0.0到1.0的值,这些值代表红色 ,绿色和蓝色的深度。
Color提供了一个构造器,以及三个双精度(Double)类型的参数:

12345678
structColor{
    let red =0.0, green =0.0, blue =0.0
    init(red:Double, green:Double, blue:Double){
        self.red   = red
        self.green = green
        self.blue  = blue
    }}

无论什么时候,你创建一个Color实例,你必须使用每一个颜色的形参来调用构造器:

1
let magenta =Color(red:1.0, green:0.0, blue:1.0)

值得注意的是,不能不通过形参名来调用构造器。在构造器定义之后,形参名必须一致使用。如果漏掉就会在编写时提示错误。

12
let veryGreen =Color(0.0,1.0,0.0)// this reports a compile-time error - external names are required

可选类型
如果你储存属性使用的是自定义的类型在逻辑上允许值为空-或者他们的值并不在构造器中初始化,或者他们被允许为空。可以定义一个可选类型的属性。可选类型属性是一个自动初始化值为nil,表示这个属性有意在构造器中设置为“空值”(no value yet)。
在下面的例子中,定义了一个SurveryQuestion类,拥有一个可选的String属性response。

这个回答在他们调查问题在发布之前是无法知道的,所以response定义为类型String? ,或者叫可选String(optional String)。说明它会被自动分配一个默认值nil,意思为当surverQuestion初始化时还不存在。

在初始化时修改静态属性
当你在设置静态属性值时,只要在初始化完成之前,你都可以在初始化时随时修改静态属性。
注意:对于类的实例化,一个静态属性只能在初始化时被修改,这个初始化在类定义时已经确定。

你可以重写SurveryQuestion例子,对于问题的text属性,使用静态属性会比动态属性要好,因为SurveyQuestion实例被创建之后就无法修改。尽管text属性现在是静态的,但是仍然可以在构造器中被设置:

1234567891011121314
classSurveyQuestion{
    let text:String
    var response:String?
    init(text:String){
        self.text = text
    }
    func ask(){
        println(text)
    }}
let beetsQuestion =SurveyQuestion(text:"How about beets?")
beetsQuestion.ask()// prints "How about beets?"
beetsQuestion.response ="I also like beets. (But not with cheese.)"

3、默认构造器

Swift为每一个结构或者基类提供了默认的构造器,来初始化它们所包含的所有属性。默认构造器将会创建一个新的实例然后将它们的属性设置为默认值。

下面的例子定义了一个叫ShoppingListItem的类,包含了名称,数量和是否已购买的属性,将会被用在购物清单中:

123456
classShoppingListItem{
    var name:String?
    var quantity =1
    var purchased =false}var item =ShoppingListItem()

因为ShoppingListItem类中所有的属性都有默认值,并且这个类是一个没有父类的基类,所以它默认拥有一个会将所有包含的属性设置为初始值的默认构造器。比如在这个例子中name属性是一个可选String属性,它会被默认设置为nil,尽管在代码中没有指明。上面的例子使用默认构造器创建了一个ShoppingListItem类,记做ShoppingListItem(),然后将它赋值给了变量item。

结构类型的成员逐一构造器

除了上面提到的默认构造器之外,结构类型还有另外一种成员逐一完成初始化的构造器,可以在定义结构的时候直接指定每个属性的初始值。

成员逐一构造器是一种为结构的成员属性进行初始化的简便方法。下面的例子定义了一个叫Size的结构,和两个属性分别叫width和height。每个属性都是Double类型的并且被初始化为0.0。

因为每个存储属性都有默认值,在Size结构创建一个实例的时候就可以自动调用这个成员逐一构造器init(width:height:):

1234
structSize{
    var width =0.0, height =0.0}
let twoByTwo =Size(width:2.0, height:2.0)

4、数值类型的构造器代理

在实例的初始化过程中,构造器可以调用其他的构造器来完成初始化。这个过程叫构造器代理,可以避免多个构造器的重复代码。

对于数值类型和类来说,构造器代理的工作形式是不一样的。数值类型(结构和枚举)不支持继承,因此他们的构造器代理相对简单,因为它们只能使用自己的构造器代理。但是一个类可以继承自另外一个类,所以类需要确保在初始化的时候将它所有的存储属性都设置为正确的值。这种过程在下一节类的继承和初始化中叙述。

对于数值类型来说,可以使用self.init来调用其他构造器,注意只能在这个数值类型内部调用相应的构造器。

需要注意的是如果你为数值类型定义了一个构造器,你就不能再使用默认构造器了。这种特性可以避免当你提供了一个特别复杂的构造器的时候,另外一个人误使用了默认构造器而出错。

注意:如果你想要同时使用默认构造器和你自己设置的构造器,不要将这两种构造器写在一起,而是使用扩展形式。更多内容可以参考Extensions一章。

下面的示例定义了一个结构Rect来表示一个几何中的矩形。这个Rect结构需要另外两个结构来组成,包括Size和Point,初始值均为0.0:

123456
structSize{
    var width =0.0, height =0.0}structPoint{
    var x =0.0, y =0.0}

现在你有三种初始化Rect结构的方式:直接使用为origin和size属性初始化的0值,给定一个指定的origin和size,或者使用中心点和大小来初始化。下面的例子包含了这三种初始化方式:

1234567891011121314
structRect{
    var origin =Point()
    var size =Size()
    init(){}
    init(origin:Point, size:Size){
        self.origin = origin
        self.size = size
    }
    init(center:Point, size:Size){
        let originX = center.x -(size.width /2)
        let originY = center.y -(size.height /2)
        self.init(origin:Point(x: originX, y: originY), size: size)
    }}

init()构造器和默认构造器的功能相同。这个构造器不需要任何内容,只是用来在已有其他构造器的时候表示默认构造器的依然存在。调用这个构造器创建的Rect,根据Point和Size的结构定义,Point(x: 0.0, y: 0.0) ,Size(width: 0.0, height: 0.0) origin和size都会被设置为0。

12
let basicRect =Rect()// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

第二个Rect构造器init(origin:size:)和成员逐一构造器类似,它使用给定的值来初始化结构的属性:

123
let originRect =Rect(origin:Point(x:2.0, y:2.0),
    size:Size(width:5.0, height:5.0))// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

第三个构造器init(center:size)就更加复杂一些,它首先使用center和size计算出了origin的值,然后调用(或者是使用代理)了init(origin:size)构造器,设置origin和size的值:

123
let centerRect =Rect(center:Point(x:4.0, y:4.0),
    size:Size(width:3.0, height:3.0))// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

init(center:size:)构造器同样可以设置oringin和size的值,而且使用起来也非常方便,代码也比较简洁因为它使用了已有的一些构造器。

注意:可以参考Extensions一章,学习怎样省略init()和init(origin:size:)

5、类的继承和初始化

译者注:本小节内容Apple从底层解释,十分复杂,建议有需要的读者自行阅读英文原文。

本小节主要的意思就是说:

1、自定义初始化方法要先调用自己类默认初始化方法,自己重写默认初始化方法要先调用父类默认初始化方法

2、应该要先调用父类的构造器或者自身的默认构造器,以防止先给属性赋值了然后才调用父类或者自身的默认构造器把以前的赋值覆盖了

一个类的所有存储属性-包括从父类继承而来的属性-都必须在初始化的时候设置初始值。

Swift为class类型定义了两种构造器来确保它们所有的存储属性都设置了初始值。这两种方式叫做指定构造器和便捷构造器。

指定构造器和便捷构造器

指定构造器是一个类最主要的构造器。指定构造器通过设置所有属性的初值并且调用所有的父类构造器来根据构造链一次初始化所有的属性。

类所拥有的指定构造器很少,一般只有一个,并且是连接这父类的构造链依次完成构造的。

每个类至少有一个指定构造器,在有些情况下,需要使用继承来从父类中得到该指定构造器,更多内容可以查看后面的Automatic Initializer Inheritance章节。

便捷构造器是类的第二种常用构造器。你可以调用同一个类中的指定构造器来定义一个便捷构造器,使用指定构造器来设置相关的参数默认值。你还可以定义一个便捷构造器来创建这个类的实例或者是别的特殊用途。

如果你的类不需要它们,也可以不定义便捷构造器。不过对于常见初始化模型需要快捷方式的时候创建一个便捷构造器可以让你的初始化过程变成十分简单便捷。

构造链

为了简化指定构造器和便捷构造器的关系,Swift为两种构造器的代理调用设置了三个规则:

规则1

指定构造器必须调用它直接父类的指定构造器

规则2

便捷构造器只能调用同一个类中的其它构造器

规则3

便捷构造器必须以调用一个指定构造器结束

记下这些规则的简单方法是:

指定构造器必须向上代理

便捷构造器必须横向代理

可以使用下面的图来表示:

image

父类中的两个便捷构造器依次调用直到指定构造器,子类中的指定构造器调用了父类的指定构造器。

注意:这些规则不会影响每个类的实例创建过程。每个构造器都可以用来创建它们各自的类的实例。这些规则只影响你如何编写类实现代码。

下图演示的是另一种更为复杂的具有四个等级的类。这个图展示了指定构造器在类的初始化过程中如何被作为“漏斗”节点的。这个构造链简化了类与类之间的交互关系:

image

两阶段的初始化

在Swift中,类的初始化要经过两个阶段。在第一个阶段,每一个存储属性都被设置了一个初始值。一旦每个存储属性的值在初始化阶段被设置了,在第二个阶段,每个类在这个实例被使用之前都会有机会来设置它们相应的存储属性。

两阶段的模式使初始化过程更加安全,还可以让每个类在类的层级关系中具有更多的可能性。两阶段初始化方法可以防止属性在被初始化之前就被使用,或者是被另一个构造器错误地赋值。

注意:Swift的这种两阶段初始化方法跟Objective-C中的类似。主要的差别是在第一个过程中,Objective-C为每个属性赋值0或者null,而在Swift中,可以个性化设置这些初始值,还可以处理一些初始值不能是0或者nil的情况。

Swift编译器通过四重检查来确保两阶段式的初始化过程是完全正确无误的:

Safety check 1
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.

As mentioned above, the memory for an object is only considered fully initialized once the initial state of all of its stored properties is known. In order for this rule to be satisfied, a designated initializer must make sure that all its own properties are initialized before it hands off up the chain.

Safety check 2
A designated initializer must delegate up to a superclass initializer before assigning a value to an inherited property. If it doesn’t, the new value the designated initializer assigns will be overwritten by the superclass as part of its own initialization.
Safety check 3
A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class). If it doesn’t, the new value the convenience initializer assigns will be overwritten by its own class’s designated initializer.
Safety check 4
An initializer cannot call any instance methods, read the values of any instance properties, or refer to selfas a value until after the first phase of initialization is complete.

The class instance is not fully valid until the first phase ends. Properties can only be accessed, and methods can only be called, once the class instance is known to be valid at the end of the first phase.

Here’s how two-phase initialization plays out, based on the four safety checks above:

Phase 1

  • A designated or convenience initializer is called on a class.
  • Memory for a new instance of that class is allocated. The memory is not yet initialized.
  • A designated initializer for that class confirms that all stored properties introduced by that class have a value. The memory for these stored properties is now initialized.
  • The designated initializer hands off to a superclass initializer to perform the same task for its own stored properties.
  • This continues up the class inheritance chain until the top of the chain is reached.
  • Once the top of the chain is reached, and the final class in the chain has ensured that all of its stored properties have a value, the instance’s memory is considered to be fully initialized, and phase 1 is complete.

Phase 2

  • Working back down from the top of the chain, each designated initializer in the chain has the option to customize the instance further. Initializers are now able to access self and can modify its properties, call its instance methods, and so on.
  • Finally, any convenience initializers in the chain have the option to customize the instance and to work with self.

Here’s how phase 1 looks for an initialization call for a hypothetical subclass and superclass:

image

In this example, initialization begins with a call to a convenience initializer on the subclass. This convenience initializer cannot yet modify any properties. It delegates across to a designated initializer from the same class.

The designated initializer makes sure that all of the subclass’s properties have a value, as per safety check 1. It then calls a designated initializer on its superclass to continue the initialization up the chain.

The superclass’s designated initializer makes sure that all of the superclass properties have a value. There are no further superclasses to initialize, and so no further delegation is needed.

As soon as all properties of the superclass have an initial value, its memory is considered fully initialized, and Phase 1 is complete.

Here’s how phase 2 looks for the same initialization call:

image

The superclass’s designated initializer now has an opportunity to customize the instance further (although it does not have to).

Once the superclass’s designated initializer is finished, the subclass’s designated initializer can perform additional customization (although again, it does not have to).

Finally, once the subclass’s designated initializer is finished, the convenience initializer that was originally called can perform additional customization.

构造器的继承和重写

Unlike subclasses in Objective-C, Swift subclasses do not not inherit their superclass initializers by default. Swift’s approach prevents a situation in which a simple initializer from a superclass is automatically inherited by a more specialized subclass and is used to create a new instance of the subclass that is not fully or correctly initialized.

If you want your custom subclass to present one or more of the same initializers as its superclass—perhaps to perform some customization during initialization—you can provide an overriding implementation of the same initializer within your custom subclass.

If the initializer you are overriding is a designated initializer, you can override its implementation in your subclass and call the superclass version of the initializer from within your overriding version.

If the initializer you are overriding is a convenience initializer, your override must call another designated initializer from its own subclass, as per the rules described above in Initializer Chaining.

NOTE

Unlike methods, properties, and subscripts, you do not need to write the override keyword when overriding an initializer.

构造器自动继承

As mentioned above, subclasses do not not inherit their superclass initializers by default. However, superclass initializers are automatically inherited if certain conditions are met. In practice, this means that you do not need to write initializer overrides in many common scenarios, and can inherit your superclass initializers with minimal effort whenever it is safe to do so.

Assuming that you provide default values for any new properties you introduce in a subclass, the following two rules apply:

Rule 1
If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.

These rules apply even if your subclass adds further convenience initializers.

NOTE

A subclass can implement a superclass designated initializer as a subclass convenience initializer as part of satisfying rule 2.

指定初始化和便捷初始化的语法

Designated initializers for classes are written in the same way as simple initializers for value types:

123
init(parameters){
    statements
}

Convenience initializers are written in the same style, but with the convenience keyword placed before theinitkeyword, separated by a space:

123
convenience init(parameters){
    statements
}

指定初始化和便捷初始化实战

下面的例子演示的是指定构造器,便捷构造器和自动构造器继承的实战。例子中定义了三个类分别叫Food,RecipeIngredient和ShoppingListItem,并给出了他们的继承关系。

基类叫做Food,是一个简单的类只有一个name属性:

123456789
classFood{
    var name:String
    init(name:String){
        self.name = name
    }
    convenience init(){
        self.init(name:"[Unnamed]")
    }}

下图就是Food类的构造链:

image

类不存在成员逐一构造器,所以Food类提供了一个指定构造器,使用参数name来完成初始化:

12
let namedMeat =Food(name:"Bacon")// namedMeat's name is "Bacon"

init(name:String)构造器就是Food类中的指定构造器,因为它保证了每一个Food实例的属性都被初始化了。由于它没有父类,所以不需要调用super.init()构造器。

Food类也提供了便捷构造器init(),这个构造器没有参数,仅仅只是将name设置为了[Unnamed]:

12
let mysteryMeat =Food()// mysteryMeat's name is "[Unnamed]"

下一个类是Food的子类,叫做RecipeIngredient。这个类描述的是做饭时候的配料,包括一个数量属性Int类型,然后定义了两个构造器:

12345678910
classRecipeIngredient:Food{
    var quantity:Int
    init(name:String, quantity:Int){
        self.quantity = quantity
        super.init(name: name)
    }
    convenience init(name:String){
        self.init(name: name, quantity:1)
    }}

下图表示这两个类的构造链:

image

RecipeIngredient类有它自己的指定构造器init(name: String, quantity:Int),用来创建一个新的RecipeIngredient实例。在这个指定构造器中它调用了父类的指定构造器init(name:String)。

然后它还有一个便捷构造器,init(name),它使用了同一个类中的指定构造器。当然它还包括一个继承来的默认构造器init(),这个构造器将使用RecipeIngredient中的init(name: String)构造器。

RecipeIngredient also defines a convenience initializer, init(name: String), which is used to create aRecipeIngredient instance by name alone. This convenience initializer assumes a quantity of 1 for anyRecipeIngredient instance that is created without an explicit quantity. The definition of this convenience initializer makes RecipeIngredient instances quicker and more convenient to create, and avoids code duplication when creating several single-quantity RecipeIngredient instances. This convenience initializer simply delegates across to the class’s designated initializer.

Note that the init(name: String) convenience initializer provided by RecipeIngredient takes the same parameters as the init(name: String) designated initializer from Food. Even though RecipeIngredient provides this initializer as a convenience initializer, RecipeIngredient has nonetheless provided an implementation of all of its superclass’s designated initializers. Therefore, RecipeIngredient automatically inherits all of its superclass’s convenience initializers too.

In this example, the superclass for RecipeIngredient is Food, which has a single convenience initializer calledinit(). This initializer is therefore inherited by RecipeIngredient. The inherited version of init() functions in exactly the same way as the Food version, except that it delegates to the RecipeIngredient version ofinit(name: String) rather than the Food version.

上述三种构造器都可以用来创建RecipeIngredient实例:

123
let oneMysteryItem =RecipeIngredient()
let oneBacon =RecipeIngredient(name:"Bacon")
let sixEggs =RecipeIngredient(name:"Eggs", quantity:6)

最后一个类是ShoppingListItem继承自RecipeIngredient,它又包括了另外两个属性,是否已购买purchased,描述description,描述本身还是一个计算属性:

12345678
classShoppingListItem:RecipeIngredient{
    var purchased =false
    var description:String{
    var output ="\(quantity) x \(name.lowercaseString)"
        output += purchased ?" yes":" no"
        return output
    }}

注意:ShoppingListItem没有定义构造器来初始化purchased的值,因为每个商品在买之前purchased都是默认被设置为没有被购买的。

因为ShoppingListItem没有提供其他构造器,那么它就完全继承了父类的构造器,用下图可以说明:

image

你可以在创建ShoppingListItem实例时使用所有的继承构造器:

12345678910111213
var breakfastList =[
    ShoppingListItem(),
    ShoppingListItem(name:"Bacon"),
    ShoppingListItem(name:"Eggs", quantity:6),]
breakfastList[0].name ="Orange juice"
breakfastList[0].purchased =truefor item in breakfastList {
    println(item.description)}// 1 x orange juice yes// 1 x bacon no// 6 x eggs no

通过输出可以看出所有的实例在创建的时候,属性的默认值都被正确的初始化了。

6、通过闭包或者函数来设置一个默认属性值

如果存储属性的默认值需要额外的特殊设置,可以使用闭包或者函数来完成。

闭包或者函数会创建一个临时变量来作为返回值为这个属性赋值。下面是如果使用闭包赋值的一个示意代码:

1234567
classSomeClass{
    let someProperty:SomeType={
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
        }()}

需要注意的是在闭包结尾有两个小括号,告诉Swift这个闭包是需要立即执行的。

注意:如果你时候闭包来初始化一个属性,在闭包执行的时候,后续的一些属性还没有被初始化。在闭包中不要访问任何后面的属性,一面发生错误,也不能使用self属性,或者其它实例方法。

下面的例子是一个叫Checkerboard的结构,是由游戏Checkers来的

image这个游戏是在一个10×10的黑白相间的格子上进行的。来表示这个游戏盘,使用了一个叫Checkerboard的结构,其中一个属性叫boardColors,是一个100个Bool类型的数组。true表示这个格子是黑色,false表示是白色。那么在初始化的时候可以通过下面的代码来初始化:

1234567891011121314151617
structCheckerboard{
    let boardColors:Bool[]={
        var temporaryBoard =Bool[]()
        var isBlack =false
        for i in1...10{
            for j in1...10{
                temporaryBoard.append(isBlack)
                isBlack =!isBlack
            }
            isBlack =!isBlack
        }
        return temporaryBoard
        }()
    func squareIsBlackAtRow(row:Int, column:Int)->Bool{
        return boardColors[(row *10)+ column]
    }}

当一个新的Checkerboard实例创建的时候,闭包会执行,然后boardColor的默认值将会被依次计算并且返回,然后作为结构的一个属性。通过使用squareIsBlackAtRow工具函数可以检测是否被正确设置:

12345
let board =Checkerboard()
println(board.squareIsBlackAtRow(0, column:1))// prints "true"
println(board.squareIsBlackAtRow(9, column:9))// prints "false"

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/initialization,商业转载请联系我们~ 感谢您对我们工作的支持~

Swift中文教程(13)继承

转载自letsswift.com

一个类可以从另外一个类中继承方法,属性或者其它的一些特性。当一个类继承于另外一个类时,这个继承的类叫子类,被继承的类叫父类。继承是Swift中类区别于其它类型的一个基本特征。

Swift中的类可以调用父类的方法,使用父类的属性和下标,还可以根据需要使用重写方法或者属性来重新定义和修改他们的一些特性。Swift可以帮助你检查重写的方法和父类的方法定义是相符的。

类还可以为它继承的属性添加观察者,这样可以能够让它在一个属性变化的时候得到通知。属性观察者可以被添加给任何属性,不管它之前是存储属性还是计算属性。

1、定义一个基类

任何一个不继承于其它类的类被称作基类

注意:Swift的类不是从一个全局基类继承而来。在你编写代码的时,只要是在类的定义中没有继承自父类的类都是基类。

下面的例子定义了一个叫Vehicle的基类。基类包含两个所有交通工具通用的属性numberOfWheels和maxPassengers。这两个属性被一个叫description的方法使用,通过返回一个String描述来作为这个交通工具的特征:

1234567891011
classVehicle{
    var numberOfWheels:Int
    var maxPassengers:Int
    func description()->String{
        return"\(numberOfWheels) wheels; up to \(maxPassengers) passengers"
    }
    init(){
        numberOfWheels =0
        maxPassengers =1
    }}

这个交通工具类Vehicle还定义了一个构造函数来设置它的属性。构造函数更多的解释在Initialization一章,但是为了说明子类如何修改继承的属性,这里需要简要解释一下什么叫构造函数。

通过构造函数可以创建一个类型的实例。尽管构造函数不是方法,但是它们在编码的时候使用了非常相似的语法。构造函数通过确保所有实例的属性都是有效的来创建一个新的实例。

构造函数最简单的形式是使用init关键词的一个类似方法的函数,并且没有任何参数:

123
init(){
    // perform some initialization here}

使用构造函数语法TypeName和空的两个小括号来完成一个Vehicle实例的创建:

1
let someVehicle =Vehicle()

Vehicle的构造函数为属性设置了一些初始值(numberOfWheels = 0 然后 maxPassengers = 1)。

Vehicle类定义的是一个通用的交通工具特性,它本身没有太多意义,所以就需要冲定义它的一些属性或者方法来让它具有实际的意义。

2、产生子类

产生子类就是根据一个已有的类产生新类的过程。子类继承了父类的一些可以修改的特性。还可以为子类添加一些新的特性。

为了表明一个类是继承自一个父类,需要将父类的名称写在子类的后面,并且用冒号分隔:

123
classSomeClass:SomeSuperclass{
    // class definition goes here}

下面的例子定义了一种特定叫Bicycle的交通工具。这个新类是基于已有的类Vehicle产生的。书写方式是在类名Bicycle后加冒号加父类Vehicle名。

可以理解为:

定义一个新的类叫Bicycle,它继承了Vehicle的特性:

123456
classBicycle:Vehicle{
    init(){
        super.init()
        numberOfWheels =2
    }}

Bicycle是Vehicle的子类,Vehicle是Bicycle的父类。Bicycle类继承了Vehicle所有的特征,比如maxPassengers和numberOfWheels属性。你还可以为Bicycle类添加心的属性。

Bicycle类也定义了构造函数,在这个构造函数中调用了父类的构造函数super.init(),这样可以确保在Bicycle修改他们之前,父类已经初始化了。

注意:跟Objective-C不同的是,Swift中的构造函数没有默认继承。更多信息可以参考Initializer Inheritance and Overriding这一章节。

maxPassengers属性在继承自父类的时候已经被初始化了,对于Bicycle来说是正确的,因此不需要再做更改。然后numberOfWheels是不对的,所以被替换成了2.

不仅属性是继承于Vehicle的,Bicycle还继承了父类的方法。如果你创建一个实例,然后调用了已经继承的description方法,可以得到该交通工具的描述并且看到它的属性已经被修改:

123
let bicycle =Bicycle()
println("Bicycle: \(bicycle.description())")// Bicycle: 2 wheels; up to 1 passengers

子类本身也可以作为父类被再次继承:

123456
classTandem:Bicycle{
    init(){
        super.init()
        maxPassengers =2
    }}

上面的例子创建了Bicycle的子类,叫做tandem,也就可以两个人一起骑的自行车。所以Tandem没有修改numberOfWheels属性,只是更新了maxPassengers属性。

注意:子类只能够在构造的时候修改变量的属性,不能修改常量的属性。

创建一个Tandem的实例,然后调用description方法检查属性是否被正确修改:

123
let tandem =Tandem()
println("Tandem: \(tandem.description())")// Tandem: 2 wheels; up to 2 passengers

注意到description方法也被Tandem继承了。

3、重写方法

子类可以提供由父类继承来的实例方法,类方法,实例属性或者下标的个性化实现。这个特性被称为重写。

重写一个由继承而来的方法需要在方法定义前标注override关键词。通过这样的操作可以确保你所要修改的这个方法确实是继承而来的,而不会出现重写错误。错误的重写会造成一些不可预知的错误,所以如果如果不标记override关键词的话,就会被在代码编译时报错。

override关键词还能够让Swift编译器检查该类的父类是否有相符的方法,以确保你的重写是可用的,正确的。

访问父类方法,属性和下标

当在重写子类继承自父类的方法,属性或者下标的时候,需要用到一部分父类已有的实现。比如你可以重定义已知的一个实现或者在继承的变量中存储一个修改的值。

适当的时候,可以通过使用super前缀来访问父类的方法,属性或者下标:

叫someMethod的重写方法可以在实现的时候通过super.someMethod()调用父类的someMethod方法。

叫someProperty的重写属性可以在重写实现getter或者setter的时候通过super.someProperty调用父类的someProperty。

叫someIndex的重写下标可以在实现下标的时候通过super[someIndex]来访问父类的下标。

复写方法

你可以在你的子类中实现定制的继承于父类的实例方法或者类方法。

下面的例子演示的就是一个叫Car的Vehicle子类,重写了继承自Vehicle的description方法。

123456789101112
classCar:Vehicle{
    var speed:Double=0.0
    init(){
        super.init()
        maxPassengers =5
        numberOfWheels =4
    }
    override func description()->String{
        returnsuper.description()+"; "
            +"traveling at \(speed) mph"
    }}

Car中定义了一个新的Double类型的存储属性speed。这个属性默认值是0.0,意思是每小时0英里。Car还有一个自定义的构造函数,设置了最大乘客数为5,轮子数量是4.

Car重写了继承的description方法,并在方法名description前标注了override关键词。

在description中并没有给出了一个全新的描述实现,还是通过super.description使用了Vehicle提供的部分描述语句,然后加上了自己定义的一些属性,如当前速度。

如果你创建一个Car的实例,然后调用description方法,会发现描述语句变成了这样:

123
let car =Car()
println("Car: \(car.description())")// Car: 4 wheels; up to 5 passengers; traveling at 0.0 mph

复写属性

你还可以提供继承自父类的实例属性或者类属性的个性化getter和setter方法,或者是添加属性观察者来实现重写的属性可以观察到继承属性的变动。

重写属性的Getters和Setters

不管在源类中继承的这个属性是存储属性还是计算属性,你都可以提供一个定制的getter或者setter方法来重写这个继承属性。子类一般不会知道这个继承的属性本来是存储属性还是计算属性,但是它知道这个属性有特定的名字和类型。在重写的时候需要指明属性的类型和名字,好让编译器可以检查你的重写是否与父类的属性相符。

你可以将一个只读的属性通过提那家getter和setter继承为可读写的,但是反之不可。

注意:如果你为一个重写属性提供了setter方法,那么也需要提供getter方法。如果你不想在getter中修改继承的属性的值,可以在getter中使用super.someProperty即可,在下面SpeedLimitedCar例子中也是这样。

下面的例子定义了一个新类SpeedLimitedCar,是Car的一个子类。这个类表示一个显示在40码一下的车辆。通过重写继承的speed属性来实现:

12345678910
classSpeedLimitedCar:Car{
    overridevar speed:Double  {
    get{
        returnsuper.speed
    }
    set{
        super.speed =min(newValue,40.0)
    }
    }}

每当你要设置speed属性的时候,setter都会检查新值是否比40大,二者中较小的值会被设置给SpeedLimitedCar。

如果你尝试为speed设置超过40的值,description的输出依然还是40:

1234
let limitedCar =SpeedLimitedCar()
limitedCar.speed =60.0
println("SpeedLimitedCar: \(limitedCar.description())")// SpeedLimitedCar: 4 wheels; up to 5 passengers; traveling at 40.0 mph

重写属性观察者

你可以使用属性重写为继承的属性添加观察者。这种做法可以让你无论这个属性之前是如何实现的,在继承的这个属性变化的时候都能得到提醒。更多相关的信息可以参考Property Observers这章。

注意:不能为继承的常量存储属性或者是只读计算属性添加观察者。这些属性值是不能被修改的,因此不适合在重写实现时添加willSet或者didSet方法。

注意:不能同时定义重写setter和重写属性观察者,如果想要观察属性值的变化,并且又为该属性给出了定制的setter,那只需要在setter中直接获得属性值的变化就行了。

下面的代码演示的是一个新类AutomaticCar,也是Car的一个子类。这个类表明一个拥有自动变速箱的汽车,可以根据现在的速度自动选择档位,并在description中输出当前档位:

1234567891011
classAutomaticCar:Car{
    var gear =1
    overridevar speed:Double{
    didSet {
        gear =Int(speed /10.0)+1
    }
    }
    override func description()->String{
        returnsuper.description()+" in gear \(gear)"
    }}

这样就可以实现,每次你设置speed的值的时候,didSet方法都会被调用,来看档位是否需要变化。gear是由speed除以10加1计算得来,所以当速度为35的时候,gear档位为4:

1234
let automatic =AutomaticCar()
automatic.speed =35.0
println("AutomaticCar: \(automatic.description())")// AutomaticCar: 4 wheels; up to 5 passengers; traveling at 35.0 mph in gear 4

4、禁止重写

你可以通过标记final关键词来禁止重写一个类的方法,属性或者下标。在定义的关键词前面标注@final属性即可。

在子类中任何尝试重写父类的final方法,属性或者下标的行为都会在编译时报错。同样在扩展中为类添加的方法,属性或者下标也可以被标记为final。

还可以在类关键词class前使用@final标记一整个类为final(@final class)。任何子类尝试继承这个父类时都会在编译时报错。

感谢翻译小组成员:李起攀(微博)、若晨(微博)、YAO、粽子、山有木兮木有枝、渺-Bessie、墨离、矮人王、CXH、Tiger大顾(微博)
个人转载请注明出处和原始链接http://letsswift.com/2014/06/inheritance,商业转载请联系我们~ 感谢您对我们工作的支持~

Using Swift with Cocoa and OC与Cocoa和OC一起使用Swift

Swift系列文章由CocoaChina翻译小组翻译自苹果的官方文档,本篇译者:@Creolophus,敬请勘误!

互用性是让Swift和Objective-C相接合的一种特性,使你能够在一种语言编写的文件中访问和使用另一种语言编写的代码。当你准备开始把Swift融入到你的开发流程中时,你应该懂得如何利用互用性来重新定义并提高你写Cocoa应用的方式。互用性很重要的一点就是当你在写Swift代码时使用Objective-C的API接口。当你导入一个Objective-C框架后,你可以使用原生的Swift语法实例化它的Class并且与之交互。

初始化(Initialization)

为了使用Swift实例化Objective-C的Class,你应该使用Swift语法调用它的一个初始化器。当Objective-C的init方法变化到Swift,它们用Swift初始化语法呈现。”init”前缀被截断当作一个关键字,用来表明该方法是初始化方法。那些以“initWith”开头的init方法,“With”也会被去除。从“init”或者“initWith”中分离出来的这部分方法名首字母变成小写,并且被当做是第一个参数的参数名。其余的每一部分方法名依次变为参数名。这些方法名都在圆括号中被调用。

举个例子,你在使用Objective-C时会这样做:

UITableView *myTableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];

在Swift中,你应该这样做:

let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)

你不需要调用alloc,Swift能替你处理。注意,当使用Swift风格的初始化函数的时候,”init”不会出现。

你可以在初始化时显式地声明对象的类型,或者也可以忽略它,Swift的类型接口能够正确判断对象的类型。

//Swift
let myTextField = UITextField(frame: CGRect(0.0, 0.0, 200.0, 40.0))

这里的UITableView和UITextField对象和你在Objective-C中使用的具有相同的功能。你可以用样的方式使用他们,包括访问属性或者调用各自的类中定义的方法。

为了统一和简易,Objective-C的工厂方法也在Swift中映射为方便的初始化方法。这种映射能够让他们使用同样简洁明了的初始化方法。例如,在Objective-C中你可能会像下面这样调用一个工厂方法:

//Objective-C
UIColor *color = [UIColor colorWithRed:0.5 green:0.0 blue:0.5 alpha:1.0];

在Swift中,你应该这样做:

//Swift
let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)

访问属性(Accessing Properties)

在Swift中访问和设置Objective-C对象的属性时,使用点语法

///Swift
myTextField.textColor = UIColor.darkGrayColor()
myTextField.text = “Hello world”
if myTextField.editing {
myTextField.editing = false
}

当getting或setting属性时,直接使用属性名称,不需要附加圆括号。注意,darkGrayColor后面附加了一对圆括号,这是因为darkGrayColor是UIColor的一个类方法,不是一个属性。 在Objective-C中,一个有返回值的无参数方法可以被作为一个隐式的访问函数(implicit getter),并且可以与访问器使用同样的方法调用。但在Swift中不再能够这样做了,只有使用Objective-C中@property语法编写的属性才能被作为属性引入。详细请参看Working with Methods。

使用方法(Working with Methods)

在Swift中调用Objective-C方法时,使用点语法。 当Objective-C方法转换到Swift时,Objective-C的selector的第一部分将会成为基本方法名并出现在圆括号的前面,而第一个参数将直接在括号中出现,并且没有参数名,而剩下的参数名与参数则一一对应地填入圆括号中。

举个例子,你在使用Objective-C时会这样做:

//Objective-C
[myTableView insertSubview:mySubview atIndex:2];

在Swift中,你应该这样做:

//Swift
myTableView.insertSubview(mySubview, atIndex: 2)

如果你调用一个无参方法,仍必须在方法名后面加上一对圆括号

//Swift
myTableView.layoutIfNeeded()

id 兼容性(id Compatibility)

Swift包含一个叫做AnyObject的协议类型,用以表示任意类型的对象,就像Objective-C中的id一样。AnyObject协议允许你编写类型安全的Swift代码,同时维持无类型对象的灵活性。因为AnyObject协议也保证了这种安全,Swift将id对象导入为AnyObject。

举个例子,跟id一样,你可以为AnyObject类型的对象分配任何其他类型的对象,你也同样可以为它重新分配其他类型的对象。

//Swift
var myObject: AnyObject = UITableViewCell()
myObject = NSDate()

你也可以在调用Objective-C方法或者访问属性时不将它转换为具体类的类型。这包括了Objcive-C中标记为@objc的方法。

//Swift
let futureDate = myObject.dateByAddingTimeInterval(10)
let timeSinceNow = myObject.timeIntervalSinceNow

然而,由于直到运行时才知道AnyObject的对象类型,所以有可能在不经意间写出不安全代码。另外,与Objective-C不同的是,如果你调用的方法或者访问的属性没有经AnyObject对象声明,运行时将会报错。比如下面的代码在运行时将会报出一个未被识别的selector error:

//Swift
myObject.characterAtIndex(5)
// crash, myObject does’t respond to that method

但是,你可以通过Swift的optinals特性来排除这个Objective-C中常见的错误。当你用AnyObject对象调用一个Objective-C方法时,这次调用将会变成一次隐式展开optional(implicitly unwrapped optional)的行为。你可以通过optional特性来决定AnyObject类型的对象是否调用该方法,同样的,你可以把这种特性应用在属性上。

举个例子,在下面的代码中,第一和第二行代码将不会被执行,因为length属性和characterAtIndex:方法不存在于NSDate对象中。myLength常量会被推测成可选的Int类型并且被赋值为nil。同样你可以使用if-let声明来有条件的展开这个方法的返回结果,从而判断对象是否能执行这个方法。就像第三行做的一样。

//Swift
let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println(“Found \(fifthCharacter) at index 5”)
}

对于Swift中的强制类型转换,从AnyObject转换为更特殊的对象类型并不会保证成功,所以它会返回一个可选值。而你需通过检查该值的类型来确认转换是否成功。

//Swift
let userDefaults = NSUserDefaults.standardUserDefaults()
let lastRefreshDate: AnyObject? = userDefaults.objectForKey(“LastRefreshDate”)
if let date = lastRefreshDate as? NSDate {
println(“\(date.timeIntervalSinceReferenceDate)”)
}

当然,如果你能确定这个对象的类型(并且确定不是nil),你可以添加as操作符强制调用。

//Swift
let myDate = lastRefreshDate as NSDate
let timeInterval = myDate.timeIntervalSinceReferenceDate

使用nil(Working with nil)

在Objective-C中,对象的引用可以是值为NULL的原始指针(同样也是Objective-C中的nil)。而在Swift中,所有的值–包括结构体与对象的引用都被保证为非空。作为替代,你将这个可以为空的值包装为optional type。当你需要宣告值为空时,你需要使用nil。你可以在Optionals中了解更多。

因为Objective-C不能保证一个对象是non-nil的,所以Swift在引入Objective-C的API的时候,确保了所有函数的返回类型与参数类型都是optional。在使用Objective-C的API之前,你应该检查并保证该值非空。在某些情况下,你可能绝对确认某些Objective-C方法或者属性永远不应该返回一个nil的对象引用。为了让对象在这种情况下更加易用,Swift使用implicitly unwrapped optionals方法引入对象,implicitly unwrapped optionals 包含optional类型的所有安全特性。此外,你可以直接访问对象的值而无需检查nil或者自己展开它。当你访问这种类型的变量时,implicitly unwrapped optional 首先检查这个对象的值是否不存在,如果值不存在,那它将会抛出一个运行时错误。因此,你通常需要检查和展开一个implicitly unwrapped optional,除非你确定值不会为空。

扩展(Extensions)

Swift的扩展和Objective-C的类别(Category)相似。扩展为原有的类、结构和枚举丰富了功能,包括在Objective-C中定义过的。你可以为系统的框架或者你自己的类型定义扩展。简单要导入合适的模块并且保证你在Objective-C中使用的类、结构或枚举拥有相同的名字。举个例子,你可以扩展UIBezierPath类通过正三角形来创建一个简单的Bézier路径,这个方法只需提供三角形的边长与起点。

//Swift
extension UIBezierPath {
convenience init(triangleSideLength: Float, origin: CGPoint) {
self.init()
let squareRoot = Float(sqrt(3))
let altitude = (squareRoot * triangleSideLength) / 2
moveToPoint(origin)
addLineToPoint(CGPoint(triangleSideLength, origin.x))
addLineToPoint(CGPoint(triangleSideLength / 2, altitude))
closePath()
}
}

你也可以使用扩展来增加属性(包括类与静态属性)。然而,这些属性必须是通过计算才能获取的,扩展不会为类,结构体以及枚举添加存储属性。下面这个例子为CGRect类增加了一个经计算过的area属性。

//Swift
extension CGRect {
var area: CGFloat {
return width * height
}
}
let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)
let area = rect.area
// area: CGFloat = 500.0

你同样可以使用扩展来为类添加协议而无需对它进行子类化。如果这个协议是在Swift中被定义的,你可以添加comformance到它的结构或枚举中,无论它们是在Objective-C或在Swift中被定义。你不能使用扩展来覆盖Objective-C类型中存在的方法与属性。

闭包(Closures)

Objective-C中的blocks会被自动导入为Swift中的闭包。例如,下面是一个Objective-C 中的block变量:

//Objective-C
void (^completionBlock)(NSData *, NSError *) = ^(NSData *data, NSError *error) {/* … */}

而它在Swift中的形式为:

//Swift
let completionBlock: (NSData, NSError) -> Void = {data, error in /* … */}

Swift的闭包与Objective-C中的blocks能够兼容,所以你可以把一个Swift闭包传递给一个把block作为参数的Objective-C方法。Swift闭包与函数具有相同的类型,所以你甚至可以传递Swift函数的名字。闭包与blocks语义上想通,但在一个地方不同:变量是可以直接改变的,但不是像block那样会拷贝变量。换句话说,Swift中变量的默认行为与Objective-C中__block变量一致。

对象比较(Object Comparison)

如果你在Swift比较两个对象,那么会有两种不同的类型的比较。第一个equality 相等(==)用于比较对象的内容,第二个identity 恒等(===)用以决定常量或者变量是否引用同一个对象实例。在Swift中比较Swift和Objective-C对象使用==和===运算符。

Swift为源自NSObject类的对象提供了默认的==实现。在该运算符的实现中,Swift调用了NSObject类的isEqual:方法。NSObject类仅实现identity (===)比较,所以你应该在源自NSObject的类中实现你自己的isEqual:方法。

由于你可以把Swift对象(包括不是源自NSObject的对象)传递给Objective-C API,所以如果你想要Objective-C API比较对象的内容时,你应该实现isEqual:方法。作为实现类相等的一部分,要确保根据Object comparison中的规则实现hash属性。更进一步说,如果你想要在字典中把类用作键,那么也要遵照Hashable协议,并实现hashValue属性。

Swift类型兼容性(Swift Type Compatibility)

当你定义了一个继承自NSObject或者其他Objective-C 类的Swift类,这些类会自动兼容Objective-C。所有的步骤都有Swift编译器自动完成,如果你从未在Objective-C代码中导入Swift 类,你也不需要担心类型适配问题。另外一种情况,如果你的Swift类并不来源自Objectve-C类,而且你希望能在Objecive-C的代码中使用它,你可以使用下面描述的@objc属性。

@objc可以让你的Swift API在Objective-C和Objective-C runtime中使用。换句话说,你可以在任何Swift方法、类、属性前添加@objc,来使得他们可以在Objective-C代码中使用。如果你的类继承自Objective-C,编译器会自动帮助你完成这一步。编译器还会在类的所有方法和属性前加@objc,如果这个类自己前面加上了@objc关键字。当你使用@IBOutlet,@IBAction,或者是@NSManaged属性时,@objc也会添加在前面。当你使用selector实现target-action设计模式时,这个属性也会非常有用,例如,NSTimer或者UIButton。

当你在Objective-C中使用Swift API,编译器基本对语句做直接的翻译。例如,Swift API func playSong(name: String)(name:String)在Objective-C会被解释为- (void)playSong:(NSString *)name。然而,有一个例外:当在Objective-C中使用Swift的初始化函数,编译器会在方法前添加”initWith”,并且将原初始化函数的第一个参数首字母大写。

例如,这个Swift初始化函数init (songName: String, artist: String将被翻译为- (instancetype)initWithSongName:(NSString*)songName artist:(NSString *)artist 。 Swift同时也提供了一个@objc关键字的变体,允许你为Objective-C中的符号指定名称。例如,如果你的Swift 类的名字包含Objecytive-C中不支持的字符,你就可以为Objective-C提供一个可供替代的名字。如果你给Swift函数提供一个Objcetiv-C名字,请使用Objective-C selector syntax。要记得为带参数的函数添加(:)

//Swift
@objc(Squirrel)
class Белка {
@objc(initWithName:)
init (имя: String) { /*…*/ }
@objc(hideNuts:inTree:)
func прячьОрехи(Int, вДереве: Дерево) { /*…*/ }
}

当你在Swift类中使用@objc(<#name#>)属性,这个类可以不需要命名空间即可在Objective-C中使用。这个属性在你迁徙Objecive-C代码到Swift时同样也非常有用。由于归档过的对象存贮了类的名字,你应该使用@objc(<#name#>)来指定和Objective-C类一样的名字,这样旧的归档可以在新Swift类中恢复。

Objective-C选择器(Selectors)

Objective-Cselector是指向一个Objective-C方法名的类型。在Swift中,Objective-C selector被Selector结构体替代。你可以通过字符串字面量创建一个选择器,比如let mySelector: Selector =”tappedButton:”。因为字符串字面量能够自动转换为选择器,所以你可以把字符串字面量直接传递给任何接受选择器的方法。

//Swift
import UIKit
class MyViewController: UIViewController {
let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))

init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) {
super.init(nibName: nibName, bundle: nibBundle)
myButton.targetForAction(“tappedButton:”, withSender: self)
}

func tappedButton(sender: UIButton!) {
println(“tapped button”)
}
}

注意:performSelector:方法和相关的调用选择器的方法没有导入到Swift中,因为它们是不安全的。

如果你的Swift类继承自Objective-C的类,那么该类的所有方法和属性都可以用作Objective-C的选择器。另外,如果你的Swift类不是继承自Objective-C,如果你想要当选择器来使用你就需要在前面添加@objc关键字,详情请看Swift Type Compatibility。

iOS 8隐藏功能曝光,支持文本编辑,预览、Siri API等

苹果自从库克接手后,做出了很多改变。如今又有开发者在iOS 8的中发现了之前没有被介绍过的新功能,用户可以更改主题的颜色以及更换字体。苹果或为iOS增加更多的个性化设置。

开发者发现iOS 8中可更改主题颜色和字体(图片来自cnbeta)

通过iOS 8的发布,我们可以看到苹果确实变得更加开放,之前在国内一直饱受诟病的输入法终于得到了解决,允许安装第三方输入法键盘。不过按照苹果以前的惯例,在iOS测试版中存在的一些新功能有着实验性质,在正式版会被取消。

如果对这个新功能感兴趣的用户,只要把iOS 8的UIUseAlternateUI设置为Enable就可以了。

另外,据一位 iOS 开发者在推特上表示,发现了iOS 8测试版中苹果正在测试独立 iTunes 广播应用的证据。此外,苹果似乎也正在测试 iOS 版文本编辑和预览应用。当然,一起被发现的还有 Siri API。这些发现与之前的传言相同,消息称苹果将在 iOS 8系统中增加文本编辑、预览和 iTunes 广播等新应用。当然,这些新功能很有可能被推迟至 iOS 8.1系统中。

我们可以在 Continuity 功能相关的.plist 文件中找到 com.apple.Preview和 com.apple.TextEdit 等代码。对于 Siri 控制其他应用,开发者发现了 UIApplicationLaunchOptionsSiriTaskKey 这样的字符串。最有趣的是,开发者还发现了 Healthbook 字符串。在 WWDC 发布会之前,有消息称苹果将在 iOS 8中预装 Healthbook 应用,这款主打健康和健身数据收集的应用将可以与传言中的 iWatch 互相协作,只是苹果只在发布会上推出了 Health(健康)应用。

Swift与Objective-C API交互(三)

Swift类型兼容性

定义一个继承自NSObject或者其他Objective-C的类,它自动与Objective-C兼容。如果你不需要将Swift对象导入Objective-C代码的话,没必要关注类型的兼容性。但是如果在Swift中定义的类不是Objective-C类的子类,在Objective-C中使用的时候,需要用@objc进行说明。

@objc使得Swift的API可以在Objective-C和它的运行时中使用。当使用@IBOutlet@IBAction或者@NSManaged等属性时,自动添加@objc属性。

@objc还可以用来指定Swift中的属性或方法在Objective-C中的名字,比如Swift支持Unicode名字,包括使用中文等Objective-C不兼容的字符。还有给Swift中定义的函数指定一个Selectorde名字。

@objc(Squirrel)
class 长沙戴维营教育 {
    @objc(hideNuts:inTree:)
    func 欢迎光临(Int, 姓名: String) {
        /* ... */
    }
}

@objc(<#name#>)属性作用在Swift的类上时,这个类在Objective-C的使用不受命名空间的限制。同样,在Swift中解归档Objective-C归档的对象时,由于归档对象中存放有类名,因此需要在Swift中用@objc<#name>说明Objective-C的类名。

Objective-C选择器(Selector)

Objective-C的选择器是方法的一个引用。在Swift中对应的是Selector结构体。使用字符串字面量可以构建一个选择器对象,如let mySelector: Selector = "tappedButton:"。由于字符串字面常量可以自动转换为选择器对象,因此可以在任何需要传递选择器的地方使用字符串字面常量。

import UIKit
class MyViewController: UIViewController {
    let myButton = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))

    init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!)
    {
        super.init(nibName: nibName, bundle: nibBundle)
        myButton.targetForAction("tappedButton:", withSender: self)
    }

    func tappedButton(sender: UIButton!) {
        println("tapped button")
    }
}
提示
performSelector:以及相关的调用选择器的方法没有被引入到Swfit中来,因为它们不是完全安全的。

如果Swift类继承自Objective-C的类,则它里面的方法和属性都能够作为Objective-C的选择器使用。而如果不是Objective-C的子类,需要使用@objc属性修饰,这个在前面的Swift类型兼容性中有描述。

Swift与Objective-C API交互(二)

兼容id类型

Swift包含一个叫AnyObject的协议,与Objective-C中的id类似,可以表示任意类型的对象。AnyObject协议允许你在利用无类型对象的灵活性的同时保持类型安全。
你可以给AnyObject类型的变量赋任意的值:

var myObject: AnyObject = NSData()

可以直接方法AnyObject类型对象的任意属性和方法,而不需要进行强制类型转换。

let dateDescription = myObject.description
let timeSinceNow = myObject.timeIntervalSinceNow

由于AnyObject类型的变量的类型需要到运行时才能确定,因此可能会导致不安全的代码。特别是你可以访问一个不存在的属性或者方法,它只是在运行时报错。

myObject.characterAtIndex(5)
//crash, myObject doesn't respond to that method

在进行类型转换的时候,不一定转换成功,因此Swift返回的是一个Optional值。你可以检查是否转换成功。

let userDefaults = NSUserDefaults.standardUserDefaults()
let lastRefreshDate: AnyObject? = userDefault.objectForKey("LastRefreshDate")
if let date = lastRefreshDate as? NSDate {
    println("\(date.timeIntervalSinceReferenceDate)")
}

如果你能够确定对象的类型,并且不为nil,可以使用as操作符进行强制转换。

let myDate = lastRefreshDate as NSDate
let timeInterval = myDate.timeIntervalSinceReferenceDate

nil对象

Objective-C中使用nil来表示引用一个空对象(null)。Swift中所有的值都不会为nil。如果需要表示一个缺失的值,可以使用Optional。

由于Objective-C不能确保所有值都非空,因此Swift将Objective-C中引入的方法的参数和返回值都用Optional表示。在使用Objective-C对象之前,应该检查它们是否存在。

扩展

Swift的扩展与Objective-C的类别有点类似。扩展能够为已有类、结构体、枚举等增加行为。

下面是给UIBezierPath添加扩展:

extension UIBezierPath {
    class func bezierPathWithTriangle(length: Float, origin: CGPoint) -> UIBezierPath {
        let squareRoot = Float(sqrt(3))
        let altitude = (squareRoot * length) / 2
        let myPath = UIBezierPath()

        myPath.moveToPoint(orgin)
        myPath.addLineToPoint(CGPoint(length, origin.x))
        myPath.addLineToPoint(CGPoint(length / 2, altitude))
        myPath.closePath()

        return myPath
    }
}

可以使用扩展增加属性(包括类属性或静态属性)。不过这些属性只能是通过计算得来,而不能进行存储。下面给CGRect添加一个area属性:

extension CGRect {
    var area: CGFloat {
        return width * height
    }
}

let rect = CGRect(x: 0.0, y: 0.0, width: 10.0, height: 50.0)
let area = rect.area
//area: CGFloat = 500.0

使用扩展可以在不创建子类的情况下让现有的类响应某个协议。需要注意的是,扩展不能覆盖已有的方法和属性。

闭包

Objective-C中的Block 被自动导入为Swift的闭包。例如:

void (^completionBlock)(NSData *, NSError *) = ^(NSData *data, NSError *error) {
    /* ... */
}

在Swift 中对应为:

let completionBlock: (NSData, NSError) -> void = {
    data, error in /* ... */
}

Swift的闭包和Objective-C中的Block是兼容的,可以在Swift中给Objective-C的方法传递闭包来代替Block对象。

闭包和Block对象有一点不同,里面的变量是可变的,也就是说与__block修饰的变量行为相同。

对象比较

Swift中有两种对象比较的方式。第一种是相等(==(equality),用来比较两个对象的内容是否相同。第二种是恒等(===(identity),比较两个变量或者常量是否引用同一个对象。

NSObject只能比较是否引用了同一个对象(恒等),如果要比较内容是否相同,应该实现isEqual:方法。

Swift与Objective-C API交互(一)

Swift和Objective-C可以进行互操作,也就是说可以在Objective-C项目中使用Swift代码,反过来也可以。当然,这种互操作之间最重要的是可以在Swift中调用Objective-C的接口,毕竟目前绝大部分接口都是通过Objective-C提供的。

初始化

在Swift中实例化一个Objective-C的类时,可以用Swift语法调用它的一个初始化器。Objective-C的初始化方法被分割成关键字。例如“initWith”变成“init”,而剩下的部分作为方法的参数名。

Objective-C的代码:

UITableView *myTableView = [[UITableView alloc] initWithFrame: CGRectZero style: UITableViewStyleGrouped];

对应的Swift代码为:

let myTableView: UITableView = UITableView(frame: CGRectZero, style: .Grouped)

在Swift中不需要调用alloc方法,它会自动处理对象的创建功能。注意:Swift不会显式的调用init方法。

定义变量或者常量的时候,可以省略它的类型,Swift会自动识别。

let myTextField = UITextField(frame: CGRect(0.0, 0.0, 200.0, 40.0))

为了方便起见,Objective-C的工厂方法被映射为Swift中的初始化器。例如:

UIColor *color = [UIColor colorWithRed: 0.5 green: 0.0 blue: 0.5 alpha: 1.0];

转换为Swift:

let color = UIColor(red: 0.5, green: 0.0, blue: 0.5, alpha: 1.0)

属性访问

在Objective-C和Swift中访问属性都是使用点操作符。

myTextField.textColor = UIColor.darkGrayColor()
myTextField.text = "Hello world"
if myTextField.editing {
    myTextField.editing = false
}

访问和设置属性的时候不需要使用圆括号,上面darkGrayColor之所以有括号,是因为调用的是UIColor的类方法,而不是属性。

Objective-C中不需要参数并且有一个返回值的方法,可以被当作隐含的获取方法(getter),从而使用点操作符。但是在Swift中点操作符只能访问Objective-C中使用@property定义的属性。

方法调用

在Swift中使用点操作符调用Objective-C中的方法。

Objective-C的方法在Swift中调用的时候,它的第一部分成为Swift的基本方法出现在括号之前。然后函数的第一个参数没有名字,剩下的部分作为Swift函数对应的参数名称。
Objective-C语法:

[myTableView insertSubview: mySubview atIndex: 2];

Swift代码:

myTableView.insertSubview(mySubview atIndex: 2)

调用无参的方法:

myTableView.layoutIfNeeded()

iOS 8/Yosemite AirDrop 视频曝光

WWDC 后,围绕着越来越开放的 OS X 和 iOS 系统上,果粉总是有聊不完的话题。演示上那幕 Mac 和 iOS 8 间的 AirDrop 帅爆了。或许它们之间的无缝沟通就成了很多用户在决定是否购买 Mac 上的一个巨型砝码。之前爱应用小编也分别刷了 iOS 8 和 Yosemite,但未实验成功,必须要在 Mac 重装成一台新机时才可能使用。不过并不是所有人都愿意为了看看这个功能而把自己的电脑清掉,今天就跟着 AppleInsider的视频来抢先看看这个超实用功能吧!

http://v.youku.com/v_show/id_XNzIzODgyNzky.html?firsttime=95

目前,咱们只能在 iOS 设备间或是 Mac 间通过 AirDrop 来传输图片等文件和文件夹。视频中,使用方法也与之前很相似。在 iOS 端和 Mac 端都打开 AirDrop,两者就能在 AirDrop 界面上互相找到,之后通过拖拽即可完成文件传输。从 iPhone 上传来的照片会被默认储存在下载文件夹中。功能这么屌,能说服自己下一台电脑换 Mac 咩?