Swift面试题总结

作者: yangliangliang | 来源:发表于2022-07-05 15:32 被阅读0次

    1.convenience : 便利,使用convenience修饰的构造函数叫做便利构造函数

    // 便利构造函数通常用在对系统的类进行构造函数的扩充时使用

    便利构造函数的特点

    1.便利构造函数通常都是写在extension里面

    2.便利构造函数init前面需要加convericece

    2.swift中Class和Struct的区别

    struct SRectangle {

    var width = 200

    }

    class CRectangle {

        var width = 200

    }

    虽然两者写法看起来很像,其实区别还是很大的

    1.类属于引用类型,结构体属于值类型

    2.继承: struct不可以继承,class可以继承。

    3.类中的每一个成员变量都必须被初始化,否则编译器会报错,而结构体不需要,编译器会自动帮我们生成init函数,给变量赋一个默认值

    4值类型变量直接包含数据,赋值时也是值拷贝,或者叫深拷贝,所以多个变量的操作不会相互影响。

    5引用类型变量存储的是对数据的引用地址,后者称为对象,赋值时,是将对象的引用地址复制过去,也叫浅拷贝,因此若多个变量指向同一个对象时,操作会相互影响。

    6值类型数据没有引用计数,也就不会因为循环引用导致内存泄漏,而引用类型存在引用计数,需要小心循环引用导致的内存泄漏

    7拷贝时,struct是深拷贝,拷贝的是内容,class则需要选用正确的深浅拷贝类型。

    8因为值类型数据是深拷贝,所以是线程安全的,而引用类型数据则不是

    3.swift之inout

    swift中需要对参数只进行修改,需要用到inout 关键字,调用函数时加&

    通俗的讲,就是使用inout关键字修饰的值,在接下来的方法中可以修改

    4.swift final

    Swift中,final关键字可以在class、func和var前修饰,表示不允许对其修饰的内容进行继承或者重新操作

    final:防止方法、属性、下标被重写。

    finalclassPerson{}

    classProgrammer:Person{}//编译错误

    is:类型检查运算符,用于确定实例是否为某个子类类型。

    classPerson{}

    classProgrammer:Person{}

    classNurse:Person{}

    letpeople = [Programmer(),Nurse()]

    foraPersoninpeople

    ifaPersonisProgrammer

        { 

    print("This person is a dev")

        } 

    elseifaPersonisNurse

        { 

    print("This person is a nurse")

        } 

    }

    5.dynamic的作用就是让swift代码也能有oc中的动态机制,常用的就是KVO。

    使用dynamic关键字标记属性,使属性启用Objc的动态转发功能

    dynamic只用于类,不能用于结构体和枚举,因为它们没有继承机制,而Objc的

    dynamic:指明编译器不会对类成员或者函数的方法进行内联或虚拟化。这意味着对这个成员的访问是使用 Objective-C 运行时进行动态派发的(代替静态调用)。

    classPerson

    {

    //隐式指明含有 "objc" 属性

    //这对依赖于 Objc-C 黑魔法的库或者框架非常有用

    //比如 KVO、KVC、Swizzling

    dynamicvarname:String?

    }

    6.@objc与@objcMembers的区别

    在Swift中,继承自NSObject的类如果有比较多的属性或方法都需要加上@objc的话,会多比较多的代码。那么可以利用@objcMembers减少代码。被@objcMembers修饰的类,会默认为类、子类、类扩展和子类扩展的所有属性和方法都加上@objc。当然如果想让某一个扩展关闭@objc,则可以用@nonobjc进行修饰。

    @objcMembers

    class People: NSObject {

        var name = ""

        var age = 10

        func playGame() {

            print("玩游戏")

        }

    }

    7.swift中与h5的进行交互的方式有哪些

    8.didSet:属性观察者,当值存储到属性后马上调用。

    vardata = [1,2,3]

    didSet

        { 

            tableView.reloadData() 

        } 

    }

    willSet:属性观察者,在值存储到属性之前调用。

    classPerson

    {

    var name:String?

        { 

    willSet(newValue) {print("I've got a new name, it's (newValue)!")}

        } 

    }

    letaPerson =Person()

    aPerson.name ="Jordan"//在赋值之前,打印 "I've got a new name, it's Jordan!"

    9.ios 深拷贝与浅拷贝区别

    浅拷贝:     浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间,当内存销毁的时候,指向这片内存的几个指针需要重新定义才可以使用,要不然会成为野指针。

    浅拷贝就是拷贝指向原来对象的指针,使原对象的引用计数+1,可以理解为创建了一个指向原对象的新指针而已,并没有创建一个全新的对象。

    2、深拷贝:  深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉。

    3、总结:深拷贝就是内容拷贝,浅拷贝就是指针拷贝。本质区别在于:是否开启新的内存地址,是否影响内存地址的引用计数

    1.NSString的copy是浅拷贝,且copy返回的对象是不可变对象;mutableCopy是深拷贝。NSMutableString对象copy与mutableCopy都是深拷贝,且copy返回的对象是不可变对象。

    2.NSArray的copy是浅拷贝,且copy返回的对象是不可变对象;mutableCopy是深拷贝。NSMutableArray对象copy与mutableCopy都是深拷贝,且copy返回的对象是不可变对象。

    三、总结准则

     No1:可变对象的copy和mutableCopy方法都是深拷贝(区别完全深拷贝与单层深拷贝) No2:不可变对象的copy方法是浅拷贝,mutableCopy方法是深拷贝。                         No3:copy方法返回的对象都是不可变对象

    10.UITableView为什么会卡顿及如何优化

    在tableView里的tableViewCell中使用许多图片的时候,而且我们大量使用的是

      xxx.clipsToBounds = YES 把图片变成圆角的时候,我们会发现滑动tableView会卡顿,很不顺畅。

    解决办法:

    1. 让UI出图的时候自带圆角图标。

    1.最常用的就是cell的重用, 注册重用标识符

    如果不重用cell时,每当一个cell显示到屏幕上时,就会重新创建一个新的cell

    如果有很多数据或者滚动cell的时候,就会堆积很多cell。如果重用cell,为cell创建一个ID

    每当需要显示cell 的时候,都会先去缓冲池中寻找可循环利用的cell,如果没有再重新创建cell

    2.避免cell的重新布局

    cell的布局填充等操作 比较耗时,一般创建时就布局好

    如可以将cell单独放到一个自定义类,初始化时就布局好

    3.提前计算并缓存cell的属性及内容

    当我们创建cell的数据源方法时,编译器并不是先创建cell 再定cell的高度

    而是先根据内容一次确定每一个cell的高度,高度确定后,再创建要显示的cell,滚动时,每当cell进入屏幕都会计算高度,提前估算高度告诉编译器,编译器知道高度后,紧接着就会创建cell,这时再调用高度的具体计算方法,这样可以方式浪费时间去计算显示以外的cell

    4.加载网络数据,下载图片,使用异步加载,并缓存

    我们时常会看到这样一个现象,就是加载时整个页面卡住不动,怎么点都没用,仿佛死机了一般。原因是主线程被阻塞了。所以对于网路数据的请求或者图片的加载,我们可以开启多线程,将耗时操作放到子线程中进行,异步化操作。这个或许每个iOS开发者都知道的知识,不必多讲。

    5.缓存行高:

    如果cell高度不动态变化,可设置预估行高来减少计算量,cell行高的计算比较消耗性能

    如果cell高度动态变化,提高cell高度的计算效率之外,对于已经计算出的高度,我们需要进行缓存,对于已经计算过的高度,没有必要进行计算第二次

    6.当我们需要圆角效果时,可以使用一张中间透明图片蒙上去

    2.使用ShadowPath指定layer阴影效果路径

    3.页面滑动的流畅性一般为60FPS,即每秒60帧,也就是说每帧画面更新需要1/60秒。只要在每帧时间内完成图像的显示就会是流畅的状态,这需要CPU和GPU共同完成,如果其中一方较为耗时就会导致位图生成和渲染的总时间超出每帧时长,就会造成掉帧卡顿的现象

    由于是因为CPU和GPU工作耗时导致的卡顿掉帧,所以可以从CPU和GPU两方面来进行优化,减轻两者的耗时工作。

    - CPU:将CPU的工作放置到子线程完成。如对象的创建、调整、销毁;layout布局计算、文本计算;文本的异步绘制、图片编解码。

    - GPU:避免离屏渲染。视图的圆角设置、阴影、蒙版、光栅化都会造成离屏渲染增加GPU工作量,可通过CPU的异步绘制机制来完成这类操作,从而减轻GPU压力

    11.项目中使用的设计模式有哪些

    1.MVC模式

    2.单例模式

    应用场景:在程序运行期间,确保对于一个给定的类只有一个实例存在,这个实例有一个全局唯一的访问点,用于进行资源共享控制。

    #import "Singleton.h"

    @implementation Singleton 

    static Singleton *sharedSingleton = nil;<2>

    + (Singleton *)sharedSingleton{

        static dispatch_once_t once;<3>

        dispatch_once(&once,^{

            sharedSingleton = [[self alloc] init];<4>

            //只需要运行一次就可以满足需求的代码都可以写在这里。

        });

        return sharedSingleton;<5>

    }

    1.声明一个可以新建和获取单个实例对象的方法                                                                2.声明一个static类型的类变量                                                                                            3. 声明一个只执行一次的任务                                                                                          4.调用dispatch_once执行该任务指定的代码块,在该代码块中实例化上文声明的类变量                                                                                                                                            5.返回在整个应用的生命周期中只会被实例化一次的变量以上就是iOS开发中单例模式的机制,

    3.代理模式

    应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。

    4.观察者模式(一般分为:通知和KVO)

    通知(notification)机制:Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。

    12.Swift 的界面之间的传值,详细介绍3种传值方式:

    1正向传值

    let TVC = TwoViewController()

            //传入一个字符串,给第二个控制器

            TVC.TSTR = "第二个控制器"

            //在传入一个颜色对象

            TVC.TColoer = UIColor.blueColor()

    //TwoViewController()首先,我们要创建两个对象,来接受上一个界面传来的值

     varTSTR :String? // 创建一个字符串对象

     var  TColoer :UIColor? //创建一个UIColoer 的颜色对象

    2、 协议的反向传值

    class FViewController: UIViewController,FTVCdelegte{

    let Tvc = FtwoViewController()

    Tvc.delegate_zsj = self

    //实现协议的方法

        //更改主题名字

        func change(title: String) {

            self.title = title

        }

        //更改背景色

        func ChangeColoer(Coloer: UIColor) {

            self.view.backgroundColor = Coloer

        }

        //是否成功

        func ChangSucces(YON: Bool) {

            print(YON)

        }

    第二个控制器的内容是:

    //要创建一个协议

    protocol FTVCdelegte : NSObjectProtocol{

        //在协议里面,声明许多方法

        // 第一个,改变标题

        func change(title:String)

        //第二个,改变背景色

        func ChangeColoer (Coloer:UIColor)

        //是否成功的标志

        func ChangSucces(YON:Bool)

    }

    class FtwoViewController: UIViewController {    // 创建一个准守协议的对象    var delegate_zsj :FTVCdelegte?

    //返回,事件的触发 func Click(){

    delegate_zsj?.change("首页")

    delegate_zsj?.ChangeColoer(UIColor.redColor())

    delegate_zsj?.ChangSucces(true) self.navigationController?.popToRootViewControllerAnimated(true) } 

    3、闭包(Block)反向传值

    let BBTC = BBTViewController()

            BBTC.bbchange = { (title:String,coloer:UIColor) in

                self.title = title

                self.view.backgroundColor = coloer

            }

    第二个控制器是:

    class BBTViewController: UIViewController {

     //定义一个闭包,带有两个参数

     var bbchange :((title:String,coloer:UIColor)->Void)?

    func bClick(){

            bbchange?(title:"成龙",coloer:UIColor.redColor())

            self.navigationController?.popToRootViewControllerAnimated(true)

    13.Currying 也是 Swift 的众多先进特性之一,用一句话说就是将接受多个参数的函数,转变成每次之接受一个参数的调用序列。

    Currying 也是 Swift 的众多先进特性之一,用一句话说就是将接受多个参数的函数,转变成每次之接受一个参数的调用序列。

    14.闭包

    闭包关键字in,用来表示下面将是闭包函数体

    {

    (参数列表) -> 返回值类型 in 函数体代码

    }

    // 例var  fun ={

    (v1 : Int, v2 : Int) -> Int in 

    return v1 + v2

    }

    // 闭包表达式调用的时候 不用写参数名 fun (10, 20 )

    //上述代码可直接写成(小括号意思是执行闭包)

    {

    (v1 : Int, v2 : Int) -> Int in return v1 + v2

    }(10, 20)

    1.swift可以推断参数类型和返回值类型,因此可以不写类型。

    2.单表达式可以不用写return.

    3.swift中可以简写实际参数名字,可以用$0,$1表示。

    4.如果闭包表达式是函数的唯一一个参数,小括号也可以省略了。

    var str = {arg1 , arg2 -> String  in return arg1 + arg2

     }("str1", "str2")

    为什么能省略参数类型?那是因为,swift的类型推导,根据后面括号的传参能自动判断参数的类型。

    5.然后我们可以省略闭包中的返回值类型

    var str : String = {arg1, arg2 in return arg1 + arg2 

    }("str1", "str2")

    注意,闭包省略了返回值类型后,变量要显示声明它的类型(注意是加粗的), 之所以能省略返回值类型,那也是因为swift类型推导,先知道了变量的类型,所以可以省略返回值类型。

    还不够爽,我们可以把参数也省略了,如果闭包中只有一行代码, 其实return 也能省略。

    var str: String={

            return $0 + $1

    }("str1","str2")

    var str : String= { $0 + $1                                                                                                         }("str1", "str2")

    如果闭包没有定义参数 ,像这样

    var str : String={ return  "str1" }()

    括号中根本没有传参数, 括号能不能省略呢?

    可以把括号省略了

    省略括号的同时等号也不能写

    var str  : String{

    return   "str1str2"  

    }

    3.尾随闭包:如果函数的最后一个参数是闭包,则闭包可以写在形参小括号的外面。为了增强数的可读性。

    4.非逃逸闭包、逃逸闭包 escaping

    1.非逃逸闭包、逃逸闭包是当闭包作为一个实际参数传递给一个函数。

    2.非逃逸闭包:闭包调用发生在函数结束之前,闭包调用在函数作用域内。

    3.逃逸闭包:闭包有可能在函数结束后调用,调用逃离了函数的作用域。需要用escaping来修饰闭包是允许逃逸的。多用于网络请求。

    // 非逃逸闭包 func test(_ fn : () -> () ){

    // 在函数作用域里面调用了这个闭包

    fn() 

     }

    test{  print(1) }

    15.typealias 给已有类型重新定义名称,方便代码阅读

     //eg:1.已有类型的重新命名     typealias  Address = CGPoint   

      let point: CGPoint = CGPoint(x: 0,y: 0)     

    //等价于     let point: Address = CGPoint(x: 0,y: 0)

    相关文章

      网友评论

        本文标题:Swift面试题总结

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