阅读 132

面试题预习下(25-55)

25._objc_msgForward()是做什么的?直接调用它会发生什么?

  1. _objc_msgForward()是IMP类型,是做消息转发的。当向一个对象发送消息,这个对象并没有实现的时候,_objc_msgForward会尝试做消息转发。
  2. 一旦调用_objc_msgForward的方法,回跳过查找IMP的过程,直接出发消息转发
  3. 如果调用了这个方法,即使这个对象实现了这个方法,也会告诉objc_msgSend,并没有找到这个方法

什么时候会使用_objc_msgForward方法

  • 你想获取某方法所对应的NSInvocation对象
  • JSPatch 就是直接调用_objc_msgForward来实现其核心功能的
  • 同时 RAC(ReactiveCocoa) 源码中也用到了该方法

26.runtime如何实现weak的自动置空

runtime对注册的类,会进行布局,对于weak对象会放入一个hash表中,以weak指向的对象内存地址为key,weak对象为value,当weak所指向的对象retainCount为0时会dealloc,加入weak所指向的对象内存地址为a,那么在hash表中找到所有以a为key的对象,从而设置为nil。

27.能否向编译后得到的类中增加实例变量,能否向运行时动态添加的类中增加成员变量,为什么?

  • 不能向编译后得到的类中增加实例变量
  • 能向运行时动态添加的类中增加实例变量

解释原因:

  • 因为编译后的类注册在Runtime中,类的实例变量链表和实例变量的内存大小都是确定的。
  • 运行时创建的类可以增加实例变量,调用class_addIvar函数。但是必须得在objc_allocateClassPairobjc_registerClassPair之间,原因如上。

28.Runloop和线程有什么关系?

  1. Runloop和线程是一一对应的,Runloop是线程的基础架构部分,有一个全局字典存储了Runloop,以线程为key,对应的Runloop为value。
  2. 主线程的Runloop是默认启动的。
  3. 子线程的Runloop默认不启动,当你获取的时候会自动创建,并存储字典,当线程被销毁的时候,Runloop也会被销毁。
  4. CFRunLoopGetCurrent()来获取当前线程所在的Runloop,CFRunLoopGetMain()来获取主线程的Runloop。

29.Runloop的Mode作用是什么?

model主要是用来指定事件在运行循环中的优先级,分为

  • NSDefaultRunLoopMode,默认,空闲状态
  • UITrackingRunLoopMode,scrollView滑动是
  • UIInitializationRunLoopMode,应用启动时
  • NSRunLoopCommonModes,前两个的集合

苹果公开提供的mode有两个:NSDefaultRunLoopMode和NSRunLoopCommonModes

30.以+scheduledTimerWithTimeInterval的方式触发的Timer,在滑动页面上的列表时,Timer会暂停回调,为什么?

Runloop只能运行在一种mode下,+scheduledTimerWithTimeInterval方法创建的Timer默认会被添加到NSDefaultRunLoopMode中,当UIScrollView滑动时,当前mode被切换成UITrackingRunLoopMode,此时Runloop会停下重启,无法再执行Timer。可以将Timer添加到NSRunLoopCommonModes中解决这个问题。

31.猜想Runloop内部是如何实现的

function loop(){
    do{
        var message = get_next_message;
        process_message(message);
    }while(message != quit);
}
复制代码

32.Objc使用什么机制来进行内存管理

ARC(自动引用计数),通过retainCount来决定对象是否该被释放,每次Runloop的时候,都会检查对象的retainCount,当对象的引用计数为0是,调用dealloc释放该对象。

33.ARC通过什么方式帮助开发者管理内存

  • 在编译器,ARC用更底层的C接口实现retain/release/autorelease,这样做性能更好,这也是为什么在ARC下不能手动管理内存,同是,编译器会忽略一些不必要的操作。
  • 在运行期,做的优化比较复杂,暂时未知

34.不手动指定autoreleasePool的情况下,一个autoRelease对象在什么时候释放?(比如在一个VC的viewDidload中创建)

  • 所有autorelease的对象,在出了作用域后,会被添加到最近创建的自动释放池中,并会在当前的Runloop迭代结束时被释放
  • 从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件和触摸事件
  • viewDidload和viewwillappear属于同一循环,viewdidappear时已经被释放了

35.BAD_ACCESS在什么情况下会出现?

  1. 访问了悬浮指针
  2. 访问一个已经释放了的对象的成员变量,或者发消息
  3. 死循环

36.苹果是如何实现autoReleasePool的?

autorelease是以一个队列数组的形式实现,主要通过下列三个函数完成。

  1. objc_autoreleasepoolPush
  2. objc_autoreleasepoolPop
  3. objc_autorelease

37,使用block什么时候会发生循环引用,如何结局?

一个对象强引用了block,block又强引用了该对象,就会出现循环引用。结局方法是用__block或者__weak修饰后的对象放到block中使用。

38.在block内如何修改外部变量

默认情况下,在block中访问的外部变量是复制过去的,即:写操作不对原变量生效。但是你可以加上 __block 来让其写操作生效。

  • block中无法修改外部变量的原因是此时外部变量指的是栈中指针的内存地址
  • 使用__block所起到的作用就是标记改对象,把该对象的内存地址copy到堆中,然后再对其进行修改
  • 栈是红灯区无法修改,堆是绿灯区

39.使用系统的某些blockAPI时,是否需要考虑循环引用

一般不需要考虑循环引用的问题

40.GCD的队列(dispatch_queue_t)分哪两种类型

  • 串行队列Serial Dispatch Queue
  • 并行队列Concurrent Dispatch Queue

41. 如何用GCD同步若干个异步调用?

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 合并图片
});复制代码

42.dispatch_barrier_async的作用是什么?

在并行队列中,为了保持某些任务的需要,需要等到一些任务完成后才能继续进行下去,使用dispatch_barrier_async来等待之前任务完成,避免数据竞争等问题。它会等待barrier之前的所有操作执行完毕后执行,并且在barrier执行完毕以后,barrier之后的操作才会得到执行。barrier必须要和dispatch_queue_create函数生成的并行队列一起使用,否则就和dispatch_async的作用一模一样。

43.苹果为什么要废弃dispatch_get_current_queue()函数

dispatch_get_current_queue容易造成死锁

44.以下代码执行结果如何

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}复制代码

发生主线程锁死

45.addObserver: forKeyPath: options: context:各个参数的作用分别是什么?observer中需要实现哪个方法才能达到回调

/*
     1.观察者,负责处理监听事件的对象
     2.观察的属性
     3.观察的选项
     4.上下文,用来区分不同的对象
     */
    [person addObserver:self forKeyPath:@"lastName" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];
复制代码

observer需要实现下面这个方法

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    
}
复制代码

46.如何手动出发一个value的KVO

-(void)setName:(NSString *)name
{
//保存旧值
    [self willChangeValueForKey:@"name"];
    _name = name;
//保存新值
    [self didChangeValueForKey:@"name"];
}
//禁用系统的自动触发
+(BOOL)automaticallyNotifiesObserversOfName
{
    return NO;
}
复制代码

47.若一个类有实例变量NSString *_foo,调用setValue:forkey,可以以foo还是_foo为key

都可以,KVC同时支持实例变量和属性

48.KVC的keyPath中的集合运算符如何使用

  • 普通集合运算符有@sum,@count,@max,@min,@avg
  • 格式为@"@sum.age"或者@"集合属性.@sum.age"
  • 必须用在集合对象上或者普通对象的集合属性上

49.KVO和KVC的keyPath一定是属性吗

KVC支持实例变量,KVO可以手动设置支持,默认不支持

50.如何关闭默认的KVO的实现,并进入自定义的KVO实现

答案见第46

51.Apple用什么方式实现对一个对象的KVO

  • 当你注册一个类!A的观察者的时候,这个类会在运行时动态创建一个新类,这个类是A的派生类,命名为NSKVONotifying_A,并进行了isa混写,使被观察对象的isa指针指向这个新类,并重写了被观察属性的set方法如下:

-(void)setName:(NSString *)name
{
    [self willChangeValueForKey:@"name"];
    [super setValue:name forKey:@"name"];
    [self didChangeValueForKey:@"name"];
}复制代码

这种继承和方法注入是在运行时而不是编译时实现的,这就是正确命名如此重要的原因。

  • 这几个方法调用的过程如下:
  1. addObserver: forKeyPath: options: context:
  2. willChangeValueForKey
  3. didChangeValueForKey
  4. didChangeValueForKey中回调observeValueForKeyPath
  • 我们只有在希望能控制回调的调用时机时,才会手动出发KVO,回调的时机就是你调用

didChangeValueForKey方法时。

52.IBOutlet连出来的视图属性为什么被设置为weak

使用storyboard(xib不行)创建的vc,会有一个叫_topLevelObjectsToKeepAliveFromStoryboard的私有数组强引用所有top level的对象,所以这时即便outlet声明成weak也没关系

53.IB中User Defined Runtime Attributes如何使用?

它能够通过KVC的方式配置一些你在interface builder 中不能配置的属性

54.如何调试BAD_ACCESS错误

  1. 重写object的respondsToSelector方法,显示出现EXEC_BAD_ACCESS前访问的最后一个object
  2. Enable Address Sanitizer
  3. Enable Zombie Objects
  4. 设置全局断点快速定位问题代码所在位置

55.lldb(gdb)常用的调试命令?

  1. breakpoint 设置断点定位到某一个函数
  2. n 断点指针下一步
  3. po打印对象

Posted by 微博@iOS程序犭袁,我有自己简单整理了一下。


文章分类
iOS