美文网首页swift学习
Swift入门基础3——类、属性、函数和方法

Swift入门基础3——类、属性、函数和方法

作者: Wang66 | 来源:发表于2016-12-16 18:16 被阅读95次

    Objective-C中一个完整的类需要两个文件,头文件.h和实现文件.m。头文件中写暴露给外部的属性和接口方法,而实现文件里写这个类的具体实现。而在Swift中再不需要两个文件了,你只需要定义一个单一的类文件,系统会自动生成面向外部的调用接口。

    类的定义语法:

    下面定义了一个有关学生的类StudentModel,它继承于NSObject。且定义了三个可变属性,stuNamestuMobile都是字符串类型的,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,我们打印size1size2,发现size1的值确实已被赋给size2。接着,我们修改size2的宽高属性,然后再打印size1size2,发现size2的值已被改变更新,而size1的值未变,仍是原来的值。
    这说明什么,说明size2size1是独立的两个实例了,当将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代码时,并未拷贝出新的实例,只是让stu2stu1一样,指向了同一个内存区域而已。

    为此,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!,假如它强转失败,程序会崩溃。

    下面看个例子:DeveloperDesigner类均继承于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,它并不存储数据,它的数据由类中其他属性计算,推导而来。所以定义计算属性时,需要写settergetter方法,在其中计算其数据。

    class SquareModel: NSObject {
        
        var length: Float = 0.0
        var girth: Float{
            get{
                return length*4
            }
            set{
                length = newValue/4
            }
        }
        
    }
    

    说到Swift的settergetter方法,就是上面代码中这样写: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{}在新的值被设置之前调用执行,并将新的属性值作为参数传入,默认叫newValuedidSet{}在新的值被设置之后立即调用执行,并会将旧的属性值作为参数传入,默认叫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一样的)

    相关文章

      网友评论

        本文标题:Swift入门基础3——类、属性、函数和方法

        本文链接:https://www.haomeiwen.com/subject/oaknmttx.html