安安IOSiOSiOS深度报告

Swift 十二讲 第六章 类 (Class) (draft)

2015-02-03  本文已影响1909人  zydscience

1.类的简单介绍

简单的说,类就是一个封装变量和函数的容器的定义。变量被称为类的属性。函数一般是操作这些变量的,被称为方法。类可以从其他的类继承而来,然后添加一些新的属性和方法。如果一个类不从任何其他的类继承而来,它被称为基类。

    class 矩形 {
var 长度 = 1 
var 宽度 = 2 
var 面积: Int 
{
return 长度*宽度
}
}  //矩形类被定义出来。

let x1 = 矩形()
x1.长度=20
println(x1.长度)  //输出20

var x2 = x1
x2.长度 = 40
println(x1.长度)  //输出变为40; 这里x2和x1其实是一个实体的不同名字而已。
//因为类只被创建了一次实例。

2.属性

*计算属性
上面例子里的“面积”是计算属性。它实际上是个函数或者方法,但是写成类似于属性的格式,方便使用。计算属性必须显式定义其类型。计算类型的完整格式如下:

var 计算类型的名字:类型
{
    get
    {
    }
    set(值的名字)
    {
    } 
} 

如上所示,get是在这个计算属性被读的时候调用的函数。set是当这个计算属性被赋值的时候调用的函数。如下例:

class 矩形 {
var 长度 = 1 
var 宽度 = 2 
var a = 1
var 面积: Int 
{
    get{return 长度*宽 }
set(a)
{长度 = a}
}
}

let x1 = 矩形()
x1.面积=20
println(x1.长度)  //输出20
println(x1.面积)  //输出40, 因为长度已经被set变为20

上面例子的执行过程是这样的。x1.面积=20 这句调用了set,所以x1.长度被变成20。println(x1.面积)调用get计算面积,得到40。

属性观测器是两个可以附加在存储属性之后的函数。一个是willSet,另一个是didSet。前者在属性被赋值之前调用。后者在属性被赋值之后调用。
这两个函数的参数就是你要赋的值,其名字你可以自己指定。如果不指定参数名,那么可以在函数体内用newValue代替。如下例:

class 动物 {
var 名字: String = ""
    {
    willSet
{
    println(newValue)
}
   
}
}

var 我的动物 = 动物()
我的动物.名字 = "小狗"
//因为名字被赋值,你可以看到playground的console有一个输出:”小狗”。
//这是因为println(newValue)被调用了。

这里有几个细节要注意。首先如果你要用willSet和didSet函数,那么必须明确指定属性的类型。第二你也必须初始化。
很明显,willSet和didSet是非常有用的功能。你可以重载IOS内建类的一些属性,添加这两个函数,来完成实例属性变化时候的一些操作。
例如一个按钮颜色变了,可以在willSet里面触发一些你想要的其他操作。

类型属性是和类绑定的计算属性。Swift目前只支持计算类型属性。不支持存储类型属性。它的写法为:

class SomeClass {

class var someComputedProperty: SomeType {

get { return SomeType } set(valueName)
{

// do something with valueName

// that sets the property }

} }

类型属性是在类上被读写,而不是在实例上。如下例:

class a {
class var A:Int
{
    get {return -1}
set
{println("xxx")}
}
}

var x1 = a()
println(a.A) //输出-1
a.A = 5     //输出xxx,因为set被调用了
println(a.A)  //输出-1

变量属性在类里面定义的时候,必须赋予初始值。常量属性可以不需要给出初始值,而是用init函数赋值。如下例:

class bx
{
let b: Int
init(c : Int)
{
    b = c
}
}

var bb = bx(c : 1) //c的值1,被传递进去init(c:Int)函数
println(bb.b) //输出为1

常量属性不能在子类里面被初始化。常量属性可以赋予初始值,然后在init()里面被改变。但是无论如何,类被实例化的时候,常量属性必须有一个值。不然就不能实例化成功。

3.方法

方法有两种。第一种是类型方法,用class func关键字定义。第二种是实例方法。

读者可能还记得。函数的参数标准定义为: (外部名 内部名 :类型)。 如果没有外部名,那么函数的参数默认是局部的。方法于此是不同的,方法的第一个参数默认是局部的。剩余的其他参数,则是默认同时是局部和外部的,如果你不明确定义外部名。第二个参数开始的其他所有参数的外部名,默认等同于内部名。

在方法的定义里,如果你想给第一个参数一个相同的外部名,那么可以用#关键字。如果你不想让第二个开始的其他参数有外部名,那么可以用_ (下划线)关键字。

这段定义非常麻烦。作者认为很大程度上,这是为了方便调用object-c的一些方法。

下面例子简单的说明了类型方法的定义和使用。

class bx
{
var ap = 1
class func method1()
    
{println("type method is defined")}
}

var a = bx()
println(a.ap) //输出为1
bx.method1() //输出为"type method is defined"

下面例子简单说明了实例方法的定义和使用:

class bx
{
var ap = 1
func method2()
    
{println(" method is defined")}
}

var a = bx()
println(a.ap) //输出1
a.method2() //输出:method is defined

值得注意的是,下表函数可以用浮点数做为参数或者自变量。这是和数组不同的地方。

如下例:

class a {
  subscript(c:Double)->Int
  {
  
      println(c)
      return 0
  }
  
 }

var b = a()
println(b[3.14]) //输出为3.14, 0。

如上面两个例子所示,下标其实就是看上去写法更像数组的函数。

中文把supercalss和subclass翻译成父类,子类实际上是很扯的事情。本文将把superclass翻译成上类,subclass翻译成下类。强调其几何关系。

我们先定义一个基类,弄清楚其意思,然后再逐步用例子说明如何重载和继承。这个基类如下:

  class a {
  var A = 1
  func B (c:Int) -> Double
  {
      return (Double(c*c/2))
  }
  subscript(c:Double)->Int
  {
  
      return (Int(c*c))
  }
  
  }

  var b = a()
  println(b[3.14])
  println(b.B(3))

这个基类很简单。但包含了属性,方法和下标这三个最常用的类的元素。

继承就是根据一个已经有了的类,定义一个新类。在写继承的时候,你只要按如下格式,表明其上类即可。然后你可以在新类里面添加自己需要的新的元素(属性方法等等)。

class 新类的名字: 上类的名字 { }

如下例:

  class aa: a{
      var x = 20
  }   //上类a的定义在前面一个例子
  
  var bb = aa() 
  println(bb[3.14]) //输出为9。调用了上类已经有的下标定义
  println(bb.x) //输出20

aa继承了a这个已经有了的类。然后什么也没做。你可以正常使用aa。

一般情况下,你继承一个已经有了的上类,自己创建了一个类,一定是为了做什么事。所以肯定会新定义一些东西,然后改变上类里已经有的一些属性方法等等。后者就是重载。

重载的写法很简单,记住下面要点,按照常识写,基本不会出错:
(1)重载的关键字是override。
(2)引用上类里已经有的属性方法和下标的话,记住要加super关键字。

如下例:

  class a {
  var A = 1
  func B (c:Int) -> Double
  {
      return (Double(c*c/2))
  }
  subscript(c:Double)->Int
  {
  
      return (Int(c*c))
  }
  
  }


  class aa: a{
      var x = 20
   override subscript(c:Double)->Int
  {
  
      return (super[c]*2)
  }  
  }
        
  var b = a()
  var bb = aa()
  println(b[2]) //输出4
  println(bb[2]) //输出8

在上面例子中,b是类a的一个实例。bb是a的下类aa的实例。a的下标函数被重载了。

你可以在一些类的属性方法等前面加上final关键字,禁止其被重载。
在class关键字之前加上final, 可以禁止其被继承。

*Swift不允许多重继承

Swift不允许多重继承。你可以用多重的,各种协议(protocol)。协议会在后面讲到。笔者认为这是个很明智的决定。多重继承带来的混乱远远超过其好处。

有时候你并不希望给类里面的存储属性指定默认初始值。而是希望在创建实例的时候,你把初始值传给它。这时候就要用到init ()函数。
Swift有两种初始化函数。第一种就是init()。第二种是在init()前面加上convenience关键字。我把第二种叫做快捷初始化。

每个类都有个self属性,指的就是被创建的实例。self最常见是在初始化名字冲突时候使用。

下面是一些例子:

  class a {

var A,B,C : Int
init()
{
A = 1;
B = 1;
C = 1;
}

  }
  
  var    b = a()
  println(b.A)

  //这个例子毫无意义,纯粹为了说明句法。A的初始化完全可以写成var A =1。省事省时间。

再看下面的例子:

class a {

var A,B,C : Int
init(A:Int,C:Int)
{
self.A = A;
B = 1;
self.C = C;
}

  }
  
 var    b = a(A:1,C:10)
  println(b.A) //输出1
  println(b.B)  //输出1
  println(b.C)  //输出10

  //这个例子,在创建实例的时候才初始化A和C属性。但是为了直观,所以init()里面被传递的参数名字也用了A和C。所以要用self来告诉编译器怎么做。B就不需要加self,自动就是指的类的属性。

有个笑话: "张三不仅仅是个人,还是个名字(也就是类,非实体)。" 可见,你到底指的是 张三的名字还是张三这个人,非得再加一点关键字不可。

名字和其所指是个很严肃的问题,大家可以思考下。

上帝是个名字,还是实体?下面是托马斯阿奎那的十二个问题:
 (1)上帝是否能够由我们命名?
  (2)任何用于上帝的名称是否都是实体性地言说他的?
  (3)任何用于上帝的名称究竟是照字义说到他呢,还是全都是在比喻地使用呢?
  (4)用于上帝的许多名称是否是同义的?
  (5)用到上帝和受造物身上的一些名称究竟是单义的,还是多义的?
  (6)如果它们是类比地应用的话,那么,它们是首先应用到上帝身上呢还是受造物身上呢?
  (7)任何名称是否是时间性地应用到上帝身上的?
  (8)“上帝”这个名称究竟是一个关于本性的名称呢,还是一个关于运作的名称呢?
  (9)“上帝”这个名称是否是一个可以传达的名称?
  (10)当藉本性、分有和意见指谓上帝时,这是在单义地还是在多义地使用“上帝”这个名称的?
  (11)“自有”这个名称是否是至上地适合于上帝的名称?
  (12)能否形成关于上帝的肯定命题?

你可以在类里面写deinit关键字,表明你在一个实例被清除前你希望做的事。语法如下:

class xxx {
...
deinit {你要做的事}
...
}

如下例:

 class a {
var A,B,C : Int
init(A:Int,C:Int)
{
self.A = A;
B = 1;
self.C = C;
}

deinit
{println("Being = Nothing")}
  }


class aa:a {}

 var    b = a(A:1,C:10)
 b = aa(A:1,C:10) 
//当b换成子类aa的时候,没有实例再实现a了。所以这时候输出"Being = Nothing"

Swift的类型下降关键字是as。还有一个判断类型的关键字是is, is的操作返回布尔值。as可以把上类指定为下类。(as加问号?会在后面optional里面讲)。

Swift不支持多重继承。所以这个类型下降,按笔者的理解,其实没多大作用。但是这个as,对IOS开发作用重大。因为种种原因,IOS里旧的object-c类库里面很多方法返回的是所谓的Anyobject,也就是任何类型的实例都可以。那么当你创建这些IOS里面的对象的时候,往往需要用as,来确保其正常工作。

如下例:

 import UIKit

let button   = UIButton.buttonWithType(UIButtonType.System)  as UIButton
button.frame = CGRectMake(100, 100, 100, 50)
button.backgroundColor = UIColor.greenColor()

//在playground下面的输出里,可以看到一个绿方块。

读者可以试着去掉"as UIButton",可以看到playground会报错,说需要给any object指定一个更下面的类。另外,你可以把as UIButton换成as UIView,这段代码也可以正常使用。

command+单击buttonWithType可以发现这个方法的定义为:

 class func buttonWithType(buttonType: UIButtonType) ->AnyObject

可见其返回值确实是AnyObject。所以必须下降为UIButton才能用其他各种button相关的属性。

上一篇下一篇

猜你喜欢

热点阅读