美文网首页
iOS objc 面试

iOS objc 面试

作者: Mannyao | 来源:发表于2022-03-03 09:56 被阅读0次

    CocoaPods是什么?工作原理是什么?
    答案:
    CocoaPods是用来iOS项目中第三方框架的工具,重要通过建立podfile文件,文件中指定项目所需要的第三方框架,然后使用podinstall 安装框架
    其重要的原理是:pods项目中的第三方框架最终会编译成一个名为libpods.a的文件,主项目依赖这个.a文件即可;cocoapods通过一个pods.xcconfig的文件在编译时设置所有的依赖和参数。对于资源文件, cocoapods提供了一个名为pods-resources.sh的bash脚本,该脚本在每次项目编译的时候都会执行,将第三方的各种资源文件复制到目标目录中;



    如果一个对象释放钱被加到了NotificationCenter中,不在NotificationCenter中remove对象可能会怎么样?
    答案:
    只要添加对象到消息中心进行注册,之后就一定要其remove进行通知注销,将对象添加到消息中心后,消息中心只是保存该对象的地址,消息中心到时候地址发送通知给该对象,但没有取得该对象的强引用,对象的引用计数不会加1,如果对象释放后没有从消息中心remove,也就是通知中心还保存着那个指针,而那个指针的对象可能已经释放销毁了,那么那个指针就成为了一个野指针,当通知发生时,会向这个野指针发送消息导致程序崩溃。



    沙盒机制?
    答案:
    用来保存应用的资源盒数据。应用程序只能访问自己的沙盒目录,而应用程序之间禁止数据的访问和共享。
    1.Document 目录主要用于存储非常大的文件或需要非常频繁更新的数据
    2.library目录
    在library目录下分别有preferences和caches目录。
    preferencesmulu主要用于存放应用程序的设置数据,通常保存应用的设置信息。
    而caches目录下主要方数据缓存文件,适合存储体积大,不要要备份的非重要数据。
    3.temp目录 是应用程序的临时目录,里面的文件随时可能被系统清除。



    iOS应用的生命周期?
    答案:
    参考链接



    kvo和kvc
    答案:
    参考链接



    imagenamed的错误用法
    答案:
    参考链接



    tableview的优化?
    答案:
    参考链接



    block为什么要用copy?
    答案:
    Block在没有使用外部变量时,内存存在全局区,然而,当Block在使用外部变量的时候,内存是存在于栈区,当Block copy之后,是存在堆区的。存在于栈区的特点是对象随时有可能被销毁,一旦销毁在调用的时候,就会造成系统的崩溃。所以Block要用copy关键字。



    你在开发过程中常用到那些定时器,会有误差吗,如果有为什么?
    答案:iOS常用的NSTimer, CADisplaylink, GCD定时器
    其中NSTimer, CADisplaylink基于runloop实现,故存在误差,
    gcdtimer是依赖系统内核,相对比前两者是比较准确的。
    误差的原因是: runloop的机制有关,因为runloop每完成一次循环,才去检查当前的累计的时间是否已经到达设置的时间间隔,如果未到达,则进入下一轮新的任务。
    而等新一轮的任务结束,可能已经超出定时器的时间间隔。所以存在误差



    @proprety的本质是什么?
    答案:@property = ivar(实例变量)+getter + setter(存取方法)
    “属性” (property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)


    如何给 Category 添加属性?
    Objective-C 中的 Category它的主要作用是在不改变原有类的前提下,动态地给这个类添加一些方法。在 Category 中增加属性的目的主要为了解耦,简化框架调用,在很多第三方框架中会使用。

    分类特点:
    • 分类是用于给原有类添加方法的,因为分类的结构体指针中,没有属性列表,只有方法列表。原则上讲它只能添加方法, 不能添加属性(成员变量),实际上可以通过其它方式添加属性 ;
    • 分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量,会编译通过,但是引用变量会报错;
    • 如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法,同名方法调用的优先级为 分类 > 本类 > 父类;
      OC runtime动态绑定变量的方法:
    objc_getAssociatedObject(<#T##object: Any##Any#>, <#T##key: UnsafeRawPointer##UnsafeRawPointer#>)
    
     objc_setAssociatedObject(<#T##object: Any##Any#>, <#T##key: UnsafeRawPointer##UnsafeRawPointer#>, <#T##value: Any?##Any?#>, <#T##policy: objc_AssociationPolicy##objc_AssociationPolicy#>)
    

    *事件传递以及响应机制?
    事件响应:

    1. 如果当前view是控制器的view,那么控制器就是上一个响应者,事件就传递给控制器;如果当前view不是控制器的view,那么父视图就是当前view的上一个响应者,事件就传递给它的父视图
    2. 在视图层次结构的最顶级视图,如果也不能处理收到的事件或消息,则其将事件或消息传递给window对象进行处理
    3. 如果window对象也不处理,则其将事件或消息传递给UIApplication对象
    4. 如果UIApplication也不能处理该事件或消息,则将其丢弃


      image.png

      事件传递:

    5. 比如点击一个view,当发生触摸事件之后,系统会利用runloop将事件添加到UIApplication管理的队列中(即首先受到事件的是UIApplication)
    6. UIApplication会从事件队列中取出最前面的触摸事件分发给应用程序的主窗口keyWindow
    7. keyWinow会在视图层次结构中找到一个最合适的视图来处理触摸事件
      如何找到最合适的视图来出时间:
     override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            super.hitTest(point, with: event)
        }
    
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
            super.point(inside: point, with: event)
        }
    

    UIApplication事件队列->[UIWindow hitTest:withEvent:]->返回更合适的view->[子控件 hitTest:withEvent:]->返回最合适的view
    pointInside:withEvent:方法判断点在不在当前view上(方法调用者的坐标系上)如果返回YES,代表点在方法调用者的坐标系上;返回NO代表点不在方法调用者的坐标系上,那么方法调用者也就不能处理事件。
    事件的传递和响应的区别:
    事件的传递是从上到下(父控件到子控件),事件的响应是从下到上(顺着响应者链条向上传递:子控件到父控件。
    参考链接


    autoreleasepool 什么时候释放?
    App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
    第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是 -2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
    第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
    在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 autoreleasepool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 pool 了。


    iOS编译都做了什么?
    两者都需要通过编译器(Clang + LLVM)把代码编译器生成机器码。


    推送的原理?

    image.png
    1. app向iOS设备发送注册的通知,用户需要同意发送推送
    2. iOS向apns远程推送服务器发送bundleid和设备的udid
    3. apns根据设备的udid和bundleid生成devicetoken返回给app
    4. app再将自己的deviceToken发送自己的服务器,由服务器保存在数据库中
    5. 当需要推送的时候,自己的服务器再根据devicetoken发送给apns
    6. apns在根据devicetoken发送给对应的app

    0C的id对应swift any 还是anyobject?
    答案:是anyobject
    any可以代表任何类型的实例,包含函数类型(包括可选类型),协议
    anyobject 可以代表任何类型的实例,对象类型


    _objc_msgForward函数是做什么的,直接调用它将会发生什么?
    _objc_msgForward是IMP类型,用于消息转发的:当一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward 会尝试做消息转发。
    详解: _objc_msgForward 在进行消息转发的过程中会涉及以下这几个方法:

    1. resolveInstanceMethod: f方法(或 resolveClassMethod:)。
    2. forwardingTargetForSelector:方法
    3. methodSignatureForSelector:方法
    4. forwardInvocation:方法
    5. doesNotRecognizeSelector:方法

    runtime如何实现weak变量的自动置为nil?知道SideTable吗?
    runtime如何实现weak属性具体流程大致可以分为3步:

    1. weak的原理在于底层维护了一张weak_table_t结构的hash表,key是所指对象的地址,value是weak指针的地址数组。
    2. 初始化的时: runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址
    3. 添加引用时: objc_initWeak 函数会调用objc_storeWeak() 函数, objc_stroeWeak()的作用是更新指针指向,创建对应的弱引用表。
    4. weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。
    5. 对象释放时,调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
      SideTable结构体是负责管理类的引用计数和weak表。
      weak 修饰的指针默认值是 nil (在Objective-C中向nil发送消息是安全的)。

    15. Compile Error/ Runtime Crash/ NSLog... ?
    下面的代码会? Compile Error/ Runtime Crash/ NSLog.. ?

    @interface NSObject (Sark)
    + (void)foo;
    - (void)foo;
    @end
    
    @implementation NSObject (Sark)
    - (void)foo {
        NSLog(@"IMP: -[NSObject (Sark) foo]");
    }
    @end
    
    // 测试代码
    [NSObject foo];
    [[NSObject new] performSelector:@selector(foo)];
    
    IMP: -[NSObject(Sark) foo] ,全都正常输出,编译和运行都没有问题。
    

    这道题和上一到题很相似,第二个调用肯定没有问题,第一个调用后会从元类中查找方法,然而方法并不在元类中,所以找元类的superclass。方法定义是在NSObject的Category,由于NSObject的对象模型比较特殊,元类的superclass是类对象,所以从类对象中找到了对象并调用。


    load和initialize的区别
    load:
    当类被引用进项目的时候就会执行load函数(在main函数开始执行之前),与这个类是否被用无关,每个类的load函数只会自动调用一次。由于load函数是系统自动加载的,因此不需要调用父类的load函数,否则父类的load函数会多次执行。
    load 函数的执行顺序:

      1. 当父类和子类都实现load函数时,父类的load方法执行顺序要优于子类。
      1. 当子类未实现load方法时,不会调用父类的load方法。
      1. 类中的load方法执行顺序要优于类别
    • 4.当有多个类别都实现了load方法,这几个load方法都会执行,但执行的顺序不确定(其执行顺序与类别在Compile Sources 中出现的顺序一致)
      1. 当然当有多个不同的类的时候,每个类的load执行顺序与其在Compile Sources 出现的顺序一致
        load使用:
        由于调用load方法时的环境很不安全,我们应该尽量减少load方法的逻辑。另一个原因是load方法是线程安全的,它的内部使用了锁,所以我们应该避免线程阻塞在load方法中。
        load很常见的一个使用场景,交换两个方法的实现。
        initialize:
        在这个类接受第一条消息之前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于这是系统调用,也不需要显式的调用父类的initialize,否则父类的initialize会被多次执行,假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的。
        initalize:
        initialize initialize方法主要用来对一些不方便在编译期初始化的对象进行赋值。比如NSMutableArray这种类型的实例化依赖于runtime的消息发送,所以显然无法在编译器初始化:
    • 1.父类的initailize方法会比子类优先执行
    • 2.当子类不实现initialize方法,会把父类的实现继承过来调用一遍
      1. 当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category的initalize方法)
        参考链接

    什么情况下使用weak关键字,相比assign有什么不同?

    1. 在ARC中,在有可能出现循环引用的时候,往往要通过让其中的一端weak来解决
    2. assign 可以用于非OC对象,而weak必须用于OC对象
      追加问题:如果用assign修饰一个OC对象会怎么样?
      答:assign 和 weak 都是弱引用,对象的内存是存在堆上,而地址指针时存在于栈上的。对于assign修饰的基本数据类型,内存空间存在栈上面由系统统一管理。不需要程序员去管理,而存在于堆上的空间需要程序员去手动管理的,当我们将对象销毁的时候,对象的内存空间释放,存在于栈的指针也会nil,就不会产生野指针了。回到上面问题,如果assign修饰一个对象后,当对象被释放的时候,存在栈上的指针还是存在的。假如此时使用次指针,它就是一个野指针,就容易造成程序崩溃,如果用weak修饰的对象,则不会产生上面的情况,因为对象销毁的时候,系统会将指针置为nil,也就不会产生野指针了。

    怎么用 copy 关键字?
    1.NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
    1.为啥要copy
    下面代码示例

    @property (nonatomic, copy) NSString *name;
    
       NSMutableString *mtaString = [[NSMutableString alloc] initWithString:@"张三"];
        self.name = mtaString;
        [mtaString appendString: @"丰"];
        NSLog(@"name=%@ mtaString: %@", self.name, mtaString);
    

    输出是

    name=张三丰 mtaString: 张三丰
    

    这时候自己的name在自己没有修改情况因为赋值的NSMutableString的改变而修改,造成一些不必要的影响。
    注意的是:下面的代码如果name赋值的NSMutableString则是深copy,如果是NSString这是浅copy。

    - (void)setName:(NSString *)name
    {
        _name = [name copy];
    }
    

    附加问题 @property (nonatomic, copy) NSMutableArray *mutableArray; 这样用copy会有什么问题?
    1、添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃.因为 copy 就是复制一个不可变 NSArray 的对象

    @property (nonatomic, copy) NSMutableArray *mutableArray;
    
    NSMutableArray *array = [NSMutableArray arrayWithObjects:@1,@2,nil];
    self.mutableArray = array;
    [self.mutableArray removeObjectAtIndex:0];
    

    接下来就会崩溃:

    -[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
    

    如何让自己的类用copy功能?

    1. 需声明该类遵从 NSCopying 协议
    2. 实现 NSCopying 协议。该协议只有一个方法:
    - (id)copyWithZone:(NSZone *)zone;
    

    Block相关?
    1.使用block时什么情况会发生循环引用,如何解决?
    一个对象中强引用了block,在block中又强引用了该对象,就会发生循环引用。

    1. 在block内如何修改block外部变量?
      参考链接

    apple 用什么方式实现对一个对象的KVO?如何手动触发kvo
    Apple 使用了 isa 混写(isa-swizzling)来实现 KVO 。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A 的新类,该类继承自对象A的本类,且 KVO 为 NSKVONotifying_A 重写观察属性的 setter 方法,setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象属性值的更改情况。
    如何手动触发kvo?

    //  .m文件
    //  Created by https://github.com/ChenYilong
    //  微博@iOS程序犭袁(http://weibo.com/luohanchenyilong/).
    //  手动触发 value 的KVO,最后两行代码缺一不可。
    
    //@property (nonatomic, strong) NSDate *now;
    - (void)viewDidLoad {
       [super viewDidLoad];
       _now = [NSDate date];
       [self addObserver:self forKeyPath:@"now" options:NSKeyValueObservingOptionNew context:nil];
       NSLog(@"1");
       [self willChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。
       NSLog(@"2");
       [self didChangeValueForKey:@"now"]; // “手动触发self.now的KVO”,必写。
       NSLog(@"4");
    }
    

    autoreleasepool的原理和使用场景?

    • 若干个autoreleasepoolpage组成的双向链表的栈结构, objc_autorelasepoolpush, objc_autoreleasepoolpop,objc_autorelease
    • 使用场景:多次创建临时变量导致内存上涨时,需要延迟释放。
    • autoreleasepoolpage的内存结构:4k储存大小 9778944-e12b88657c490f33.jpg

    离屏渲染?
    如果有时因为一些限制,无法把渲染结果直接写入frame buffer,而是先暂存在另外的内存区域,之后再写入frame buffer,那么这个过程被称之为离屏渲染。也就是GPU需要在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。流程如图:

    173313b3aebc4252.png
    1. OpenGL中,GPU屏幕渲染有以下两种方式当前屏幕渲染(On-Screen Rendering):正常情况下,我们在屏幕上显示都是GPU读取帧缓冲区(Frame Buffer)渲染好的的数据,然后显示在屏幕上。流程如图: 1732f3543028519f.png

    相关文章

      网友评论

          本文标题:iOS objc 面试

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