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 咩?

iOS 8将支持多种模式的多任务功能

在前不久的 2014 年 WWDC 上,苹果推出了自己最新的 iOS 系统 iOS 8,但传言中的分屏多任务管理功能却并没有出现。最近,有开发者发现,iOS 8 的分屏多任务管理功能是的确存在的,并且向用户提供了多种分屏模式。开发者发现 iOS 8 系统的 SpringBoard 包含了允许应用在多种尺寸下同屏运行的代码,两个应用可以在 1/4、1/2 或 3/4 的比例下运行。

iOS 8 支持分屏多任务的消息早在今年 5 月就曾出现,消息称 iPad Air 将支持两个应用同时运行,就像是微软 Surface。iOS 8 分屏多任务功能只支持在横屏模式下运行,两个同时运行的应用可以互相分享链接、图片和信息等数据。目前的消息是,多屏多任务可能只支持 iPad Air,随后也有可能支持 iPhone 和 Retina iPad mini。此外,分屏多任务功能还可能支持苹果没有发布的 12.9 英寸 iPad Pro。WWDC 举行之前,《纽约时报》就有报道称分屏多任务功能正在开发中,但不会在 WWDC 上公布。

Source:macrumors

 

数据说话4G版本iPhone约占全球LTE 42%

注意,注意,说的是硬件4G版iPhone,我们知道,并不是所有的人在4G设备上使用4G卡,所以不用想象,全球有这么多。。。很多中国移动还在4G设备上使用2G语音卡呢。

根据市场研究机构 Counterpoint Research 最新的研究报告显示,在过去的一年时间里,4G 版本 iPhone 约占全球 LTE 手机总量的 42%,而全球的 LTE 手机出货量增长了 91%,约占全球智能手机出货量的四分之一。

从图中,我们可以看出,无论是 2013 年第一季度还是 2014 年第一季度,苹果的 LTE 手机出货量均遥遥领先三星。在 2014 年第一季度有 2% 的下降,可能和迟迟未推出 iPhone 6 有一定关系。而这份数据并未考虑连接到 LTE 网络的平板设备,若是把 LTE 版本的 iPad 算上,苹果的比例还会提高。目前美国约占世界 LTE 手机的三分之一,排名第一,中国已经排名世界第三位,并有可能在今年超越排名第二的日本。

目前中国的 4G 市场,iPhone 设备接入 4G 网络的主要还是移动用户,中国移动在 2013 年底就已开展 4G 网络的试运营,过去很多忍受移动 2G 龟速网络的 iPhone 用户,趁此机会用上了移动的 4G 网络。同时中国联通和中国电信也在积极争取 FDD-LTE 的牌照,若是牌照拿到,体验到 4G 的用户还将会进一步增加。有体验到 4G 网络的 iPhone 用户吗?来说说看实际的体验速度。