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

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一样的)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容