iOS面试题

作者: LemonFan | 来源:发表于2017-07-16 20:11 被阅读22次

    iOS面试准备

    基础

    1. 为什么说Objective-C是一门动态的语言?

    编译期:即编译器对语言的编译阶段,编译时只是对语言进行最基本的检查报错,包括词法分析、语法分析等等,将程序代码翻译成计算机能够识别的语言(例如汇编等),编译通过并不意味着程序就可以成功运行。

    运行时: 即程序通过了编译这一关之后编译好的代码被装载到内存中跑起来的阶段,这个时候会具体对类型进行检查,而不仅仅是对代码的简单扫描分析,此时若出错程序会崩溃。

    OC语言的动态性主要体现在三个方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading)。

    动态性:即OC的动态类型、动态绑定和动态加载特性,将对象类型的确定、方法调用的确定、代码和资源的装载等推迟到运行时进行,更加灵活;
    多态:多态是面向对象变成语言的特性,OC作为一门面向对象的语言,自然具备这种多态性,多态性指的是来自不同类的对象可以接受同一消息的能力,或者说不同对象以自己的方式响应相同的消息的能力。


    2. 讲一下MVC和MVVM,MVP?

    这三种都是代码设计模式。
    MVC把代码分为三层,Model层,View层,Controller层。
    Model层负责封装数据、存储和处理数据运算等工作
    view层 负责数据展示、监听用户触摸等工作
    Controller层负责业务逻辑、事件响应、数据加工等工作


    3. 为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?

    代理就是一个对象,在使用代理的时候是:obj.delegate = self;
    当self持有了obj对象的时候回造成引用循环。代理用weak可以打破引用循环。

    delegate和dataSource的区别在于,delegate通常在处理交互的事件。dataSource是要获取数据来展示。


    4. 属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?

    属性的实质是实例对象中数据存储的地址。
    包含三部分:(对象的实例变量,包括类型和名字) 获取属性方法 设置属性方法
    @property = ivar + getter + setter;

    @property (nonatomic, copy) NSString *name;
    
    //T@"NSString",C,N,V_name
    //T 类型
    //C copy
    //N nonatomic
    //V 实例变量
    

    @synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。

    @dynamic告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成。(当然对于readonly的属性只需提供getter即可)。假如一个属性被声明为@dynamic var,然后你没有提供@setter方法和@getter方法,编译的时候没问题,但是当程序运行到instance.var =someVar,由于缺setter方法会导致程序崩溃;


    5. 属性的默认关键字是什么?

    对应基本数据类型默认关键字是
    atomic,readwrite,assign
    对于普通的OC对象
    atomic,readwrite,strong


    6. NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)

    NSString *str1 = @"abc"; //如果strongStr是Strong修饰的话 self.strongStr = str1; self.copyStr = str1; str1 = @"zxc"; //self.strongStr -> @"zxc",strongStr的指针是指向str1的内存地址,修改str1的时候,strongStr也会跟着改变,可能造成意外的结果。 //而copy修饰的属性会自动生成新的地址,self.copyStr->@"abc",copyStr把str的值复制到新的地址上。


    7. 如何令自己所写的对象具有拷贝功能?

    遵守NSCopying协议
    实现- (id)copyWithZone:(nullable NSZone *)zone;协议方法

    @implementation Person
    - (id)copyWithZone:(NSZone *)zone
    {
      Person *p = [[[self class] alloc] init]; // <== 注意这里
      p.userId = self.userId;
      return p;
    }
    @end
    
    @implementation Student
    - (id)copyWithZone:(NSZone *)zone
    {
      Student *s = [super copyWithZone:zone];
      s.studentId = self.studentId;
      return s;
    }
    @end
    
    
    

    8. 可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?

    使用copy未必是深拷贝,但使用mutablecopy一定是深拷贝。

    浅拷贝:指针(地址)拷贝,不会产生新对象;深拷贝:内容拷贝,会产生新对象

    当不可变字符串(或不可变数组,不可变集合,不可变字典)调用copy的时候,指针地址没有发生改变,也就意味着没有产生新的对象,所以属于浅拷贝;

    当可变字符串(或可变数组,可变集合,可变字典)调用copy的时候,指针地址发生了改变,也就意味着产生新的对象,所以属于深拷贝;

    当可变字符串(或可变数组,可变集合,可变字典调用mutableCopy的时候,指针地址发生了改变,意味着产生新的对象,所以属于深拷贝。

    当不可变字符串(或不可变数组,不可变集合,不可变字典)调用mutableCopy的时候,指针地址发生了改变,意味着产生新的对象,所以属于深拷贝。

    集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。

    // 属性为copy的时候,等价于下面的代码
    - (void)setName:(NSString *)name {
          if (_name != name) {
             [_name release];
              _name = [name copy];
           }
    }
    

    9. 为什么IBOutlet修饰的UIView也适用weak关键字?

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


    10. nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?

    atomic:默认是有该属性的,这个属性是为了保证程序在多线程情况下,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步问题。
    nonatomic:如果该对象无需考虑多线程的情况,请加入这个属性,这样会让编译器少生成一些互斥加锁代码,可以提高效率。
    在iOS中使用同步锁的开销比较大, 这会带来性能问题。

    如果不加锁的话(或者说使用nonatomic语义),那么当其中一个线程正在改写某属性值的时候,另外一个线程也许会突然闯入,把尚未修改好的属性值读取出来。发证这种情况时,线程读取到属性值可能不对。

    一般iOS程序中,所有属性都声明为nonatomic。这样做的原因是:
    在iOS中使用同步锁的开销比较大, 这会带来性能问题。一般情况下并不要求属性必须是“原子的”,因为这并不能保证“线程安全”(thread safety),若要实现“线程安全”的操作,还需采用更为深层的锁定机制才醒。

    nonatomic的内存管理语义是非原子性的,非原子性的操作本来就是线程不安全,而atomic的操作是原子性的,但并不意味着他就是线程安全的,它会增加正确的几率,能够更好的避免线程错误,但仍旧是不安全的。

    几种线程锁:
    OSSpinLock: 46.15 ms
    dispatch_semaphore: 56.50 ms
    pthread_mutex: 178.28 ms
    NSCondition: 193.38 ms
    NSLock: 175.02 ms
    pthread_mutex(recursive): 172.56 ms
    NSRecursiveLock: 157.44 ms
    NSConditionLock: 490.04 ms
    @synchronized: 371.17 ms

    总的来说:

    OSSpinLock和dispatch_semaphore的效率远远高于其他。

    @synchronized和NSConditionLock效率较差。

    鉴于OSSpinLock的不安全,所以我们在开发中如果考虑性能的话,建议使用dispatch_semaphore。

    如果不考虑性能,只是图个方便的话,那就使用@synchronized。


    11. UICollectionView自定义layout如何实现?


    12. 用StoryBoard开发界面有什么弊端?如何避免?

    目中用到StoryBoard的地方较多谈下想法,首先团队开发中用StoryBoard,如果模块划分的不是那么独立,会有多人共同写一个模块的情况,那最后不要用StoryBoard,应为SVN经常会冲突导致更新下的代码会有问题,可能需要恢复到上导致冲突前一版本才能解决。


    13. 进程和线程的区别?同步异步的区别?

    进程本身不会运行,是线程的容器。程序本身只是指令的集合,进程才是程序(那些指令)的真正运行。若干进程有可能与同一个程序相关系,且每个进程皆可以同步(循序)或不同步(平行)的方式独立运行。进程为现今分时系统的基本运作单位

    进程:每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内

    线程:操作系统能够进行运算调度的最小单位。它被包涵在进程之中,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    1个进程要想执行任务,必须得有线程(每1个进程至少要有1条线程)

    14991779754660.jpg

    14. 线程间通信?

    线程间通信的体现:
    (1 ).一个线程传递数据给另一个线程
    (2).在一个线程中执行完特定任务后,转到另一个线程继续执行任务

    在子线程计算然后 在主线程刷新也是一种线程传递数据

    15. GCD的一些常用的函数?(group,barrier,信号量,线程同步)

    关于GCD链接

    16. 如何使用队列来避免资源抢夺?

    当我们使用多线程来访问同一个数据的时候,就有可能造成数据的不准确性。这个时候我么可以使用线程锁的来来绑定。也是可以使用串行队列来完成。如:fmdb就是使用FMDatabaseQueue,来解决多线程抢夺资源。


    17. 数据持久化的几个方案

    plist文件
    NSUserDefaults
    NSKeyedArchiver
    SQLite 3 (FMDB封装了SQLite3)
    CoreData


    18. 说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?

    – (void)applicationDidFinishLaunching:(UIApplication *)application; 此方法基本已经弃用,改用第2个方法代替。

    – (BOOL)application:(UIApplication )application didFinishLaunchingWithOptions: 当应用程序启动时(不包括已在后台的情况下转到前台),调用此回调。launchOptions是启动参数,假如用户通过点击push通知启动的应用,这个参数里会存储一些push通知的信息。

    – (void)applicationDidBecomeActive:(UIApplication *)application; 当应用程序全新启动,或者在后台转到前台,完全激活时,都会调用这个方法。如果应用程序是以前运行在后台,这时可以选择刷新用户界面。

    – (void)applicationWillResignActive:(UIApplication *)application; 当应用从活动状态主动到非活动状态的应用程序时会调用这个方法。这可导致产生某些类型的临时中断(如传入电话呼叫或SMS消息)。或者当用户退出应用程 序,它开始过渡到的背景状态。使用此方法可以暂停正在进行的任务,禁用定时器,降低OpenGL ES的帧速率。游戏应该使用这种方法来暂停游戏。 调用时机可能有以下几种:锁屏,按HOME键,下接状态栏,双击HOME键弹出低栏,等情况。

    – (BOOL)application:(UIApplication )application handleOpenURL:(NSURL )url; 这个方法已不再支持,可能会在以后某个版本中去掉。建议用下面方法代替

    – (BOOL)application:(UIApplication )application openURL:(NSURL )url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation NS_AVAILABLE_IOS(4_2); 当用户通过其它应用启动本应用时,会回调这个方法,url参数是其它应用调用openURL:方法时传过来的。

    – (void)applicationDidReceiveMemoryWarning:(UIApplication *)application; 当应用可用内存不足时,会调用此方法,在这个方法中,应该尽量去清理可能释放的内存。如果实在不行,可能会被强行退出应用。

    – (void)applicationWillTerminate:(UIApplication *)application; 当应用退出,并且进程即将结束时会调到这个方法,一般很少主动调到,更多是内存不足时是被迫调到的,我们应该在这个方法里做一些数据存储操作。

    -(void)application:(UIApplication )application didRegisterForRemoteNotificationsWithDeviceToken: -(void)application:(UIApplication )application didFailToRegisterForRemoteNotificationsWithError:(NSError )error NS_AVAILABLE_IOS(3_0); 当客户端注册远程通知时,会回调上面两个方法。 如果成功,则回调第一个,客户端把deviceToken取出来发给服务端,push消息的时候要用。 如果失败了,则回调第二个,可以从error参数中看一下失败原因。 注:注册远程通知使用如下方法: UIRemoteNotificationType t = UIRemoteNotificationTypeBadge|UIRemoteNotificationTypeAlert|UIRemoteNotificationTypeSound; [[UIApplication sharedApplication] registerForRemoteNotificationTypes:t];

    – (void)application:(UIApplication )application didReceiveRemoteNotification: 当应用在前台运行中,收到远程通知时,会回调这个方法。 当应用在后台状态时,点击push消息启动应用,也会回调这个方法。

    – (void)application:(UIApplication )application didReceiveLocalNotification:(UILocalNotification )notification NS_AVAILABLE_IOS(4_0); 当应用收到本地通知时会调这个方法,同上面一个方法类似。 如果在前台运行状态直接调用,如果在后台状态,点击通知启动时,也会回调这个方法

    – (void)applicationDidEnterBackground:(UIApplication *)application NS_AVAILABLE_IOS(4_0); 当用户从台前状态转入后台时,调用此方法。使用此方法来释放资源共享,保存用户数据,无效计时器,并储存足够的应用程序状态信息的情况下被终止后,将应用 程序恢复到目前的状态。如果您的应用程序支持后台运行,这种方法被调用,否则调用applicationWillTerminate:用户退出。

    – (void)applicationWillEnterForeground; 当应用在后台状态,将要进行动前台运行状态时,会调用此方法。如果应用不在后台状态,而是直接启动,则不会回调此方法。


    19. NSCache优于NSDictionary的几点?

    NSDictionary *dic = @{
                         @(1) : @(1),
                         @(2) : @(2)
    };
    NSLog(@"%@",dic.allKeys);
    

    注意一点它和NSDictionary区别就是,NSCache 中的key不必实现copy,NSDictionary中的key必须实现copy

    NSCache优于NSDictionary的几点:

    1. 当系统资源将要耗尽时,NSCache具备自动删减缓冲的功能。并且还会先删减“最久未使用”的对象。
    1. NSCache不拷贝键,而是保留键。因为并不是所有的键都遵从拷贝协议(字典的键是必须要支持拷贝协议的,有局限性)。
    2. NSCache是线程安全的:不编写加锁代码的前提下,多个线程可以同时访问NSCache。

    20. 知不知道Designated Initializer?使用它的时候有什么需要注意的问题?

    Designated Initializer 指定初始化器

    1. 每个类的正确初始化过程应当是按照从子类到父类的顺序,依次调用每个类的Designated Initializer。并且用父类的Designated Initializer初始化一个子类对象,也需要遵从这个过程。
    1. 如果子类指定了新的初始化器,那么在这个初始化器内部必须调用父类的Designated Initializer。并且需要重写父类的Designated Initializer,将其指向子类新的初始化器
    - (instancetype)initWithFrame:(CGRect)frame andName:(NSString *)name{ 
      //incorrect 
        if (self = [super init]){ 
            self.name=name; 
        } 
        return self; 
    }
    

    21. 实现description方法能取到什么效果?

    重写该方法。
    - (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level

    在使用NSObject类替换%@占位符时,会调用description相关方法,所以只要实现此方法,就可以起到修改打印内容的作用。因此对于系统的类,可用增加分类的方式实现,而自己的类,就是增加方法。


    22. objc使用什么机制管理对象内存?

    1).MRC(manual retain-release)手动内存管理
    2).ARC(automatic reference counting)自动引用计数


    Block

    block的实质是什么?一共有几种block?都是什么情况下生成的?

    block也是OC的对象
    _NSConcreteStackBlock 保存在栈中的block,出栈时会被销毁
    _NSConcreteGlobalBlock 全局的静态block,不会访问任何外部变量
    _NSConcreteMallocBlock 保存在堆中的block,当引用计数为0时会被销毁
    

    关于block知识


    为什么在默认情况下无法修改被block捕获的变量? __block都做了什么?

    配置在全局的GlobalBlock可以出了作用域还是能继续访问,但是在栈上的StackBlock就废弃了,因此为了出了作用域能继续使用,OC提供了把Block和__block这两个东西从栈上复制到堆上的方法来解决这个问题。而_forwarding其实既可以指向自己,也可以指向复制后的自己,也就是说有了这个指针的存在,无论__block变量配置在堆上还是栈上都能够正确的访问__block变量。


    模拟一下循环引用的一个情况?block实现界面反向传值如何实现?

    self.blockClass = [[LMBlockClass alloc] init];
    
    [self.blockClass testBlock1:^{
      self.string = @"ssss";
    }];
    

    但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:

    __weak __typeof__(self) weakSelf = self;
    dispatch_group_async(_operationsGroup, _operationsQueue, ^
    {
        __typeof__(self) strongSelf = weakSelf;
        [strongSelf doSomething];
        [strongSelf doSomethingElse];
    } );
    
     __weak __typeof__(self) weakSelf = self;
     _observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note) {
         __typeof__(self) strongSelf = weakSelf;
         [strongSelf dismissModalViewControllerAnimated:YES];
     }];
    
    

    Runtime

    objc在向一个对象发送消息时,发生了什么?

    1.消息由接收者,选择子及参数构成。给某对象“发送消息”(invoke a method)也就相当于在该对象上调用方法。

    //1.self是接收者 2.messageName:是选择子 3.选择子和参数合起来称为“消息”
    id returnValue = [self messageName:@"messageValue"];
    

    2.发给某对象的全部消息都要由“动态消息派发系统”来处理,该系统会查出对应的方法,并执行其代码。
    编译器看到消息后,会将上面转换为一条标准的C语言函数调用。
    第一个参数是接收者 第二个参数是选择子 后面的参数就是消息中的参数
    void objc_msgSend(id self,SEL cmd,....);
    id objc_returnValue = objc_msgSend(self,@selector(messageName:),@"messageValue");

    执行过程 objc_msgSend 会根据接收者和选择子的类型来调用适当的方法
    * 1.在接收者所在的类中搜其“方法列表”,找到相符的方法,就跳至其实现代码。
    * 2.找不到就沿着继承体系继续往上找,等找到合适的方法后再跳转;
    * 3.如果最终还是找不到相符的,就执行“消息转发”操作


    什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?

    当对象无法响应该方法和消息转发没有处理该方法时就报该错误。


    能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

    不能向编译后得到的类中增加实例变量;
    能向运行时创建的类中添加实例变量;

    因为编译后的类已经注册在 runtime 中,类结构体中的 objc_ivar_list 实例变量的链表 和 instance_size 实例变量的内存大小已经确定,同时runtime 会调用 class_setIvarLayout 或 class_setWeakIvarLayout 来处理 strong weak 引用。所以不能向存在的类中添加实例变量;

    运行时创建的类是可以添加实例变量,调用 class_addIvar 函数。但是得在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上。


    runtime如何实现weak变量的自动置nil?

    runtime 对注册的类, 会进行布局,对于 weak 对象会放入一个 hash 表中。 用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak 表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。

    我们可以设计一个函数(伪代码)来表示上述机制:

    objc_storeWeak(&a, b)函数:

    objc_storeWeak函数把第二个参数--赋值对象(b)的内存地址作为键值key,将第一个参数--weak修饰的属性变量(a)的内存地址(&a)作为value,注册到 weak 表中。如果第二个参数(b)为0(nil),那么把变量(a)的内存地址(&a)从weak表中删除,

    你可以把objc_storeWeak(&a, b)理解为:objc_storeWeak(value, key),并且当key变nil,将value置nil。

    在b非nil时,a和b指向同一个内存地址,在b变nil时,a变nil。此时向a发送消息不会崩溃:在Objective-C中向nil发送消息是安全的。

    而如果a是由assign修饰的,则: 在b非nil时,a和b指向同一个内存地址,在b变nil时,a还是指向该内存地址,变野指针。此时向a发送消息极易崩溃。


    RunLoop

    runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?

    RunLoop 的概念

    一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:

    function loop() {
        initialize();
        do {
            var message = get_next_message();
            process_message(message);
        } while (message != quit);
    }
    

    RunLoop 实际上就是一个对象,iOS系统提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

    RunLoop这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行;而且在没有事件处理的时候,会进入睡眠模式,从而节省CPU资源,提高程序性能。

    CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。

    NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

    runloop和线程有什么关系

    1. 一条线程对应一个RunLoop对象,每条线程都有唯一一个与之对应的RunLoop对象。
    1. 我们只能在当前线程中操作当前线程的RunLoop,而不能去操作其他线程的RunLoop。
    2. RunLoop对象在第一次获取RunLoop时创建,销毁则是在线程结束的时候。
    3. 主线程的RunLoop对象系统自动帮助我们创建好了(原理如下),而子线程的RunLoop对象需要我们主动创建。

    主线程默认开启了runloop,子线程的runloop默认关闭,第一次获取的时候才创建,有时候用不到浪费资源。runloop有资源才不会销毁,没资源会自定销毁。

    1877784-6ab632fc118e31f3.jpg

    runloop的mode是用来做什么的?有几种mode?

    一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

    CFRunLoopModeRef

    系统默认定义了多种运行模式(CFRunLoopModeRef),如下:

    1. kCFRunLoopDefaultMode:App的默认运行模式,通常主线程是在这个运行模式下运行
    1. UITrackingRunLoopMode:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
    2. UIInitializationRunLoopMode:在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
    3. GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到
    4. kCFRunLoopCommonModes:伪模式,不是一种真正的运行模式(后边会用到)

    其中kCFRunLoopDefaultMode、UITrackingRunLoopMode、kCFRunLoopCommonModes是我们开发中需要用到的模式

    而当我们拖动ScllorView的时候,RunLoop就结束NSDefaultRunLoopMode,切换到了UITrackingRunLoopMode模式下,这个模式下没有添加NSTimer,所以我们的NSTimer就不工作了。

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];,也就是将定时器添加到当前RunLoop的UITrackingRunLoopMode下,你就会发现定时器只会在拖动Text View的模式下工作,而不做操作的时候定时器就不工作

    我们之前说过的伪模式(kCFRunLoopCommonModes),这其实不是一种真实的模式,而是一种标记模式,意思就是可以在打上Common Modes标记的模式下运行。

    1877784-2177aa2828b1ad34.png

    CFRunLoopTimerRef是定时源(RunLoop模型图中提到过),理解为基于时间的触发器,基本上就是NSTimer

    CFRunLoopSourceRef是事件源(RunLoop模型图中提到过),CFRunLoopSourceRef有两种分类方法。
    同时我们可以看到11行中有Sources0,也就是说我们点击事件是属于Sources0函数的,点击事件就是在Sources0中处理的。

    而至于Sources1,则是用来接收、分发系统事件,然后再分发到Sources0中处理的。

    CFRunLoopObserverRef是观察者,用来监听RunLoop的状态改变

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0),               // 即将进入Loop:1
        kCFRunLoopBeforeTimers = (1UL << 1),        // 即将处理Timer:2    
        kCFRunLoopBeforeSources = (1UL << 2),       // 即将处理Source:4
        kCFRunLoopBeforeWaiting = (1UL << 5),       // 即将进入休眠:32
        kCFRunLoopAfterWaiting = (1UL << 6),        // 即将从休眠中唤醒:64
        kCFRunLoopExit = (1UL << 7),                // 即将从Loop中退出:128
        kCFRunLoopAllActivities = 0x0FFFFFFFU       // 监听全部状态改变  
    };
    
    

    苹果是如何实现Autorelease Pool的?

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

    objc_autoreleasepoolPush
    objc_autoreleasepoolPop
    objc_autorelease
    看函数名就可以知道,对 autorelease 分别执行 push,和 pop 操作。销毁对象时执行release操作。

    举例说明:我们都知道用类方法创建的对象都是 Autorelease 的,那么一旦 Person 出了作用域,当在 Person 的 dealloc 方法中打上断点,我们就可以看到这样的调用堆栈信息:

    类结构

    isa指针?(对象的isa,类对象的isa,元类的isa都要说)

    类方法和实例方法有什么区别?

    类方法:
    类方法是属于类对象的
    类方法只能通过类对象调用
    类方法中的self是类对象
    类方法可以调用其他的类方法
    类方法中不能访问成员变量
    类方法中不能直接调用对象方法

    实例方法:
    实例方法是属于实例对象的
    实例方法只能通过实例对象调用
    实例方法中的self是实例对象
    实例方法中可以访问成员变量
    实例方法中直接调用实例方法
    实例方法中也可以调用类方法(通过类名)


    介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?

    运行时能增加成员变量么?能增加属性么?如果能,如何增加?如果不能,为什么?

    objc中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)

    如果 spouse 对象为 nil,那么发送给 nil 的消息 mother 也将返回 nil。 2. 如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double 或者 long long 的整型标量,发送给 nil 的消息将返回0。 2. 如果方法返回值为结构体,发送给 nil 的消息将返回0。结构体中各个字段的值将都是0。 2. 如果方法的返回值不是上述提到的几种情况,那么发送给 nil 的消息的返回值将是未定义的。


    高级

    UITableview的优化方法(缓存高度,异步绘制,减少层级,hide,避免离屏渲染)

    有没有用过运行时,用它都能做什么?(交换方法,创建类,给新创建的类增加方法,改变isa指针)

    看过哪些第三方框架的源码?都是如何实现的?(如果没有,问一下多图下载的设计)

    SDWebImage的缓存策略?

    AFN为什么添加一条常驻线程?

    KVO的使用?实现原理?(为什么要创建子类来实现)

    当你观察一个对象时,一个新的类会被动态创建。这个类继承自该对象的原本的类,并重写了被观察属性的 setter 方法。重写的 setter 方法会负责在调用原 setter 方法之前和之后,通知所有观察对象:值的更改。最后通过 isa 混写(isa-swizzling) 把这个对象的 isa 指针 ( isa 指针告诉 Runtime 系统这个对象的类是什么 ) 指向这个新创建的子类,对象就神奇的变成了新创建的子类的实例。我画了一张示意图,


    687474703a2f2f6936322e74696e797069632e636f6d2f7379353775722e6a7067.png

    KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar)

    KVC 支持实例变量,KVO 只能手动支持手动设定实例变量的KVO实现监听

    相关文章

      网友评论

        本文标题:iOS面试题

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