2020 iOS 面试题

面了20多家总结出来的部分iOS面试题(四)

2020-08-07  本文已影响0人  JoeyM

23. 有没有使用过performSelector?

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    Person *p = [[Person alloc] init];

    // 默认person,没有实现eat方法,可以通过performSelector调用,但是会报错。
    // 动态添加方法就不会报错
    [p performSelector:@selector(eat)];
}

@end


@implementation Person

// **这里真是奇葩, 实在想不到什么时候才有这种使用场景, 我再外面找不到方法, 我再当前类里面直接在写一个方法就好咯,干嘛要在这里写这个玩意, 还要写一个C语言的东西, 既然面试想问, 那咱就要会!**

// void(*)()
// 默认方法都有两个隐式参数,
void eat(id self,SEL sel)
{
    NSLog(@"%@ %@",self,NSStringFromSelector(sel));
}

// 当一个对象调用未实现的方法,会调用这个方法处理,并且会把对应的方法列表传过来.
// 刚好可以用来判断,未实现的方法是不是我们想要动态添加的方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(eat)) {
        // 动态添加eat方法

        // 第一个参数:给哪个类添加方法
        // 第二个参数:添加方法的方法编号
        // 第三个参数:添加方法的函数实现(函数地址)
        // 第四个参数:函数的类型,(返回值+参数类型) v:void @:对象->self :表示SEL->_cmd
        class_addMethod(self, @selector(eat), eat, "v@:");
    }
    return [super resolveInstanceMethod:sel];
}
@end
// 延时操作 和GCD的after 一个效果
[p performSelector:@selector(eat) withObject:nil afterDelay:4];
[[NSRunLoop currentRunLoop] run];
// 完整调用
 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        //  [[NSRunLoop currentRunLoop] run]; 放在上面执行时不可以的,因为当前只是开启了runloop 里面没有任何事件(source,timer,observer)也是开启失败的
         [self performSelector:@selector(test) withObject:nil afterDelay:2];
         [[NSRunLoop currentRunLoop] run];
});

// 由此我自行又做了一个测试, 把        
[self performSelector:@selector(test)];
在子线程调用,是没有任何问题的。

// 我又测试了一下,
 [self performSelector:@selector(test) withObject:nil afterDelay:2];
 这个方法在主线程执行  打印线程是1

在子线程中调用打印线程 非1

引申 NSTimer在子线程执行?

引申 为什么说NSTimer不准确?

// 在子线程中开启NStimer,或者更改当前Runloop的Mode 为NSRunLoopCommonModes
[[NSRunLoop mainRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];

// 利用CADisplayLink (iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高)
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(logInfo)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

// 利用GCD
NSTimeInterval interval = 1.0;
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{
    NSLog(@"GCD timer test");
});
dispatch_resume(_timer);

24. 为什么AFN3.0中需要设置self.operationQueue.maxConcurrentOperationCount = 1;而AF2.0却不需要?

AFNetworking 2.0 和3.0 的区别?

2.x版本常驻线程的分析

3.x版本不在常驻线程的分析?

25. autoreleasePool 在何时被释放?

//来自Apple文档,见参考
NSArray *urls = <# An array of file URLs #>;
for (NSURL *url in urls) { 
  @autoreleasepool { 
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL:urlencoding:NSUTF8StringEncoding error:&error]; 
}

// 如果循环次数非常多,而且循环体里面的对象都是临时创建使用的,就可以用@autoreleasepool 包起来,让每次循环结束时,可以及时释放临时对象的内存

// for 和 for in 里面是没有自动包装@autoreleasepool着的,而下面的方法是由@autoreleasepool自动包围的
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 这里被一个局部@autoreleasepool包围着
}];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString* str = [[[NSString alloc] initWithString:@"666"] autorelease];
[pool drain];

// 其作用于为drain 和 init 之间

子线程中的autorelease变量什么时候释放?

autoreleasepool是如何实现的?

26. iOS界面渲染机制? [这是很大的一个模块,里面牵扯很多东西, 耐心看下去]

首先iOS渲染视图的核心是Core Animation,其渲染层次依次为:图层树->呈现树->渲染树

程序卡顿的原因?

这里会出现一个面试题!!!
题目如下:

在科普一下
1.Core Animation
Core Animation 在 RunLoop 中注册了一个 Observer,监听了 BeforeWaiting 和 Exit 事件。这个 Observer 的优先级是 2000000,低于常见的其他 Observer。当一个触摸事件到来时,RunLoop 被唤醒,App 中的代码会执行一些操作,比如创建和调整视图层级、设置 UIView 的 frame、修改 CALayer 的透明度、为视图添加一个动画;这些操作最终都会被 CALayer 捕获,并通过 CATransaction 提交到一个中间状态去(CATransaction 的文档略有提到这些内容,但并不完整)。当上面所有操作结束后,RunLoop 即将进入休眠(或者退出)时,关注该事件的 Observer 都会得到通知。这时 CA 注册的那个 Observer 就会在回调中,把所有的中间状态合并提交到 GPU 去显示;如果此处有动画,CA 会通过 DisplayLink 等机制多次触发相关流程。

2.CPU渲染职能

3.GPU渲染职能
GPU会根据生成的前后帧缓存数据,根据实际情况进行合成,其中造成GPU渲染负担的一般是:离屏渲染,图层混合,延迟加载。

这里又会出现一个面试题!!!
一个UIImageView添加到视图上以后,内部如何渲染到手机上的?

图片显示分为三个步骤: 加载、解码、渲染
通常,我们程序员的操作只是加载,至于解码和渲染是由UIKit内部进行的。
例如:UIImageView显示在屏幕上的时候需要UIImage对象进行数据源的赋值。而UIImage持有的数据是未解码的压缩数据,当赋值的时候,图像数据会被解码变成RGB颜色数据,最终渲染到屏幕上。


看完上面的又来问题了!
关于UITableView优化的问题?(真他妈子子孙孙无穷尽也~)
先说造成UITableView滚动时候卡顿的的原因有哪些?


在说关于UITableView的优化问题!

基础的


进阶的

高阶的

至于上面的那些基础的,涉及到渲染级别的自己说的时候悠着点,面试官如果想搞你的话,考一考你最上面的那些,CUP和GUP,以及openGL相关, 在考一下你进程通信IPC,以及VSync信号啥的, 这些东西太鸡儿高深了,没点匠心 这东西还真搞不了,要想研究可以看看YYKit的作者写的一篇关于页面流畅的文章:https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/

卡顿检测的方法


继续

既然都是图形绘制了,那就再研究一下事件响应链&原理

传统的问法来了:UIView和CALayer的区别?
通常我们这样回答:UIView可以响应用户事件,而CALayer不能处理事件


回答这个之前, 先回顾一下另外一个经典面试题:事件响应链和事件传递?

基本概念:

// 此方法返回的View是本次点击事件需要的最佳View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

// 判断一个点是否落在范围内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

事件传递给控件之后, 就会调用hitTest:withEvent方法去寻找更合适的View,如果当前View存在子控件,则在子控件继续调用hitTest:withEvent方法判断是否是合适的View, 如果还不是就一直遍历寻找, 找不到的话直接废弃掉。

// 因为所有的视图类都是继承BaseView
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
   // 1.判断当前控件能否接收事件
   if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) return nil;
   // 2. 判断点在不在当前控件
   if ([self pointInside:point withEvent:event] == NO) return nil;
   // 3.从后往前遍历自己的子控件
   NSInteger count = self.subviews.count;
   for (NSInteger i = count - 1; i >= 0; i--) {
      UIView *childView = self.subviews[I];
       // 把当前控件上的坐标系转换成子控件上的坐标系
      CGPoint childP = [self convertPoint:point toView:childView];
      UIView *fitView = [childView hitTest:childP withEvent:event];
       if (fitView) { // 寻找到最合适的view
           return fitView;
       }
   }
   // 循环结束,表示没有比自己更合适的view
   return self;
   
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch  
{  
    if([touch.view isKindOfClass:[XXXXcell class]])  
    {  
        return NO;  
    }  
    return YES;  
}

上一篇 下一篇

猜你喜欢

热点阅读