美文网首页
2024 iOS 面试题

2024 iOS 面试题

作者: 小y想吃糖 | 来源:发表于2024-01-29 15:48 被阅读0次

    一、说一下对自动释放池的理解

    ARC中,主线程默认开启一个runloop,runloop自动创建一个autoreleasepool,autorelease对象会自动被加入autoreleasepool中,一次runloop后清空自动释放池,用__autoreleasing修饰符修饰,或类方法创建会自动加入autoreleasepool中,添加到最近的autoreleasepool中。autoreleasepool是被一个栈区管理,当需要释放一个autoreleasepool时,会根据其中的哨兵对象(一个nil指针),对这个对象所在的autoreleasepool中所有对象进行引用计数-1,清空所有对象的内存地址,然后next指针会指向一个合适的位置,next根据表示begin()和end()来约束autoreleasepool是否存满对象,是否需要创建新的autoreleasepool存储新的autorelease对象。

    二、说一下都有那些修饰词并阐述作用

    nonatomic、atomic、assign、weak、retain、strong、copy、readonly、readwrite

    nonatomic、atomic区别:

    atomic:设置成员变量的@property属性时,默认是atomic,提供多线程安全,但不是绝对安全。

    nonatomic:禁止多线程,变量保护,提高安全性。

    atomic是OC使用的一种线程保护技术,防止在一个线程上的任务未完成时,被其他线程读取,造成数据错误,这种机制是耗费资源的。一般不需要多线程间通讯编程,可以使用nonatomic。

    Assign、 weak的区别

    引用计数都不会➕1。

    assign 修饰基本数据类型,例如int float,在MRC环境下不会自动置为nil。

    weak 修饰的变量会在栈中自动清空,赋值为nil。

    eg:delegate在MRC环境下,用assign修饰,是为了不造成循环引用,需要在-dealloc方法中手动设置self.delegate=nil;以免造成野指针。而在ARC环境下,直接用weak修饰就可以了。

    原理:一个对象,所有指向它的weak指针都存放在一个hash表中,key是对象所占内存地址,value是一个weak指针数组,当对象被销毁的时候,这个数组中的所有weak指针都会指向nil。

    retain、strong、copy的区别

    retain 是MRC下使用的修饰词,在ARC下可用strong替代。

    当修饰不可变对象时,三者的作用是一样的,对象不会跟着原本的引用对象的改变而改变,因为是浅拷贝的指针,当源数值发生改变,就会产生新的指针地址,不会影响浅拷贝的指针。

    当修饰可变对象的时候,retain和strong是一样的,对象会跟着原本引用对象的数值改变而改变,因为原对象是可变的,不会出现新的内存和指针。而copy的对象不会跟着改变,因为copy是深拷贝的新的内容,开辟了新的存储空间和指针。

    readonly、readwrite的区别

    readonly 这个属性只生成了getter方法,没有setter方法,只能读。

    readwrite 这个属性getter setter方法都有,可读可改。

    三、ARC和MRC的区别

    ARC下,内存交给系统自动管理,系统会在编译的时候自动插入retain/release,而MRC下,需要手动管理对象的引用计数,需要手动发送release或者autorelease消息。

    四、简述category和extension的区别

    category 运行时决定,只能添加方法,不能添加实例变量(可以添加属性)。

    extension 编译时决定,不仅可以添加方法,还可以添加实例变量和属性,默认是私有的。但是extension没有独立的实现部分。

    category细聊:

    1)分类的方法名如果和原类中方法名冲突,优先分类。

    2)Category的方法可以被子类继承。

    3)利用runtime倒序遍历方法列表,可以调用被覆盖的原类的方法。

    4)一个类的两个category,如果存在方法名相同的情况,根据buildPhases->Compile Sources里面的从上至下顺序编译情况去调用,即后编译的会被直接调用。

    5)扩展一个类的方式,category比继承要好,继承需要定义子类,类目不需要定义子类来增加现有类的方法,且不会影响其他类和原有类的关系。

    五、说一下对内存管理的理解

    内存管理机制

    1、引用计数:当我们创建一个对象时,其引用计数会➕1,系统会分配内存给到这个对象。每次引用这个对象,它的引用计数都会➕1。当指针不再指向这个对象,那么它的引用计数会➖1,直到引用计数为0,它的内存会释放掉,对象被销毁。在ARC环境下,对象会被系统释放,不需要手动释放,避免了内存泄漏和野指针的出现。

    2、循环引用:当几个对象互相强引用时,会造成内存泄漏,导致程序崩溃。例如,delegate如果用strong修饰,那么就会造成两个类之间的互相强引用,此时引入弱引用概念,即用weak修饰delegate,不会使delegate的引用计数➕1,当delegate对象销毁时,指针会指向nil。

    3、内存泄漏:没有及时释放掉内存,例如计时器没有释放。

    实际开发中如何避免内存泄漏

    1、防止循环引用,使用weak弱引用,例如修饰delegate。

    2、及时释放对象内存,例如创建计时器NSTimer,在计时结束的时候,一定要调用invalidate方法,将timer置为nil。

    3、使用懒加载的方式创建对象,在使用对象的时候再进行实例化,还可以同时加入一些处理逻辑,这样利于内存管理。

    六、循环引用

    举例:

    1、UIViewController中创建UITableView,当实例一个tableView时,控制器的view对tableView进行了强引用,此时tableView的delegate又引用了控制器,这就形成了循环引用,此时需要用weak去修饰delegate。

    2、block内部调用了外部变量(控制器本身或者控制器的属性)

    此时需要用__weak typeof(self) weakSelf = self;来进行对控制器的弱引用,然后在block内部去调用。

    3、关于定时器

    使用定时器时,要主动调用invalidated,一旦和控制器形成循环引用,控制器不会被自动销毁,不会调用dealloc方法,如果invalidated放在dealloc中,是不会被执行的。那么我们可以通过拦截点击返回按钮的方法,在该方法中主动调用invalidated方法,或者写在ViewWillDisappear方法中。请记住,创建一个NSTimer对象,会形成一个runloop,不止控制器会对该timer持有,iOS系统也会对其强引用,所以不要以为设置timer=nil就可以释放timer了,只有调用invalidated才会让iOS系统释放对timer的强引用。所以,[timer invalidated];timer=nil;要同时书写,这是一种良好的代码习惯和规范。

    七、关于block

    1、block是一个OC的对象,它封装了一段代码,这段代码可以在任何时候执行。Block可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。可以嵌套定义,可以定义在方法内部和外部。

    2、block的循环引用:在使用block的地方,self.block,self持有了block,在block代码块里处理逻辑,如果用到了self.xx,那么block又会持有self,这样当你离开界面的时候,并不会走self的dealloc析构函数,self不会释放,这是因为self和block的循环引用,导致了内存泄漏。为了避免这种情况,要使用__weak 在block外面修饰一下self,在block代码块里使用weakself处理逻辑,weakself只是指向了self,它在一个弱引用表里,self的引用计数没有+1。

    3、按照上面的方式调用self的属性,有可能会失败,原因是,weakself会因为析构函数被释放,那么weakself.xx就会是nil。为了避免这种情况,在block代码块里,需要用__strong修饰一下weakself,那么相当于一个临时的strong指针指向了self,当逻辑处理完毕,strong销毁,不会产生内存泄漏问题。

    八、

    相关文章

      网友评论

          本文标题:2024 iOS 面试题

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