2016-04-27 09:15:50 qq_25491201 阅读数 2312

swift面向对象之多态与继承

1.多态



运行时多态

运行时多态的要点

1.要有继承关系的存在

2.子类要重写父类的方法

3.父类类型的引用指向子类实例

2.继承


1.继承的注意

继承可以用于类而不能用于结构体和枚举,继承也可用于协议

swift中不能使用多继承,oc中也是


父类的计算属性可以被子类所重写,如果该计算属性在父类中是只读的,则子类可以重写该属性使其成为只读或可读可写属性,但是如果属性在父类中是可读可写属性那么在子类重写后必须是可读可写属性而不能是只读属性,因为子类属性的读写权限必须大于或等于父类


2.Super的使用

super的使用可以调用父类的方法,不过只有拥有父类的类才可以使用super

在子类的实例方法中使用super,super代表子类实例中用父类类型初始化的部分,而在子类的类型方法中调用super时,super表示父类的类型本身


3.拒绝子类重写

final这个关键字可以防止父类中的方法或属性被之类给改写,如果改写就出错


2016-06-16 13:49:10 qq_16628781 阅读数 415

swift之类的继承

知识点:

1、类的继承、重写等概念;

2、子类和父类的属性和方法关系;


继承(Inheritance)

综述:一个类可以继承(inherit)另一个类的方法(methods),属性(properties)和其它特性。当一个类继承其它类时,继承类叫子类(subclass),被继承类叫超类(或父类,superclass)。在 Swift 中,继承是区分「类」与其它类型的一个基本特征。


首先定义一个基类

下面的例子定义了一个叫Vehicle的基类。这个基类声明了一个名为currentSpeed,默认值是0.0的存储属性(属性类型推断为Double)。currentSpeed属性的值被一个String 类型的只读计算型属性description使用,用来创建车辆的描述。

Vehicle基类也定义了一个名为makeNoise的方法。这个方法实际上不为Vehicle实例做任何事,但之后将会被Vehicle的子类定制:

class Vehicle {
    var currentSpeed = 0.0
    var description: String {
        return "traveling at \(currentSpeed) miles per hour"
    }
    func makeNoise() {
        // 什么也不做
    }
}

您可以用初始化语法创建一个Vehicle的新实例,即类名后面跟一个空括号:

let someVehicle = Vehicle()

现在已经创建了一个Vehicle的新实例,你可以访问它的description属性来打印车辆的当前速度。

print("Vehicle: \(someVehicle.description)")
// Vehicle: traveling at 0.0 miles per hour

创建子类并继承父类

继承的格式:为了指明某个类的超类,将超类名写在子类名的后面,用冒号分隔:子类会自动获得父类的所有属性和方法,还可以添加属性和方法。

class SomeClass: SomeSuperclass {
    // 类的定义
}

这里定义一个叫Bicycle的子类,继承成父类Vehicle

class Bicycle: Vehicle {
    var hasBasket = false //新添加一个属性,推断为boolean类型
}

使用:

let bicycle = Bicycle()
bicycle.hasBasket = true 
bicycle.currentSpeed = 15.0 //修改父类属性值
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour

重写(Overriding)

重写方法:

下面的例子定义了Vehicle的一个新的子类,叫Train,它重写了从Vehicle类继承来的makeNoise方法:

class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

重写属性:

class Car: Vehicle {
    var gear = 1
    override var description: String {
        return super.description + " in gear \(gear)"
    }
}

重写的description属性,首先要调用super.description,然后在后面添加自己需要的信息。

class AutomaticCar: Car {
    override var currentSpeed: Double {
        didSet {//didSet在新的值被设置之后立即调用,willSet在新的值被设置之前调用
            gear = Int(currentSpeed / 10.0) + 1
        }
    }
}

运行结果:

let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4


防止重写

只要在属性和方法中,在方法和属性前面用final修饰词(例如:final var, final func, final class func, 以及 final subscript)。

如果你重写了final方法,属性或下标脚本,在编译时会报错。

你可以通过在关键字class前添加final特性(final class)来将整个类标记为 final 的,这样的类是不可被继承的,任何子类试图继承此类时,在编译时会报错。


2017-03-14 09:44:13 ChasingDreamsCoder 阅读数 426

一个类可以继承另一个类的方法,属性和其他特性。当一个雷继承其他类的时候,继承类叫子类,被继承类叫超类。在swift中,继承是区分类与其他类型的一个基本特征。

在swift中,类可以调用和访问超类的方法,属性和下标,并且可以重写这些方法,属性和下标来优化或修改他们的行为。swift会检查你的重写定义在超类中是否有匹配的定义,以此保证你的重写行为是正确的。

可以为类中继承来的属性添加属性观察器,这样一来,当属性值改变时,类就会被通知到。可以为任何属性添加属性观察器,无论它原本被定义为存储型还是计算型属性。

定义一个基类

不继承与其他类的类,称之为基类。
 class Vehicle {
        var currentSpeed = 0.0
        var description:String {
            return "\(currentSpeed)"
        }
        func makeNoise() {
            
        }
    }

子类生成

子类生成指的是在一个已有的类的基础上创建一个新的类。子类继承超类的特性,并且可以进一步完善。还可以为子类添加新的特性。
为了指明某个类的超类,将超类名写在子类的后面:
 class Bicycle: Vehicle {
        var hasBasket = false
    }
新的Bicycle类自动获取Vehicle类的所有特性,比如currentSpeed和description属性,还有它的makeNoise()方法。
除了它所继承的特性,Bicycle类还定义了一个默认值为false的存储属性hasBasket。

重写

子类可以为继承来的实例方法,类方法,实例属性,或下标提供自己定制的实现。我们把这种行为叫重写。
如果要重写某个特性,需要在重写定义的前面加上override关键字。标明了你是想提供一个重写版本,而非错误的提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少override关键字的重写都会在编译时被诊断为错误。
override关键字会提醒swift编译器去检查该类的超类是否有匹配重写版本的声明,这个检查可以确保你的重写定义时正确的。

访问超类的方法,属性及下标

当你在子类中重写超类的方法,属性或下标时,有时在你的重写版本中使用已经存在的超类实现会大有裨益。比如,可以完善已有实现的行为,或在一个继承来的变量中存储一个修改过的值。
在合适的地方,可以通过使用super前缀来访问超类版本的方法,属性或者下标:
在方法someMethod()的重写实现中,可以通过super.someMethod()来调用超类版本的someMethod()方法。
在属性someProperty的getter或setter的重写实现中,可以通过super.someProperty来访问超类版本的someProperty属性。
在下标的重写实现中,可以通过super[someIndex]来访问超类版本中的相同下标。

重写方法

在子类中,可以重写继承来的实例方法或类方法,提供一个定制或替代的方法实现。
下面例子就是重写makeNoise()方法:
  class Train: Vehicle {
        override func makeNoise() {
            print("choo choo")
        }
    }

重写属性

可以重写继承来的实例属性或类型属性,提供自己定制的getter和setter,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。
重写属性的getters和setters
可以根据定制的getter或setter来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。在重写一个属性时,必须将它的名字和类型都写出来。这样才能使编译器去检查你重写的属性与超类中同名同类型的属性相匹配的。
可以将一个继承来的只读属性重写为一个读写属性,只需要在重写版本的属性里提供getter和setter即可,但是,你不可以将一个继承来的读写属性重写为一个只读属性。
    class Car: Vehicle {
        var gear = 1
        override var description: String {
            return super.description + "in gear \(gear)"
        }
    }

重写属性观察器

可以通过重写属性为一个继承来的属性添加属性观察器,这样一来,当继承来的属性值发生改变时,就会被通知到,无论那个属性原本是如何实现的。
   class AutomaticCar: Car {
        override var currentSpeed: Double{
            didSet{
                gear = Int(currentSpeed / 10.0) + 1
            }
        }
    }
当设置currentSpeed属性,属性的didSet观察器就会自动的设置gear属性,为新的速度选择一个合适的档位。具体来说就是,属性观察器将新的速度值除以10,然后向下取整,最后加1来得到档位的值。

防止重写

可以通过把方法,属性或下标标记为final来防止它们被重写,只需要在声明关键字前加上final修饰符(例如:final var,final func ,final class func ,以及final subscript)
如果重写了带有final标记的方法,属性或者下标,在编译时会报错。在类扩展中的方法,属性或下标也可以在扩展的定义里标记为final的。
可以通过在关键字class前添加final修饰符来将整个类标记为final的,这样的类是不可被继承的,试图继承这样的类会导致编译报错。


选自《the swift programming language》中文版




















2015-10-23 09:41:00 hsf_study 阅读数 3089
  swift中,一个对象可以不继承自NSObject
- 继续自NSObject 可以使用KVC方法给属性设置数值  =》如果是模型对象,最好还是使用NSObject
- 如果对象,没有属性,或者不依赖KVC , 可以建立一个没有父类的对象!, 对象的量级比较轻,加载时,就在内存创建
   什么,内存消耗小
2014-11-25 15:56:43 Lirx_Tech 阅读数 1267

1. Swift继承的基本概况:

    1) 只有类能继承其它类型都不支持继承,包括基本类型、集合、结构体、枚举;

    2) 和Java一样只支持单继承不支持多继承,Swift的多继承是由协议实现的(和Java的接口很像,可以通过遵守多个协议的方式来间接达到多继承的目的);

    3) Swift没有试下封装性,并不支持C++和Java那样的private、public、protected等访问限定符,因此类的所有成员都可以继承(属性、方法、下标等都可以),但是构造器不能继承,因为继承就是通过调用父类构造器在子类中构造出父类部分实现的,因此构造器不能算作类的属性或者方法等范畴,应该说构造器就是一段逻辑脚本,该脚本规定子类部分和父类部分在内存中加载的步骤和方法;

    4) 继承的写法:和C++一样,直接在子类名后面加": 父类名"即可,而Java是使用extends关键字来声明子类继承父类关系的

class Father {
    var a: Int = 5
    init(a: Int) {
        self.a = a
    }
}

class Son: Father { // 注意继承的写法
    var b: Int = 7
    init(a: Int, b: Int) { // 和Java一样使用super代表父类,这里利用super调用父类的构造器
        self.b = b
        super.init(a: a) // 构造出父类部分的内存从而从父类那里继承得到父类的东西
    }
}


2. 继承中构造器和直接初始化之间的执行顺序:

    1) 还是那个原则,就是先构造器后直接初始化;

    2) 由于继承的关系会使各个类之间形成继承链条,比如C继承B,B又继承A从而形成一条继承链,而在继承机制中构造器和直接初始化之间的顺序非常微妙,这涉及到Swift构造过程的一个安全检查机制;

    3) Swift的其中一大安全检查机制——保证类中所有的内容都被初始化:这个机制其实之前讲过的,而在继承机制中该安全机制是被这样执行的:

        i) 大致顺序是:分两遍,第一遍是从子类到父类,第二遍再从父类到子类;

        ii) 第一遍的从子类到父类是指先调用子类构造器,其中子类构造器中调用了父类的构造器,而父类的构造器又调用了爷类的构造器,从而一层一层往上知道最顶层的构造器调用完;

        iii) 到达顶层后,再原路返回执行第二遍检查,而这个第二遍检查的内容就是直接初始化语句了,一直从顶层一层一层往下检查到底层的子类,方式和之前讲过的一样,就是忽略已经被构造器初始化过的属性只对那些没有被构造器初始化过属性执行直接初始化语句:

class Grandfather {
    var a: Int = 1
    init(a: Int) {
        self.a = a
    }
}

class Father: Grandfather {
    var b: Int = 2
    init(a: Int, b: Int) {
        self.b = b
        super.init(a: a)
    }
}

class Son: Father {
    var c: Int = 3
    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }
}
如果Son类这样写,则会由于构造器中未初始化c同时直接初始化语句中也找不到初始化c的语句而发生编译报错,要求对c进行初始化:

class Son: Father { // Bad!
    var c: Int /* = 3 */
    init(a: Int, b: Int, c: Int) {
        // self.c = c
        super.init(a: a, b: b)
    }
}


3. Swift要求并建议的构造器代理规则:

    1) 再次重申指定构造器和便利构造器的区别:这个区别只在类中有,对于结构体没有这个概念,便利构造器就是用convenience修饰的构造器,除此之外的构造器都是指定构造器;

    2) Swift要求并建议的代理规则:前面的继承代码中已经出现了向上代理的概念了,即使用super.init的方式调用父类的构造器;

规则总共有5条,对于横向代理(便利构造器)有2条,向上代理有3条:

        i) 横向代理规则一:便利构造器只能调用同一类中的其它构造器(指定构造器和便利构造器都行),但是不能调用父类构造器(否则就变成纵向代理而不是横向代理了),如果这样搞会直接报错的!

class A {
    var a: Int = 1
    var b: Int = 2
    
    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }
    
    convenience init(a: Int) { // 不仅可以调用指定构造器
        self.init(a: a, b: 7)
    }
    
    convenience init() { // 也可以调用其它便利构造器
        self.init(a: 9)
    }
}
        ii) 横向代理规则二:代理的构造器调用必须出现在第一句,并且仅有一句(之前强调过了)

        iii) 向上代理规则一:子类的每个指定构造器必须调用直接父类的指定构造器(必须是父类的指定构造器不能是便利构造器,可能会觉得记忆起来麻烦但其实很简单,因为便利构造器是横向的,纵向的不能和横向的交叉,因此纵向代理只能调用父类的指定构造器而不能调用父类的便利构造器),否则会报错!

class A {
    var a: Int = 1
    var b: Int = 2
    
    init(a: Int, b: Int) {
        self.a = a
        self.b = b
    }
    
    convenience init(a: Int) { // 不仅可以调用指定构造器
        self.init(a: a, b: 7)
    }
    
    convenience init() { // 也可以调用其它便利构造器
        self.init(a: 9)
    }
}

class B: A {
    var c: Int = 10
    
    init() {
        // super.init(a: 7) // Bad! 只能调用父类的指定构造器,编译错误!
        super.init(a: 3, b: 3)
        c = 11
    }
    
    init(c: Int) {
        self.c = c // 注意!调用父类构造器的语句不一定非得放在首位置
        super.init(a: 7, b: 20)
    }
    
    convenience init(m_c c: Int) { // 遍历构造器只能调用本层的其它构造器!
        self.init(c: 33)
    }
}
其中你可以发现,向上代理的时候,代理的构造器不必放在第一句,这和横向代理的便利构造器的语法不太一样!

        iv) 向上代理规则二:Swift建议在向上代理的时候先初始化子类的属性再调用直接父类的构造器,即子类的指定构造器要以向上代理作为结束

这个规则不是强制的,因为上例中也看到了,那个对c的赋值既可以写在上上代理前面也可以写在后面,两者都没有问题,但是还记不记得前面讲过的安全机制中的两遍检查?如果子类属性的初始化写在向上代理的后面,则构造的时候会先一直通过构造器构造到顶层,然后在从顶层开始执行那些写在向上代理后面的构造器内的初始化语句一直到底层,完了之后再从顶层到底层检查一遍直接初始化语句,这回非常麻烦影响效率,因此Swift建议将子类属性的初始化写在向上代理之前,也就是说将向上代理作为子类指定构造器的最后一句!

在之前的版本中违反这个规则会直接报错,但现在更先进了,并不会做那次重复的向下执行写在向上代理之后的语句了,而是在编译的时候Swift内部重新组织构造器的代码,隐式地将向上代理语句放在最后!

        v) 向上代理规则三:在向上代理之前不得访问父类中的属性,因为在Swift的继承就是通过向上代理实现的,在调用父类构造器之前父类的内容在内存中还不存在因此是无法访问父类中的属性的,不管使用self还是用super访问父类中的属性都会报错!

class A {
    var a: Int = 9
    init(a: Int) {
        self.a = a
    }
}

class B: A {
    var b: Int = 10
    init(b: Int) {
        self.b = b
        // self.a = 9 // Bad!
        // super.a = 9 // Bad! 此时父类的内存还不存在!
        super.init(a: 7)
    }
}


4. 子类从父类那里自动获取构造器:

    1) 如果子类不定义任何构造器则会从父类那里获取构造器(即子类的默认构造器和父类的构造器一样),但是构造器不能继承,因此只能说是获取;

    2) 获取的规则:

         i) 如果子类不写任何构造器则将获取父类的所有指定构造器;

         ii) 如果子类写了哪怕一个构造器则子类不会获得父类的任何一个构造器;

         iii) 如果获得了一个父类的指定构造器,则父类中所有调用它的便利构造器以及间接调用它的便利构造器都将被子类获得

!!简单地说就是如果子类写过构造器则父类构造器一个都没,如果什么都没写,则父类所有的构造器都将获得,但是上述的过程也是Swift编译器的分析过程,因此在这里我觉得有必要声明一下!

class A {
    var a: Int = 1
    var b: Int = 2
    
    init(a: Int, b: Int) {
        self.a = 3
        self.b = 3
    }
    
    convenience init() {
        self.init(a: 10, b: 20)
    }
    
    init(a: Int, b: Int, c: Int) {
        self.a = a
        self.b = b
    }
    
    convenience init(s: String) {
        self.init()
    }
}

class B: A {
    var c = 0
}

var b1 = B()
var b2 = B(s: "haha")
var b3 = B(a: 32, b: 77)
var b4 = B(a: 1, b: 2, c: 3)
    3) 这种获取的关系可以一直继承下去(如果后面在定义一个class C继承B也是OK的):

class C: B {
    var d = 0
}

var c1 = C()
var c2 = C(s: "haha")
var c3 = C(a: 32, b: 77)
var c4 = C(a: 1, b: 2, c: 3)


5. 重写属性:

    1) Swift的继承可以在子类中覆盖父类的一些特性,和C++不同的是Swift不仅可以覆盖方法而且可以覆盖属性和下标,必须使用关键字override声明重写的内容,同时重写对象和被重写对象之间的名字、类型必须完全一致;

    2) 属性重写的规则(一下的父类是指祖先,包括直接父类以及父类的父类等等):

         * 重写存储属性:

            i) 可以将父类的存储属性覆盖成计算属性或属性观察器;

            ii) 只要是父类的存储属性被重写,则该存储属性会被继承下来,只不过外界无法访问该存储属性,只能在子类内部通过super关键字访问!

            iii) 不能将父类的计算属性或属性观察器覆盖成存储属性;

            iv) 不能将存储属性重写成存储属性;

         ** 重写计算属性:

             i) 可以将可读可写计算属性重写为可读可写计算属性;

             ii) 不能将可读可写计算属性重写为只读计算属性;

             iii) 但只读计算属性可以覆盖为可读可写计算属性;

             iv) 可以将可读可写计算属性重写成属性观察者;

             v) 但是不能将只读计算属性覆盖成属性观察者;

!!如果覆盖成计算属性则必须同时覆盖getter和setter,不能只覆盖其中的一个,否则会报错!

!!如果覆盖成属性观察器,则willSet和didSet两个访问器可以只重写一个,但是不推荐这样做!

         *** 重写属性观察器:

               i) 可以将属性观察器重写为属性观察器;

               ii) 可以将属性观察器重写为可读可写计算属性;

               iii) 但是不可以将属性观察器重写为只读计算属性;

!!不能为常量存储属性覆盖计算属性和属性观察器,因为常量本身就不能定义成计算属性和属性观察器的!

class A {
    var a = 2
    var b: Int {
        return 7
    }
    var t: Int = 30
    
    var x: Int {
        get {
            return 76
        }
        set {
            a = 770
        }
    }

    var y: Int = 22 {
        willSet {
            println(newValue)
        }
        didSet {
            println(oldValue)
        }
    }
    
    var z: Int = 22 {
        willSet {
            println(newValue)
        }
        didSet {
            println(oldValue)
        }
    }
}

// 只要是父类的存储属性被重写,则该存储属性会被继承下来
// 只不过外界无法访问该存储属性,只能在子类内部通过super关键字访问!

class B: A {
    var c = 10
    
    override var a: Int { // 如果覆盖成计算属性则必须同时覆盖getter和setter,不能只覆盖其中的一个,否则会报错!
        get {
            return 33
        }
        set {
            c = 70
            super.a = 33 // 此时父类被覆盖的存储属性会被保存下来,访问必须通过super关键字
        }
    }
    
    override var b: Int { // 只读计算属性可以覆盖为可读可写
        get {
            return 99
        }
        set {
            c = 23
        }
    }
    
    override var t: Int { // 重写属性观察者
        willSet { // 属性观察器可以只重写一个
            println(newValue)
        }
        didSet {
            println(oldValue)
        }
    }
    
    override var x: Int { // 可以将父类的可读可写计算属性覆盖为属性观察器
        // 但不能将只读计算属性重写成属性观察器,否则会报错!
        willSet { // 同样属性观察器可以只覆盖一个
            println(newValue)
        }
        didSet {
            println(oldValue)
        }
//      return 99 // 不可以将可读可写计算属性重写为只读计算属性
    }

    override var y: Int { // 属性观察器可以重写成属性观察器
        willSet {
            println(10)
        }
        didSet {
            println(20)
        }
    }

    override var z: Int { // 属性观察器可以重写成可读可写计算属性
        get {
            return 77
        }
        set {
            c = 44
        }
//      return 99 // 不能就爱你过属性观察器重写成只读属性!
    }
}

var obj_b = B()
println(obj_b.a) // 33
// 虽然覆盖父类存储属性为计算属性后父类存储属性会被保留,但是无法访问!
// 只能访问子类覆盖过的计算属性

obj_b.b = 90
obj_b.t = 77 // 77 30,可见父类的t作为存储属性保存下来了
obj_b.x = 88 // 88 76,可见将父类计算属性的getter的返回值76作为原值来处理了


6. 重写方法:

Swift允许重写对象方法和静态方法,要求重写和被重写的方法的函数签名要完全一致(包括外部变量名),否则就是重载了:

class A {
    func show(str s: String) {
        println(s)
    }
    
    class func present(s: String) {
        println(s)
    }
}

class B: A {
    override func show(str s: String) { // 函数签名必须完全一致,否则就会报错
        super.show(str: "haha") // 有时需要利用父类的被重写的方法进行辅助
        println("hihi")
    }
    
    override class func present(s: String) {
        super.present("haha")
        println("hihi")
    }
    
    func test() {
        super.show(str: "x") // 同样可以在其它方法中调用父类的方法
    }
}

var a = A()
var b = B()

a.show(str: "A") // A
b.show(str: "B") // haha\nhihi

A.present("A") // A
B.present("B") // haha\nhihi
!!注意!父类的方法并没有被删除,其实还是被继承下来了,只不过被覆盖了以后无法通过外界调用,而只能在子类的内部使用super关键字调用来完成一定的辅助功能;


7. 重写下标:

和重写方法的规则一模一样:

class MyArray {
    let size: Int
    var buf: [Int]
    
    init(size: Int) {
        self.size = size
        buf = Array(count: size, repeatedValue: 0)
    }
    
    subscript(i: Int) -> Int {
        get {
            return buf[i]
        }
        set {
            buf[i] = newValue
        }
    }
}

class ChildMyArrary: MyArray {
    override subscript(i: Int) -> Int { // 同样函数签名必须相同,包括外部变量名
        get { // 两个访问器必须都重写,不能只重写一个
            return buf[i] // 因此即使其中一个代码和父类相同也必须重新写一遍
        }
        set {
            buf[i] = newValue * newValue
        }
    }
}

var arr = ChildMyArrary(size: 15)
arr[10] = 9
println(arr[10]) // 81


8. 继承限制:

    1) 和Java一样使用关键字final来限制继承;

    2) 如果用final修饰类成员(静态/非静态)则该成员不可被重写;

    3) 如果用final修饰整个类,则该类不可被继承;

    4) 不能同时用final修饰类和成员,因为两者逻辑上是冲突的,因为不能继承的类何以重写它的成员!

    5) 在开发商业软件的时候非常有必要使用final进行限制;


Swift笔记:继承

阅读数 161

Swift- 继承

阅读数 255

Swift 继承

阅读数 14

没有更多推荐了,返回首页