iOS面试题集锦

396 阅读8分钟

1.@property的本质是什么?

@property即是属性,本质上是由实例变量+setter+getter组成。

2. +load+initialize的区别是什么?

initialize方法会在该类收到第一个消息时被调用,只会调用一次。如果类只是被引用而没有被调用,initialize方法不会被调用。Category重写了initialize方法,类第一次被调用的时候只会调用Categoryinitialize。子类第一次被调用的时候会先调用父类Categoryinitialize,如果Category没有实现initialize方法,会调用父类的initialize,然后调用自身的initialize

load方法是在加载类的时候被调用,也就是iOS应用启动的时候就会加载所有的类,就会调用每个类的load方法。load方法会在main方法之前被调用。

  1. loadinitialize方法都是在类实例化之前调用,区别是load会在main之前调用。这两个方法都是自动调用的。
  2. loadinitialize方法都不用显示的调用父类的方法。即使子类没有initialize方法,也会自动调用父类的方法,但是load方法不会。
  3. loadinitialize是线程安全的,实现时应该尽可能的简单,避免阻塞线程。
  4. load通常用来实现Method Swizzleinitialize方法初始化全局变量和静态变量。

3.framebounds的区别?

  • frame是相对于父视图坐标系的位置和大小。bounds是相对于自身坐标系下的位置和大小
  • frame是以父视图的左上角为原点。bounds是以自身的左上角为原点。

4.简述iOS的runtime。

OC是一门动态语音,会将一些决定工作从编译期推迟到运行期,其不止需要依赖编译器,还需要依赖运行时环境。Runtime是一套由CC++汇编实现的API,其由ClassMeta ClassInstanceClass Instance组成,是一套完整的面向对象的数据结构。RuntimeOC提供运行时环境。OC语言在编译期都会被编译成Runtime中的结构体。

5.category为什么不能添加成员变量?

在运行期,类的内存布局已经确定,如果添加成员变量就会破坏类的内存布局,这对编译型语言来说是灾难性的。

6.category为什么可以添加属性?

所有的OC类在runtime层都是struct。在runtime层,category是结构体category_t。下面是category_t的源码:

typedef struct category_t {
   const char *name;
   classref_t cls;
   struct method_list_t *instanceMethods;
   struct method_list_t *classMethods;
   struct protocol_list_t *protocols;
   struct property_list_t *instanceProperties;
} category_t;
  • name代表类的名字。
  • cls代表类对象,编译期间是不会定义的,而是在runtime阶段通过name对应到类对象。
  • instanceMethods给类添加的实例方法列表。
  • classMethods给类添加的类方法列表。
  • protocols给类实现的所有协议的列表。
  • instanceProperties给类添加的属性列表。这就是我们可以通过objc_setAssociatedObjectobjc_getAssociatedObject增加属性的原因,不过这个和一般的属性是不一样的。不一样的地方是没有对应的成员变量。

runtime的方法添加属性是通过对象关联来完成的。所有的关联对象都是由AssociationsManager管理。AssociationsManager里由一个静态的AssociationsHashMap来存储所有的关联对象。这相当于把所有的对象的关联对象都放进一个全局的map里面,map的key就是这个对象的指针地址。

7.ExtensionCategory的区别?

  • Extension可以给类添加成员变量,Category不可以。
  • Extension添加的类方法和实例方法必须在类的实现文件中实现,所有必须要拿到类的源码,而Category则不需要。
  • Extension在编译期和类的头文件和类的实现文件组成一个完成的类。伴随着类的产生而产生,类的消亡而消亡。
  • Category是在运行时动态的给类添加方法和属性等。

8.iOS产生内存泄漏的原因?

  1. NSTimer产生循环引用,导致内存泄漏。一般我们在controller中这样使用:
 [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];

因为target:self,NSTimercontroller产生了强引用,controllerNSTimer也是强引用,形成循环引用。在适当的时机调用[timer invalidate]就可以了。

注意:不要在dealloc中调用[timer invalidate];,因为在NSTimer没有释放的情况下,dealloc是不会被调用的。

  1. delegate引起的循环引用导致内存泄漏。解决办法是用weak修饰delegate。
  1. block循环引用。解决方案是在block使用弱引用,在block内部采用强引用(防止对象被提前释放)。
  2. 非OC语言不是由ARC来管理内存,iOS中存在一些非OC语言需要手动释放,可能会造成内存泄漏。
  3. 大次数循环内存暴涨造成内存泄漏。如下代码会导致内存暴涨:
for (int i = 0; i < 100000; i++) {

        NSString *string = @"Abc";

        string = [string lowercaseString];

        string = [string stringByAppendingString:@"xyz"];

        NSLog(@"%@", string);

}

在循环内产生大量临时对象,直到循环结束才释放,造成内存泄漏。 解决办法

for (int i = 0; i < 100000; i++) {

        @autoreleasepool {

            NSString *string = @"Abc";

            string = [string lowercaseString];

            string = [string stringByAppendingString:@"xyz"];

            NSLog(@"%@", string);

        }

    }

9.UIViewCALayer的关系?

每一个UIView都有一个CALayer的实例属性。UIView的职责就是创建和管理这个CALayer,以确保子视图在层级关系中添加或者被移除的时候,他们关联的图层在关系树中也有相同的操作。CALayer才是真正用来在屏幕上显示和做动画的,UIView是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口。

10.UIViewCALayer的区别?

  1. UIView可以接受并处理事件,CALayer是不可以的。
  2. CALayer默认修改属性支持隐式动画,UIView不支持。
  3. CALayer可以修改阴影,边框颜色,3D变换等属性。

11. 栈和堆得区别?

  1. 栈由系统自动分配。堆需要程序员申请,并由程序员进行释放。
  2. 栈是一块连续的内存区域,栈的大小是固定的。堆是不连续的内存区域,堆得大小受限于计算机系统中有效的虚拟内存。
  3. 栈由系统自动分配,速度快,程序员无法控制。堆由程序员分配,速度慢,容易产生碎片。
  4. 堆都是动态分配的。栈分为动态分配和静态分配,栈的静态分配是有编译器完成的,栈的动态分配是由编译器进行释放,无需手工分配。

12. Block的本质是什么?

Block本质上是一个封装了函数调用和函数调用环境的OC对象。

13. NSTimerUIScrollView滑动时为什么会失效?怎么解决?

NSTimer创建之后需要加到Runloop中才可以运行,此时RunloopmodeNSDefaultRunLoopMode。当页面滑动时,Runloop切换为UITrackingRunLoopMode,此时NSTimer就不能运行了。

解决办法:

  1. Runloopmode改为NSRunLoopCommonModes
  2. NSTimer加到子线程的Runloop中,在主线程中更新UI

14. ControllerA跳转到ControllerB,ControllerAviewDidDisappearControllerBviewDidLoad和那个先执行?

ControllerBviewDidLoad先执行。

15.简述OC的内存管理。

OC采用引用计数的方式来管理对象的生命周期。当创建一个新对象时,它的引用计数为1,当有一个新的指针指向这个对象时,引用计数加1,当某个指针不再指向这个对象时,对象的引用计数减1,当对象的引用计数为0时,说明这个对象不被任何指针指向了,这个时候销毁对象,回收内存。

16.什么是ARC?

ARC是苹果推出的自动管理引用计数的机制。ARC的原理是依赖编译器的静态分析能力,通过在编译时合理的插入引用计数管理代码,帮助程序员管理内存。

17.property修饰符有哪些?

  • 访问权限修饰符:readwrite | readonly
  • gettersetter修饰符指定getset方法名。
  • 内存管理修饰符:assign, strong, copy, weak
  • 原子性修饰符:atomic, nonatomic

18.assignweak的区别?

assign表示直接赋值,用于基本数据类型和C数据类型,这个修饰符不会影响引用计数。如果assign用了修饰对象,当对象被释放时不会被置为nil,会产生野指针。

weak表示弱引用,不会持有改对象,所以对象的引用计数不会增加。weak引用的对象被释放时被自动置为nilweak不能修饰基本数据类型。

19.OC内存分区。

  • 栈区:存放函数的参数,局部变量,基本数据。
  • 堆区:存放OC对象。
  • 静态区:全局变量和静态变量
  • 常量区:常亮字符
  • 代码区:存放代码的二进制文件