1、面向对象
面向过程:面向过程编成将所要解决的问题按解决问题的步骤进行分析。如果是大问题,就分解成为多个不同的小问题,在程序里叫做划分成不同的模块。每一个解决的步骤可能是一行或者几行代码,也可能是一个函数。这样把每一个步骤都实现或者解决掉,最后把所有的解决手段按顺序进行调用,如果遇到一些意外情况就分情况处理,如果遇到重复的操作就循环处理,如此就完成了整个程序。
面向对象:以面向对象的方式去解决问题的思路,我们关注的往往不是解决问题的步骤。更多的是将问题所涉及的方面进行一些角色层次上的划分。在每个角色上都有其特性,也有角色所要承担的责任。分清楚了这些之后,通过整合各个角色之间的行为互相配合,来最终解决问题。我们划分问题解决中各个参与者的角色,并不是为了完成某一个步骤而是从所划分的角色本身出发,描述其在整个问题解决过程中的行为。
面向过程的编成,步骤划分是关键。而面向对象的编成,对象分析是关键。面向过程的编成,随着步骤的增多和步骤的复杂,程序的逻辑也将更加的复杂。而面相对象的编成,跟步骤无关,各个对象各司其职,职责清楚,并不会因步骤的复杂而复杂,至少复杂的程度并不像面向过程那么深,并且更适合做大型问题的解决方案。面向过程编成中,是以过程、步骤、算法为驱动的,我们的程序逻辑跟这三点紧紧关联。而面向对象的实现中,对象之间是以方法、消息来互相通信协调的,程序可以方便实现角色的分离和分层。
-
面向对象编程的特点:面向对象编程有三大特点,称之为封装性、继承性和多态性。
-
封装性:封装性意味着至少三点。一是说我们的类或对象在外部看来是一个有数据和方法的整体,不是支离破碎的数据、行为、步骤,而是整体的对象,有对象然后才是对象的属性和方法。二是说我们的类或者对象对外界存在一定的信息隐藏,对内来设置哪些外界能看到,哪些看不到,对外,只能看到能看到的,操作能操作到的。三是外界知道对象可以接受那些信息,进行什么样的处理,但是具体是如何进的,外界不用知道也无从知道。
- 简单点讲,封装性就是为了方便类的设计者对信息进行隐藏,提高安全性。而对调用者而言,也减少了需要知道的内容,更不需要知道实现,减少了调用的复杂度。
继承性:继承性是说,在面向对象中,除非某个类已经显式的指出不允许在其上做功能的扩展,否则我们有办法对其进行扩展,不论这个类的源代码是否为我们所掌握,我们都能根据现有的类来创建新类,扩展类的能力。
多态性:多态性指的是,由于不同的类进行了不同的封装,它们公开出来的方法可能有同一个方法,但是外界调用同一个方法的时候,不同的类返回不同的行为,这就是多态性。通过继承过程中对同一方法的不同实现,就可以实现多态。
-
- 为什么要使用面向对象编程:
- 总结来讲,面向对象有如下优点:
- 1、易维护:采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块进行,所以维护起来非常的方便,成本也较低。
- 2、质量高:在设计时,可重用现有的、在以前的项目的领域中已经测试过的类,使系统满足业务需求并具有较高的质量。
- 3、效率高:在软件开发时,根据设计的需求对现实世界的事物进行抽象,产生类。使用这样的方法解决问题,接近日常生活和自然的思考方式,势必提高软件开发到的效率和质量。
- 4、易扩展:由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。
- 总结来讲,面向对象有如下优点:
2、类
类:类是一种抽象的概念,它实际上并不存在于现实中的时间和空间里。在定义一个类时,我们就会在类中定义该类事物的特性和行为,从术语上讲,特性叫类的属性,行为叫类的方法。类是一个静态的概念,类本身不携带任何数据。当没有为类创建任何对象时,类本身不存在于内存空间中。
-
1)类与结构体的关系(在 Swift 语言中):
-
它们的共同点如下:
- 1、都可以定义一些可以赋值的属性。属性的类型和写法大多相同。当然也有不同,比如类型属性的写法,在类中用 class 前缀,在结构体中用 static 前缀修饰。
- 2、都可以定义具有功能性的方法。可定义方法类型也大多相同。但正如属性,对于类型方法的定义,在类中用 class 前缀,在结构体中用 static 前缀修饰。
- 3、都可以定义下标脚本,使用下标语法。
- 4、都可以定义初始化方法来设置初始状态,初始化属性。
- 5、都可以做扩展实现。
-
它们的不同点如下:
- 1、在数据结构的扩展和继承方面,类更加强大。
- 2、我们可以让一个类的实例来反初始化,适时释放存储空间,但是结构体做不到。
- 3、类的对象是引用类型,而结构体是值类型。
从上面可以看出类、结构体在定义、初始化方法、属性定义等方面几乎完全相同,class 和 struct 关键字可以互换。但类的赋值是传引,而结构体则是传值。两个结构体,只要属性值都相同,我们就可以认为结构体相同,是同一个结构体。而两个类的对象,光是属性值相同,并不一定相同,不一定是类的同一个对象。
-
一般来说,下面的一个或多个条件满足时,应当选择创建一个结构:
- 1、结构主要是用来封装一些简单的数据值。
- 2、当复制或者传递的时候更希望这些封装的数据被赋值,而不是被引用过去。
- 3、所有被结构存储的属性本身也是数字类型。
- 4、结构不需要被另外一个类型继承或者完成其他行为。
用类会是更好的选择。也就是说一般情况下,数据都会定义为类。
-
-
2)类扩展: 在 Swift 语言中类扩展是一个强大的工具我们可以通过这个扩展语法完成如下事情:
- 1、给已有的类添加计算属性和计算静态属性。
- 2、定义新的实例方法和类型方法。
- 3、提供新的构造器。
- 4、定义下标脚本。
5、使一个已有的类型符合某个协议。
-
在定义类扩展时,在类名前直接加前缀 extension 。
extension String { } // 使类 String 符合 URLStringConvertible 协议 extension String: URLStringConvertible { }
-
3)类组合:在面向对象的设计原则中,有一条叫“组合优先于继承”。组合就是在已有的类或者模块上实现一个新功能时,可以通过在新的类里实现新功能,同时在新的类里,调用或者组合已有的对象,两者联合在一起实现功能。既是说一个类的某一属性是另一个类的实例。
白箱复用:继承时子类继承父类,子类就拥有了父类所有相关的属性和方法,也就是说父类的实现细节,对于子类其实是可见的。使用继承的目的是为了代码复用,这种复用方式叫白箱复用。
黑箱复用:组合是通过对象的方式访问已有的类的功能。在类中,可以封装的比较彻底,类所暴露出来的接口和属性是相当有限的,在调用时也不需要明白类的实现细节,这种复用方式叫黑箱复用。
-
4)类前缀:
使用 Objective-C 开发 iOS 程序时,最好在每个类名前面加一个前缀,用来标识这个类。
- 目的是防止 N 个人开发了一样的类,出现冲突。
- 比如 Jake Will、Kate Room 在同一个项目中都各自开发了个 Button 类,这样的程序是不能运行起来的。
- 解决方案:Jake Will 的类名叫做 JWButton,Kate Room 的类名叫做 KRButton。
-
类前缀的设置
-
Xcode 6 之后:
-
创建完项目后设置。
-
-
设置完后,再创建新的文件时会自动添加上设置的类前缀。
-
3、对象
对象:对象是类的具体化的东西,从抽象整体中具体化出的特定个体。
对象是一个动态的概念。每一个对象都存在着有别于其他对象的属于自己的独特属性和行为。对象的属性可以随着他自己的行为的变化而改变,我们把具体的对象叫做类的实例化。
4、属性
属性:也叫类的成员变量。成员变量不能独立于类而存在。成员变量是描述类的对象的状态数据。
属性就是类所表示的现实对象的特性在代码中的反应。在 Swift 语言中属性的类型分为两种,存储属性和计算属性。
-
1)存储属性:用来表示类的一个特性的属性。正如变量和常量一样,属性也分为变量属性和常量属性,都叫存储属性。并且分别用关键字 var 和关键字 let 来描述。对于存储属性需要注意以下三点注意事项。
第一、存储属性在定义时,需要为每一个属性定义一个默认值,当然也可以在初始化的时候设置属性的初始值。如果初始化也不定义默认值,在调用 super.init() 时会出错,或者在声明了对象之后,也无法为此变量赋值,除非属性变量被明确定义为强制解包可选。
第二、存储属性中的常量属性并不意味着绝对不能变化,而是要看情况。如果存储属性的常量属性是值类型,比如字符串、结构体,就不能再变,结构体的属性也不能再变。如果是类,那么就可能重新赋值。
-
第三、存储属性一般要求初始化,我们可以设置一个 lazy 修饰符,使得这种初始化延迟,尽管我们在属性声明时已经做了这种初始化上的定义。
所谓修饰符就是加在变量、函数、类等的定义前面,用来约束或者增强变量、函数、类功能的一种特殊的关键字。比如修饰符 objc 表示导出给 OC 调用。
-
lazy 修饰的存储属性叫懒惰存储属性(懒加载)。懒惰存储属性是指当他第一次被使用时才进行初值计算。懒惰存储属性必须是变量属性(即用 var 定义的属性),因为他的初始值直到实例初始化完成之后,在调用之时才被计算。常量属性在实例初始化完成之前就应该被赋值,因此常量属性不能够被声明为懒惰存储属性。
lazy var shops:NSArray = { let filePath:String = NSBundle.mainBundle().pathForResource("shops", ofType: "plist")! let tmp = NSArray(contentsOfFile: filePath)! return tmp }()
在全局变量中不需要显式的指定 lazy 属性,而是所有的全局变量都是延迟计算的。
-
2)计算属性:本身并不直接存储特性,提供的是一个计算后的结果。计算属性并不能直接的保存数据,但是可以使用 getter 和可选的 setter 来间接的获得或者改变其他的属性和值。
var scale:Int { get { return a/b } set { a = a * newValue b = b * newValue } } var scale:Int { get { return a/b } set (val) { a = a * val b = b * val } } var sum:Int { return a+b } var sum:Int { get { return a+b } }
上式中第一个 set 的方法也是简写方法,简写时,新赋的值默认为 newValue。而不简写时可以自定义变量。
-
有关计算属性,有如下几点注意事项:
第一、计算属性(包含只读计算属性)都应该使用 var 关键字,因为他们的值并不是固定的。
第二、使用计算属性一定要声明类型,否则编译器会报错。
-
3)属性观察者:属性观察者更像是触发器,不过属性观察者同触发器不同的是,还可以在属性变更前触发。属性观察者观察属性值的改变并对此作出响应。当设置属性的值时,属性观察者就被调用,即使当新值同原值相同时也会被调用。除了懒惰存储属性,你可以为任何存储属性加上属性观察者定义。另外,通过重写子类型属性,也可以为继承的属性(存储或计算)加上属性观察者定义。
var score:Int = 0 { willSet { label.text = "\(stype):\(newValue)" } } var score:Int = 0 { didSet { label.text = "\(stype):\(score)" } }
-
属性观察者,有如下需要注意的几点:
1、给属性添加观察者必须要声明清楚属性类型,否则编译器报错。
2、willSet 可以带一个 newName 的参数,如果不这样的话,正如计算属性中一样,这个参数就默认的被命名成 newValue。
3、didSet 也可以带一个 oldName 的参数,表示旧的属性,如果不带,这个参数默认命名为 oldValue。
4、属性初始化时,willSet 和 didSet 并不会被调用。只有在初始化上下文时,当设置属性值时才被调用。
5、willSet 和 didSet 每次设置属性值都会被调用,即使是设置的值和原来的值相同。
-
-
4)类型属性:可以定义一种属性,这种属性不属于任何一个类的实例,即不属于任何一个对象。即使创建再多的这个类的实例,这个属性也不属于任何一个。它只属于类本身,这样的属性就称为类型属性。我们在类中定义的时候,直接在关键字 var 前加上 class 关键字,同时要用 get 函数的设置方法返回变量的值,而不能直接赋值,因为目前不支持。
class Teacher { var name:String = "" class var coumtry:String { return "中国" } }
-
其实类型属性也可以改写,但是目前 Swift 语言还支持的不太好。
class Teacher { var name:String = "" class var country:String { get { return "中国" } // 这里会导致循环赋值,写入不成功 set { Teacher.country = (newValue) } } }
-
但可以使用一些折中的解决方案。
class Teacher { var name:String = "" // 使用带有静态属性的结构体 private struct scountry {static var country:String = "中国"} public class var country:String { get { return scountry.country } set { scountry.country = newValue } } }
-
5、方法
方法:另一个说法也叫成员函数,英文单词为 method,而不是表示函数的单词 function,所以叫方法。方法中定义的是类的行为。
-
1)实例方法:在类内部的定义和实现上看起来同函数没有区别,不过是能直接访问类的属性。在访问类的属性时,直接引用属性的名称即可,当然也可以加一个 self 前缀。
- 类的每一个实例都有一个隐含属性 self。self 完全等同于该实例本身,可以在类的实例方法中,使用 self 来引用实例本身,从而获得对实例其他属性和方法的访问。正由于是隐含的,这个 self 大部分情况下都可以省略。但是在类中,存在同名局部变量的情况下,self 就不能省略。
-
2)类型方法:类型方法不属于任何一个类的对象,而是属于公共的类,可以通过类名直接调用。但是正是如此,他也不能调用变量属性,因为使用类型方法,并不需要初始化类,没有空间存储对象,因而也并没有空间来保存变量。在类型方法的定义中,使用 self 是不允许的。类型方法的定义只需在定义的 func 前面加上 class 即可。
class func localizedStringWithFormat(format: NSString, _ args: CVarArgType...) -> Self
-
3)初始化方法:是在类初始化为对象时调用的方法。在创建对象之后,使用对象的属性之前,我们得做好相应的属性初始化,否则在没有初始化的前提下,就会报错。遇到这种情况时,一种解决方案是在定义变量时就做好初始化,另一种解决方案就是使用初始化方法。
- 初始化方法,同普通方法不同,方法名是固定的 init,也没有返回值,方法名 init 前面也不加 func 关键字。初始化方法可以接受参数,init 初始化方法的参数,在创建对象时就可以传入,不过同函数不太一样的是,调用时,每一个参数的变量名都需要加上,而普通函数,第一个是不需要写的。
init() { width = 0 height = 0 } var rect = Rect() init(w:Int, h:Int) { width = w height = h } var rect = Rect(w:10, h:20)
初始化方法可以有多个,并且每一个在方法名上都一样,都是 init,但是参数上要有所不同。同其他语言不同,Swift 语言中,对于方法参数的不同,不仅仅是通过参数类型来判定的,还涉及参数的名称。两个方法相同,尽管参数数据类型一样,但是参数名不同,就是可以同时存在。
-
<1>、默认初始化方法:Swift 语言将为所有属性已提供默认值的且自身没有定义任何构造器的结构体的基类提供一个默认的初始化方法。
- 第一种情况,对存储属性都初始化了之后,可以用默认初始化方法实例化对象。
- 第二种情况,对类存储属性设置为可选存储属性,可以用默认初始化方法实例化对象。
- 第三种情况,对类存储属性设置为强制解包可选,可以使用默认初始化方法实例化对象。
-
<2>、便利初始化方法:便利初始化方法的出现正像他的名字,是为了便利我们调用而出现的。在他的实现里面,必须调用一个其他的初始化方法,便利初始化方法,在 init 前面加上 convenience 关键字即可。这些调用独立初始化方法的初始化方法,就叫便利初始化方法。
init(width:Int, height:Int) { self.width = width self.height = height } convenience init(w:Int, h:Int) { self.init(width:w, height:h) }
-
<3>、闭包初始化方法:如果某个存储类型属性的默认值需要特别定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。闭包后需要加“( )”,这里用来告诉 Swift 需要立刻执行此闭包,否则会把闭包本身作为值赋给了属性。
var score:[Int] = { var scores:[Int] = Array() for m in 0...3 { scores.append(m) } return scores } ()
-
4)反初始化方法:我们假设一个场景,我要建立一个数据库访问类,在初始化时打开链接,如果程序退出,连接不释放,资源就会浪费。所以我们引入一种新的特殊方法,跟初始化方法相对,叫反初始化方法。反初始化方法的方法名为 deinit,在把对象设置为 nil 时,系统会自动调用反初始化方法。
deinit { self.conn.close() self.conn = nil }
5)重载:在类中,这种相同名字不同参数的方法的写法有一个专门的术语来描述,叫做重载。在别的语言中,重载的情况只出现在类定义里,但是在 Swift 语言中,类中的实例方法可以重载,而全局的函数也可以重载,只要参数名或者参数别名不同就可以实现重载,而不用关心参数数据类型的相同与不同。
6、封装、继承、多态
-
1)封装:封装主要有两大目的:一是为了我们使用数据更加方便。二是为了数据保护。
在 Swift 语言中,访问修饰符也分为三类,分别是 private、internal、public,并且从 Xcode6 beta4 才开始支持。Swift 对访问权限的控制,不是基于类的,而是基于文件的。
-
在 Swift 语言中,若要设置某一属性的访问权限,需在定义属性时加上相应的修饰前缀。如:private var name:String = "" ,修饰只对这个属性变量起作用。
private :所修饰的属性或者方法只能在当前 Swift 源文件里可以访问,在别的文件里访问会出错。
- internal:所修饰的属性或者方法在源代码所在的整个模块都可以访问。是默认的访问权限。
- 如果是框架或者是库代码,则在整个框架内部可以访问,框架由外部代码所引用时,则不可访问。如果是 App 代码,在整个 App 内部可以访问。
- public :所修饰的属性或者方法对于引用了该文件或者模块的文件来讲,都能访问这些属性和方法。
- 一般来说 public 的出现是为了 API 开发而设置。
-
2)继承:由上至下,是一个具体化的过程,由下至上,是一个抽象化的过程。而继承的概念就是在这种基础上产生的。
我们使用“ : ”符号来表示继承的关系。在定义类时的类名后加上“ : ”符号和基类名,表示当前定义的类继承了该基类。
继承后子类自动就获得了父类的相关属性和方法。
self 指向的是当前对象,而对于超类属性和方法的访问,如果继承自父类或祖先类的,就可以用 self 来访问。但在初始化方法中,我们调用父类的 init 方法时,只能用 super 来指向和访问。
-
重写(覆盖):如果子类和父类的方法、属性重名,有一个专门的术语叫覆盖或者重写。我们可以在子类中写上同名,同样参数和返回值的方法,以重写父类中的相应方法。参数一定要相同,否则就是重载,而不是重写或者覆盖。
-
重写方法或参数时需在方法的 func 前和属性的类型 var 前加上 override 关键字。
// 重写方法 override func grow(){ } // 重写属性 override var position:Position
-
防止父类中的某个方法被重写,可以在父类中定义方法的时候在 func 前写上 @final 关键字。这样子类想覆盖时就会出错。
@final func fatherGrow(){ }
-
-
3)多态:同样的操作或者方法,不同的对象在执行时会出现完全不同的行为,这就叫多态。
多态是讲具有同样的父类的一批类之间,都重写了父类的同一个方法实现自己的行为,但是表现出来则各不相同。多态属于一种叫模板模式的设计模式。
多态是针对不同对象调用同一个方法,能产生不一样的结果。而重载是在同一个类内实现了多个同名的方法,重载针对同一个对象,同名的方法,参数不同,调用后结果相同不相同不关心。
7、协议
我们有时实现一个类,并不想拥有该类的所有属性和方法。甚至由多个类来实现同一个类,只是想拥有同一个方法,不想获得父类的具体实现。
Swift 语言中协议类似于在别的语言里的接口,协议里只做了方法的声明,包括方法名、返回值、参数等这些信息,而没有具体的方法实现。这样的话,类实现了该协议,也只是获得了一个方法的声明,而不是详细的实现。达到了几个好处:一是只实现了该实现的,子类只知道了该知道的。其次,协议也可以作为数据类型来传输。这样在传递数据时,我们不用关注对象的类型,提高了抽象层次和通用性,也就是可复用性。
-
Swift 语言中协议的定义方法用 protocol 关键字,类实现协议只需在类名后加“ : 协议名”,类似于一个类继承自某个协议。
// 创建协议 protocol Bird { // 协议方法 func song() } // 遵守协议 class Swallow: Bird { // 实现协议方法 func song() { print("swallow song") } }
协议本身可以继承自另一个协议,当然不能继承自另一个类,因为协议是不能有具体方法实现的,所以不能继承自类,协议继承自另一个协议,无非就是多了一个方法定义。
协议的方法前面可以加 @optional 构成可选方法,但值得注意的是,这个修饰符只适用于通过导出给 OC 代码调用的 Swift 代码实现。