欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS开发--Swift语言2

程序员文章站 2022-06-30 13:24:09
函数的闭包 函数 函数是一个完成独立任务的代码块,swift中的函数不仅可以像c语言中的函数一样作为函数的参数和返回值,而且还支持嵌套,并且有c#一样的函数参数默认值、可变参数等。 //定义一个函数...

函数的闭包

函数

函数是一个完成独立任务的代码块,swift中的函数不仅可以像c语言中的函数一样作为函数的参数和返回值,而且还支持嵌套,并且有c#一样的函数参数默认值、可变参数等。

//定义一个函数,注意参数和返回值,如果没有返回值可以不写返回值或者写成void、空元组()(注意void的本质就是空元组) funcsum(num1:int,num2:int)->int{ returnnum1+num2 } sum(1,2)

可以看到swift中的函数仅仅表达形式有所区别(定义形式类似于javascript,但是js不用书写返回值),但是本质并没有太大的区别。不过swift中对函数参数强调两个概念就是局部参数名(又叫“形式参数”)和外部参数名,这极大的照顾到了objc开发者的开发体验。在上面的例子中调用sum函数并没有传递任何参数名,因为num1、num2仅仅作为局部参数名在函数内部使用,但是如果给函数指定一个外部参数名在调用时就必须指定参数名。另外前面也提到关于swift中的默认参数、可变长度的参数,包括一些高级语言中的输入输出参数,通过下面的例子大家会有一个全面的了解。

/** /** * 函数参数名分为局部参数名和外部参数名 */ funcsplit(stringa:string,seperatorb:character)->[string]{ returnsplit(a,maxsplit:int.max,allowemptyslices:false,isseparator: {$0==b}) } //由于给split函数设置了外部参数名string和seperator,所以执行的时候必须带上外部参数名,此处可以看到一个有意义的外部参数名大大节省开发者使用成本 split(string:"hello,world,!",seperator:",")//结果:["hello", "world", "!"] //下面通过在局部参数名前加上#来简写外部参数名(此时局部参数名和外部参数名相同) funcsplit2(#string:string,#seperator:character)->[string]{ returnsplit(string,maxsplit:int.max,allowemptyslices:false,isseparator: {$0==seperator}) } split2(string:"hello,world,!",seperator:",") //上面的split函数的最后一个参数默认设置为",",注意如果使用默认参数那么此参数名将默认作为外部参数名(此时局部参数名和外部参数名相同) funcsplit3(#string:string,seperator:character=",")->[string]{ returnsplit(string,maxsplit:int.max,allowemptyslices:false,isseparator: {$0==seperator}) } split3(string:"hello,world,!",seperator:",")//结果:["hello", "world", "!"] split3(string:"hello world !",seperator:" ")//结果:["hello", "world", "!"] //但是如果有默认值,又不想指定局部参数名可以使用“_”取消外部参数名 funcsplit4(string:string,_seperator:character=",")->[string]{ returnsplit(string,maxsplit:int.max,allowemptyslices:false,isseparator: {$0==seperator}) } split4("hello,world,!",",")//结果:["hello", "world", "!"] /** * 可变参数,一个函数最多有一个可变参数并且作为最后一个参数 * 下面strings参数在内部是一个[string],对于外部是不定个数的string参数 */ funcjoinstr(seperator:character=",",strings:string...)->string{ varresult:string="" forvari=0;i ifi!=0{ result.append(seperator) } result+=strings[i] } returnresult } joinstr(seperator:" ","hello","world","!")//结果:"hello world !" /** * 函数参数默认是常量,不能直接修改,通过声明var可以将其转化为变量(但是注意c语言参数默认是变量) * 但是注意这个变量对于外部是无效的,函数执行完就消失了 */ funcsum2(varnum1:int,num2:int)->int{ num1=num1+num2 returnnum1 } sum2(1,2)//结果:3 /** * 输入输出参数 * 通过输入输出参数可以在函数内部修改函数外部的变量(注意调用时不能是常量或字面量) * 注意:下面的swap仅仅为了演示,实际使用时请用swift的全局函数swap */ funcswap(inouta:int,inoutb:int){ a=a+b b=a-b a=a-b } vara=1,b=2 swap(&a,&b)//调用时参数加上“&”符号 println("a=\(a),b=\(b)")//结果:"a=2,b=1"

和很多语言一样,swift中的函数本身也可以看做一种类型,既可以作为参数又可以作为返回值。

/** * 函数类型 */ varsum3=sum//自动推断sum3的类型:(int,int)->int,注意不同的函数类型之间不能直接赋值 sum3(1,2)//结果:3 //函数作为返回值 funcfn()->(int,int)->int{ //下面的函数是一个嵌套函数,作用于是在fn函数内部 funcminus(a:int,b:int)->int{ returna-b } returnminus; } varminus=fn() //函数作为参数 funccaculate(num1:int,num2:int,fn:(int,int)->int)->int{ returnfn(num1,num2) } caculate(1,2,sum)//结果:3 caculate(1,2,minus)//结果:-1

闭包

swift中的闭包其实就是一个函数代码块,它和objc中的block及c#、java中的lambda是类似的。闭包的特点就是可以捕获和存储上下文中的常量或者变量的引用,即使这些常量或者变量在原作用域已经被销毁了在代码块中仍然可以使用。事实上前面的全局函数和嵌套函数也是一种闭包,对于全局函数它不会捕获任何常量或者变量,而对于嵌套函数则可以捕获其所在函数的常量或者变量。通常我们说的闭包更多的指的是闭包表达式,也就是没有函数名称的代码块,因此也称为匿名闭包。

在swift中闭包表达式的定义形式如下:

{ ( parameters ) -> returntype in

statements

}

下面通过一个例子看一下如何通过闭包表达式来简化一个函数类型的参数,在下面的例子中闭包的形式也是一而再再而三的被简化,充分说明了swift语法的简洁性:

funcsum(num1:int,num2:int)->int{ returnnum1+num2 } funcminus(num1:int,num2:int)->int{ returnnum1-num2 } funccaculate(num1:int,num2:int,fn:(int,int)->int)->int{ returnfn(num1,num2) } var(a,b)=(1,2) caculate(a,b,sum)//结果:3 caculate(a,b,minus)//结果:-1 //利用闭包表达式简化闭包函数 caculate(a,b, {(num1:int,num2:int)->intin returnnum1-num2 })//结果:-1 //简化形式,根据上下文推断类型并且对于单表达式闭包(只有一个语句)可以隐藏return关键字 caculate(a,b, {num1,num2in num1-num2 })//结果:-1 //再次简化,使用参数名缩写,使用$0...$n代表第n个参数,并且此in关键字也省略了 caculate(a,b, { $0- $1 })//结果:-1

考虑到闭包表达式的可读取性,swift中如果一个函数的最后一个参数是一个函数类型的参数(或者说是闭包表达式),则可以将此参数写在函数括号之后,这种闭包称之为“尾随闭包”。

funcsum(num1:int,num2:int)->int{ returnnum1+num2 } funcminus(num1:int,num2:int)->int{ returnnum1-num2 } funccaculate(num1:int,num2:int,fn:(int,int)->int)->int{ returnfn(num1,num2) } var(a,b)=(1,2) //尾随闭包,最后一个参数是闭包表达式时可以卸载括号之后,同时注意如果这个函数只有一个闭包表达式参数时可以连通括号一块省略 //请注意和函数定义进行区分 caculate(a,b){ $0- $1 }//结果:-1

前面说过闭包之所以称之为“闭包”就是因为其可以捕获一定作用域内的常量或者变量进而闭合并包裹着。

funcadd()->()->int{ vartotal=0 varstep=1 funcfn()->int{ total+=step returntotal } returnfn } //fn捕获了total和step,尽管下面的add()执行完后total和step被释放,但是由于fn捕获了二者的副本,所以fn会随着两个变量的副本一起被存储 vara=add() a()//结果:1 a()//结果:2,说明a中保存了total的副本(否则结果会是1) varb=add() b()//结果:1 ,说明a和b单独保存了total的副本(否则结果会是3) varc=b c()//结果:2,说明闭包是引用类型,换句话说函数是引用类型(否则结果会是1)

swift会自动决定捕获变量或者常量副本的拷贝类型(值拷贝或者引用拷贝)而不需要开发者关心,另外被捕获的变量或者常量的内存管理同样是由swift来管理,当上面的函数a不再使用了那么fn捕获的两个变量也就释放了。

作为一门面向对象语言,类当然是swift中的一等类型。首先通过下面的例子让大家对swift的class有一个简单的印象,在下面的例子中可以看到swift中的属性、方法(包括构造方法和析构方法):

//swift中一个类可以不继承于任何其他基类,那么此类本身就是一个基类 classperson{ //定义属性 varname:string varheight=0.0 //构造器方法,注意如果不编写构造方法默认会自动创建一个无参构造方法 init(name:string){ self.name=name } //定义方法 funcshowmessage(){ println("name=\(name),height=\(height)") } //析构方法,在对象被释放时调用,类似于objc的dealloc,注意此函数没有括号,没有参数,无法直接调用 deinit{ println("deinit...") } } varp=person(name:"kenhin") p.height=172.0 p.showmessage()//结果:name=kenhin,height=172.0 //类是引用类型 varp2=p p2.name="kaoru" println(p.name)//结果:kaoru ifp===p2{//“===”表示等价于,这里不能使用等于“==”(等于用于比较值相等,p和p2是不同的值,只是指向的对象相同) println("p===p2")//p等价于p2,二者指向同一个对象 }

从上面的例子不难看出:

swift中的类不必须继承一个基类(但是objc通常必须继承于nsobject),如果一个类没有继承于任何其他类则这个类也称为“基类”;

swift中的属性定义形式类似于其他语句中的成员变量(或称之为“实例变量”),尽管它有着成员变量没有的特性;

swift中如果开发者没有自己编写构造方法那么默认会提供一个无参数构造方法(否则不会自动生成构造方法);

swift中的析构方法没有括号和参数,并且不支持自行调用;

属性

swift中的属性分为两种:存储属性(用于类、结构体)和计算属性(用于类、结构体、枚举),并且在swift中并不强调成员变量的概念。 无论从概念上还是定义方式上来看存储属性更像其他语言中的成员变量,但是不同的是可以控制读写操作、通过属性监视器来属性的变化以及快速实现懒加载功能。

classaccount{ varbalance:double=0.0 } classperson{ //firstname、lastname、age是存储属性 varfirstname:string varlastname:string letage:int //fullname是一个计算属性,并且由于只定义了get方法,所以是一个只读属性 varfullname:string{ get{ returnfirstname+"."+lastname } set{ letarray=split(newvalue,maxsplit:int.max,allowemptyslices:false,isseparator: {$0=="."}) ifarray.count==2{ firstname=array[0] lastname=array[1] } } //set方法中的newvalue表示即将赋的新值,可以自己设置set中的newvalue变量,如下: // set(myvalue){ // } } //如果fullname只有get则是一个只读属性,只读属性可以简写如下: // var fullname:string{ // return firstname + "." + lastname // } //属性的懒加载,第一次访问才会计算初始值,在swift中懒加载的属性不一定就是对象类型,也可以是基本类型 lazyvaraccount=account() //构造器方法,注意如果不编写构造方法默认会自动创建一个无参构造方法 init(firstname:string,lastname:string,age:int){ self.firstname=firstname self.lastname=lastname self.age=age } //定义方法 funcshowmessage(){ println("name=\(self.fullname)") } } varp=person(firstname:"kenshin",lastname:"cui",age:29) p.fullname="kaoru.sun" p.account.balance=10 p.showmessage()

需要提醒大家的是:

计算属性并不直接存储一个值,而是提供getter来获取一个值,或者利用setter来间接设置其他属性;

lazy属性必须有初始值,必须是变量不能是常量(因为常量在构造完成之前就已经确定了值);

在构造方法之前存储属性必须有值,无论是变量属性(var修饰)还是常量属性(let修饰)这个值既可以在属性创建时指定也可以在构造方法内指定;

从上面的例子中不难区分存储属性和计算属性,计算属性通常会有一个setter、getter方法,如果要监视一个计算属性的变化在setter方法中即可办到(因为在setter方法中可以newvalue或者自定义参数名),但是如果是存储属性就无法通过监视属性的变化过程了,因为在存储属性中是无法定义setter方法的。不过swift为我们提供了另外两个方法来监视属性的变化那就是willset和didset,通常称之为“属性监视器”或“属性观察器”。

classaccount{ //注意设置默认值0.0时监视器不会被调用 varbalance:double=0.0{ willset{ self.balance=2.0 //注意newvalue可以使用自定义值,并且在属性监视器内部调用属性不会引起监视器循环调用,注意此时修改balance的值没有用 println("account.balance willset,newvalue=\(newvalue),value=\(self.balance)") } didset{ self.balance=3.0 //注意oldvalue可以使用自定义值,并且在属性监视器内部调用属性不会引起监视器循环调用,注意此时修改balance的值将作为最终结果 println("account.balance didset,oldvalue=\(oldvalue),value=\(self.balance)") } } } classperson{ varfirstname:string varlastname:string letage:int varfullname:string{ get{ returnfirstname+"."+lastname } set{ //对于计算属性可以直接在set方法中进行属性监视 letarray=split(newvalue,maxsplit:int.max,allowemptyslices:false,isseparator: { $0=="."}) ifarray.count==2{ firstname=array[0] lastname=array[1] } } } lazyvaraccount=account() init(firstname:string,lastname:string,age:int){ self.firstname=firstname self.lastname=lastname self.age=age } //类型属性 staticvarskin:array{ return["yellow","white","black"]; } } varp=person(firstname:"kenshin",lastname:"cui",age:29) p.account.balance=1.0 println("p.account.balance=\(p.account.balance)")//结果:p.account.balance=3.0 forcolorinperson.skin{ println(color) }

和setter方法中的newvalue一样,默认情况下载willset和didset中会有一个newvalue和oldvalue参数表示要设置的新值和已经被修改过的旧值(当然参数名同样可以自定义);

存储属性的默认值设置不会引起属性监视器的调用(另外在构造方法中赋值也不会引起属性监视器调用),只有在外部设置存储属性才会引起属性监视器调用;

存储属性的属性监视器willset、didset内可以直接访问属性,但是在计算属性的get、set方法中不能直接访问计算属性,否则会引起循环调用;

在didset中可以修改属性的值,这个值将作为最终值(在willset中无法修改);

方法

方法就是与某个特定类关联的函数,其用法和前面介绍的函数并无二致,但是和objc相比,objc中的函数必须是c语言,而方法则必须是objc。此外其他语言中方法通常存在于类中,但是swift中的方法除了在类中使用还可以在结构体、枚举中使用。关于普通的方法这里不做过多赘述,用法和前面的函数区别也不大,这里主要看一下构造方法。

classperson{ //定义属性 varname:string varheight:double varage=0 //指定构造器方法,注意如果不编写构造方法默认会自动创建一个无参构造方法 init(name:string,height:double,age:int){ self.name=name self.height=height self.age=age } //便利构造方法,通过调用指定构造方法、提供默认值来简化构造方法实现 convenienceinit(name:string){ self.init(name:name,height:0.0,age:0) } //实例方法 funcmodifyinfowithage(age:int,height:double){ self.age=age self.height=height } //类型方法 classfuncshowclassname(){ println("class name is \"person\"") } //析构方法,在对象被释放时调用,类似于objc的dealloc,注意此函数没有括号,没有参数,无法直接调用 deinit{ println("deinit...") } } //通过便利构造方法创建对象 varp=person(name:"kenshin")

除构造方法、析构方法外的其他方法的参数默认除了第一个参数是局部参数,从第二个参数开始既是局部参数又是外部参数(这种方式和objc的调用方式很类似,当然,可以使用“#”将第一个参数同时声明为外部参数名,也可以使用“_”将其他参数设置为非外部参数名)。但是,对于函数,默认情况下只有默认参数既是局部参数又是外部参数,其他参数都是局部参数。

构造方法的所有参数默认情况下既是外部参数又是局部参数;

swift中的构造方法分为“指定构造方法”和“便利构造方法(convenience)”,指定构造方法是主要的构造方法,负责初始化所有存储属性,而便利构造方法是辅助构造方法,它通过调用指定构造方法并指定默认值的方式来简化多个构造方法的定义,但是在一个类中至少有一个指定构造方法。

下标脚本

下标脚本是一种访问集合的快捷方式,例如:var a:[string],我们经常使用a[0]、a[1]这种方式访问a中的元素,0和1在这里就是一个索引,通过这种方式访问或者设置集合中的元素在swift中称之为“下标脚本”(类似于c#中的索引器)。从定义形式上通过“subscript”关键字来定义一个下标脚本,很像方法的定义,但是在实现上通过getter、setter实现读写又类似于属性。假设用record表示一条记录,其中有多列,下面示例中演示了如何使用下标脚本访问并设置某一列的值。

classrecord{ //定义属性,假设store是record内部的存储结构 varstore:[string:string] init(data:[string:string]){ self.store=data } //下标脚本(注意也可以实现只有getter的只读下标脚本) subscript(index:int)->string{ get{ varkey=sorted(array(self.store.keys))[index] returnself.store[key]! } set{ varkey=sorted(array(self.store.keys))[index] self.store[key]=newvalue//newvalue参数名可以像属性一样重新自定义 } } //下标脚本(重载) subscript(key:string)->string{ get{ returnstore[key]! } set{ store[key]=newvalue } } } varr=record(data:["name":"kenshin","sex":"male"]) println("r[0]=\(r[0])")//结果:r[0]=kenshin r["sex"]="female" println(r[1])//结果:female

继承

和objc一样,swift也是单继承的(可以实现多个协议,此时协议放在后面),子类可以调用父类的属性、方法,重写父类的方法,添加属性监视器,甚至可以将只读属性重写成读写属性。

classperson{ varfirstname:string,lastname:string varage:int=0 varfullname:string{ get{ returnfirstname+" "+lastname } } init(firstname:string,lastname:string){ self.firstname=firstname self.lastname=lastname } funcshowmessage(){ println("name=\(fullname),age=\(age)") } //通过final声明,子类无法重写 finalfuncsayhello(){ println("hello world.") } } classstudent:person{ //重写属性监视器 overridevarfirstname:string{ willset{ println("oldvalue=\(firstname)") } didset{ println("newvalue=\(firstname)") } } varscore:double //子类指定构造方法一定要调用父类构造方法 //并且必须在子类存储属性初始化之后调用父类构造方法 init(firstname:string,lastname:string,score:double){ self.score=score super.init(firstname:firstname,lastname:lastname) } convenienceinit(){ self.init(firstname:"",lastname:"",score:0) } //将只读属性重写成了可写属性 overridevarfullname:string{ get{ returnsuper.fullname; } set{ letarray=split(newvalue,maxsplit:int.max,allowemptyslices:false,isseparator: { $0=="."}) ifarray.count==2{ firstname=array[0] lastname=array[1] } } } //重写方法 overridefuncshowmessage() { println("name=\(fullname),age=\(age),score=\(score)") } } varp=student() p.firstname="kenshin"

在使用objc开发时init构造方法并不安全,首先无法保证init方法只调用一次,其次在init中不能访问属性。但是这些完全依靠文档约定,编译时并不能发现问题,出错检测是被动的。在swift中构造方法(init)有了更为严格的规定:构造方法执行完之前必须保证所有存储属性都有值。这一点不仅在当前类中必须遵循,在整个继承关系中也必须保证,因此就有了如下的规定:

子类的指定构造方法必须调用父类构造方法,并确保调用发生在子类存储属性初始化之后。而且指定构造方法不能调用同一个类中的其他指定构造方法;

便利构造方法必须调用同一个类中的其他指定构造方法(可以是指定构造方法或者便利构造方法),不能直接调用父类构造方法(用以保证最终以指定构造方法结束);

如果父类仅有一个无参构造方法(不管是否包含便利构造方法),子类的构造方法默认就会自动调用父类的无参构造方法(这种情况下可以不用手动调用);

常量属性必须默认指定初始值或者在当前类的构造方法中初始化,不能在子类构造方法中初始化;