类
Objective-C中一个完整的类需要两个文件,头文件.h
和实现文件.m
。头文件中写暴露给外部的属性和接口方法,而实现文件里写这个类的具体实现。而在Swift中再不需要两个文件了,你只需要定义一个单一的类文件,系统会自动生成面向外部的调用接口。
类的定义语法:
下面定义了一个有关学生的类StudentModel
,它继承于NSObject
。且定义了三个可变属性,stuName
和stuMobile
都是字符串类型的,stuAge
虽然未指明类型,但通过赋的初值,系统能推断它的类型为整型。
class StudentModel: NSObject {
var stuName: String
var stuMobile: String
var stuAge = 0
}
类的实例化和访问:
类StudentModel
只是一个虚无的,对事物的描述。要想通过这个类处理数据或者做一些事,还得通过该类生成一个该类的实例,即创建该类的对象。
类名StudentModel
后加一个括号()
,就是每个类默认的无参数的创建及初始化方法。它将分配内存和初始化整合成了一步,不像OC中创建对象先以alloc
分配内存,然后以init
初始化对象。
有了实例后,我们就可以通过实例对象访问它的属性和方法了。在Swift中均是以点语法访问属性和方法的。请注意:我们创建对象实例后将其赋值给了常量stuModel1
,既然该对象是常量,那我们为什么还能对其属性进行修改赋值呢? 因为类是引用型的,后面会说这个问题。
let stuModel1 = StudentModel()
stuModel1.stuName = "wang66"
stuModel1.stuAge = 24
print(stuModel1.stuAge)
类和结构体的比较:
Swift中的结构体和类有诸多相似的地方,很多类有的功能,结构体也有。比如:它们都可以定义属性存储数据;都可以定义方法提供某功能;都可以自定义一些构造器(初始化方法);都可以通过扩展增强其原本实现的功能;都可以实现协议。但是,类与其不同的是,首先类可以继承,它有继承的层级关系;其次,类是引用类型的,它是通过引用计数来管理类的实例的生命周期的。而结构体是值类型的。所谓值类型,就是其被赋予给一个变量、常量或者被传递给一个函数,总之在代码中传递时总是通过复制的方式。比如系统中表示尺寸的Size
就是个结构体:
struct Size {
var width = 0.0, height = 0.0
}
我们首先以结构体Size
的全员初始化方法实例化一个实例size1
,然后第二行将size1
赋给size2
,我们打印size1
和size2
,发现size1
的值确实已被赋给size2
。接着,我们修改size2
的宽高属性,然后再打印size1
和size2
,发现size2
的值已被改变更新,而size1
的值未变,仍是原来的值。
这说明什么,说明size2
和size1
是独立的两个实例了,当将size1
赋给size2
时,进行拷贝动作,size2
是从size1
拷贝出的新实例。这就是“值类型”,Swift中结构体,枚举和一般基本数据类型,包括数组,集合等集合类型都是“值类型”,它们的底层实现都是结构体。
注意:虽说在Swift中,String,Array和Dictionary类型均以结构体的形式实现。这意味着被赋值给新的常量或变量,或者被传入函数或方法中时,它们的值会被拷贝。但是真实情况却有所不同,Swift在幕后做了处理,只在绝对必要时才执行真实的拷贝。
用同样的观察方法,我们来观察类。以StudentModel
类为例:
var stu1 = StudentModel(name: "wang1", mobile: "18693133051", age: 24)
var stu2 = stu1
print(stu1.stuName)
print(stu2.stuName)
stu2.stuName = "wang2"
print(stu1.stuName)
print(stu2.stuName)
打印结果:
wang1
wang1
wang2
wang2
可以看到将stu2
的属性修改后,stu1
的属性也一样变化了。这就是“引用类型”的缘故。当我们创建一个对象,在内存中分配出一块内存后,我们是不能直接以这块在堆中的内存进行操作的,所以用一个变量stu1
指向了该内存区域,这时可以说这块内存被stu1
引用了,这是第一次被引用,引用计数器+1后为1了。
然后,我们将stu1
赋给stu2
,实际上意思是将stu2
也指向了stu1
所指向的那块内存区域,对于那块在堆中的内存区域来说就是,此时又有一个变量引用了它而已,引用计数器+1。在执行stu2= stu1
代码时,并未拷贝出新的实例,只是让stu2
和stu1
一样,指向了同一个内存区域而已。
为此,Swift定义了一个专门的运算符:“恒等运算符”===
和!==
来比较俩引用所指向的是否是同一内存区域。
if stu1 === stu2 {
print("stu1和stu2指向同一块内存区域")
}
判断类型和向下转型:
is关键字:
OC中有isKindOf:
方法来判断当前实例是什么类型。在Swift中则简洁到可怕,用is
关键字就可以了。
as关键字:
将某个实例的类型强制向下转换时,在OC中(Student *)obj
这样做,把obj
强制向下转换为Student
类型的。而在Swfit中,则通过as?
和as!
关键字来完成。强制向下转换有可能失败,若你不保证一定会成功时,可以用as?
关键字。即使转换失败也不会崩溃,而是为可选类型的nil
,你可以通过这个结果判断强转是否成功。如果你非常肯定一定可以强转成功,则可以用as!
,不过还是要慎用as!
,假如它强转失败,程序会崩溃。
下面看个例子:Developer
和Designer
类均继承于JobType
。
class JobType: NSObject {
var jobName: String = ""
init(name: String) {
self.jobName = name
super.init()
}
}
class Developer: JobType {
var gitHudUrl: String?
init(name: String, gitHud: String) {
self.gitHudUrl = gitHud
super.init(name: name)
}
}
class Designer: JobType {
var designStyle: String?
init(name: String, style: String) {
designStyle = style
super.init(name: name)
}
}
调用:
let jobs = [Developer(name: "wang66", gitHud: "wang66_url"),
Designer(name: "chen1", style: "UI"),
Designer(name: "chen2", style: "other"),
Developer(name: "wang77", gitHud: "wang77_url")
]
var developers = [Developer]()
var designers = [Designer]()
for item in jobs
{
if item is Developer {
developers.append(item as! Developer)
}else if item is Designer {
designers.append(item as! Designer)
}
}
print("developers:\(developers)")
print("designers:\(designers)")
}
属性
Swift中定义属性简洁了许多。不再像OC一样,分为在头文件里暴露给外部的属性,内部实现的用的属性,还有成员变量。在Swift只在一个类文件中以一种形式定义属性。
存储属性和计算属性:
Swift中的属性分为“存储属性”和“计算属性”,乍一看好像很复杂,其实在OC中虽然没有没有这样的叫法,但在实际编码中我们肯定使用过。所谓存储属性,就是存储在特定类或结构体实例里的一个常量或变量;看个例子:
定义了一个表示正方形的类SquareModel
,其中定义了一个表示边长的属性length
,这就是存储属性,用于在该类中存储正方形的边长。还定义了一个表示周长的属性girth
,它并不存储数据,它的数据由类中其他属性计算,推导而来。所以定义计算属性时,需要写setter
和getter
方法,在其中计算其数据。
class SquareModel: NSObject {
var length: Float = 0.0
var girth: Float{
get{
return length*4
}
set{
length = newValue/4
}
}
}
说到Swift的setter
和getter
方法,就是上面代码中这样写:get
关键字加花括号就是getter
方法,set
加花括号就是setter
方法了。setter
方法是为实例的属性进行赋值的,赋的新值是由默认的,名叫newValue
的参数传进来的。若想使代码语义更清晰,可以自己命名参数名。
set(newGirth){
length = newGirth/4
}
其实这个表示周长的计算属性,应该是只读的就比较恰当。在外部给周长赋值没什么意义,因为完全可以由边长计算出。所以说,这个girth
应当是只读计算属性。在定义时不要写setter
方法就是了,它就是只读的了。
class SquareModel: NSObject {
var length: Float = 0.0
var girth: Float{
get{
return length*4
}
}
}
只读计算属性的getter
方法,可以去掉get
关键字和花括号:
var length: Float = 0.0
var girth: Float{
return length*4
}
** 注意: 计算属性,不管是可读还是不可读,都得定义成变量var
。
延迟存储属性:
延迟存储属性,这是个名词,指延迟了的存储属性,不如形象地叫做“懒属性”。它是指有些属性比较复杂,比较消耗资源,在实例化对象时就对所有属性也进行初始化的话,有时比较浪费。不如,我们可以设置某些不常用,或者很复杂的属性可以延迟加载,即在外部真正调用它时才初始化。
在属性申明时,前面加lazy
来指示它是延迟加载的属性。比如StudentModel
类中有个courseManage
属性,代表有关学生课程管理的东西,可以延迟初始化它。
lazy var courseManage: CourseManager? = CourseManager()
属性观察器:
属性观察器用来监听属性的变化。willSet{}
在新的值被设置之前调用执行,并将新的属性值作为参数传入,默认叫newValue
;didSet{}
在新的值被设置之后立即调用执行,并会将旧的属性值作为参数传入,默认叫oldValue
。当然这两处的新值和旧值都是可以自定义命名的。比如willSet(newLength){}
。
class SquareModel: NSObject {
var length: Float = 0.0 {
willSet{
print("has not changed\t newValue=\(newValue)")
}
didSet{
print("has changed\t oldValue=\(oldValue)")
}
}
}
打印结果为:
has not changed newValue=125.5
has changed oldValue=0.0
类型属性:
上面我们说的属性都是“实例属性”,即某个类型的,实实在在的实例的属性。一个类可以有许多实例,那这些实例则各有一份的“实例属性”,互相独立不影响。其实,也可以给类本身定义属性,即为“类属性”,无论该类有多少份实例,类属性是唯一的一份。
跟实例的存储型属性不同,必须给存储型类属性指定初始值,因为类型本身没有构造器,也就无法在初始化过程中使用构造器给类型属性赋值。
存储型类属性默认即是延迟初始化的,不需要手动加lazy
关键字。
类型属性的语法:
使用static
关键字定义一个类属性:
static var isRight = true
使用:直接以类名“点”出来。
let isRight = StudentModel.isRight
print(isRight)
注意:如果计算型类型属性允许被子类重写的话,则应当用class
关键字取代static
来定义计算型类型属性。
函数
函数的定义和使用:
下面定义了一个需要传入String
类型,名为name
的参数,返回值为String
类型的,名叫greet
的函数。
以关键字
func
定义一个函数,参数列表写在括号()
里,->
后写返回值类型。
func greet(name: String) -> String {
let greetStr = "hello,\(name)!"
return greetStr
}
// 无参数
func greet() -> String {
return "hello,dear"
}
// 多参数
func greet(name: String, alreadyGreeted: Bool) -> String {
if alreadyGreeted{
return "hi~\(name)"
}else{
return self.greet(name: name)
}
}
// 无返回值
func sayHello(name: String) -> Void {
print("hello,\(name)")
}
// 无返回值的函数,也可以省略后面的Void
func sayHello1(name: String){
print("hello,\(name)")
}
参数标签:
定义函数时,可以在参数列表的参数前面写“参数标签”,使得调用函数时语义更清晰直观;Swift还允许在参数列表里为参数设置“参数默认值”,当调用函数未给此函数赋值时,便将该默认值传入函数内部。
// 参数默认值
func studentScore(name: String, score: Int = 0) -> Void {
print("name=\(name), score=\(score)")
}
可变参数:
除此外,Swift还允许“可变参数”,这里的可变参数指参数的个数是不定的,变化的。需要注意的是,可变参数必须是同一类型的,而且一个函数最多只能拥有一个可变参数。
// 可变参数(一个函数最多只能拥有一个可变参数)
func numsAdd(nums: Int...) -> Int {
var resultNum = 0
for num in nums {
resultNum+=num
}
return resultNum
}
调用:
let resultNum = self.numsAdd(nums: 2,4,5,7,6,2)
print(resultNum)
可变参数传入函数内部,完全是一个数组,其实它本身就和数组很像,都是一组同类型的数据。但是对于调用者来说直接传入一串数据,可能稍比构建一个数组后再传入函数简洁点吧。
输入输出参数:
函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。
定义一个输入输出参数时,在参数定义前加 inout 关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看输入输出参数一节。
你只能传递变量给输入输出参数。你不能传入常量或者字面量,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加 & 符,表示这个值可以被函数修改。
注意: 输入输出参数不能有默认值,而且可变参数不能用 inout 标记。
// 输入输出参数(交换俩整型变量)
func swapTwonInts(a: inout Int, b: inout Int) -> Void {
let tempInt = b
b = a
a = tempInt
}
调用:
self.swapTwonInts(a: &numA, b: &numB)
print("numA=\(numA) numB=\(numB)")
函数的类型:
函数的类型由参数类型和返回值类型一并决定。
func handleName(firstName: String, lastName: String) -> String {
return firstName + lastName
}
上面这个函数的类型是(String, String) -> String
。
既然函数也是有类型的,那它完全也可以像普通变量那样赋给别的变量,也可以当作参数,也可以当做返回值。
将函数当作变量:
handleName
是个(String, String)->String
类型的函数,将其赋给同是(String, String)->String
的变量handeNameFuntion
,然后通过handeNameFuntion
传入参数调用之,打印结果为:wang66
。
func handleName(firstName: String, lastName: String) -> String {
return firstName + lastName
}
let handeNameFuntion: (String, String)->String = handleName
print(handeNameFuntion("wang", "66"))
将函数类型作为参数。我们另外定义了一个函数
handleSomething
,它有三个参数,第一个为(String, String)->String
类型的函数类型,后两个参数均为String
。这个函数的功能就是:传入三个参数,然后在函数内部用传入的第一个参数,它是个函数。将后两个参数传入该函数,然后执行它。
func handleSomething(handleNameFun: (String, String)->String, str1: String, str2: String) -> String {
return handleNameFun(str1, str2)
}
类方法:
方法是与某些特定类型相关联的函数。其实完全可以认为是同一个东西。
上面我们所说的都是“实例方法”,Swift中怎么定义“类方法”呢?OC中是以-
和+
来区分实例方法和类方法的,而在Swift中则在定义方法的关键字func
前加上static
关键字即可。
我们在StudentModel
中定义一个类方法:
static func wholeNameHandle(firstName: String, lastName: String) -> String{
return firstName + lastName
}
调用:
let resultName = StudentModel.wholeNameHandle(firstName: "wang", lastName: "66")
print(resultName)
如果此类方法允许被子类重写,则应当使用
class
关键字来取代static
来定义类方法。(这和计算型类型属性若允许子类重写用class
一样的)
网友评论