美文网首页
2020-ios-面试总结

2020-ios-面试总结

作者: 85ca4232089b | 来源:发表于2020-02-25 14:37 被阅读0次

    基础篇

    1 . 一个字节多少位?一个汉字多少位?一个字母多少位
    一个汉字等于两个英文字母等于两个字节、一个字节是八位

    1. 写一个标准的宏MIN,输入两个参数并返回较小的一个
      define MIN (A,B) ( (A) <= (B) ? (A) : (B) )
    2. define定义和const定义的常量的区别
    const定义的常量,是在程序运行时存放在常量表中的,系统会为它自动分配内存,而且在编译时进行类型检查
    
    1. static
    1).作用于变量:
    用static声明局部变量时,则改变变量的存储方式(生命期),使变量成为静态的局部变量,即编译时就为变量分配内存,直到程序退出才释放存储单元。这样,使得该局部变量有记忆功能,可以记忆上次的数据,不过由于仍是局部变量,因而只能在代码块内部使用(作用域不变)。
    用static声明外部变量-------外部变量指在所有代码块{}之外定义的变量,它缺省为静态变量,编译时分配内存,程序结束时释放内存单元。同时 其作用域很广,整个文件都有效甚至别的文件也能引用它。为了限制某些外部变量的作用域,使其只在本文件中有效,而不能被其他文件引用,可以用static 关键字对其作出声明
    2).作用于函数:
    使用static用于函数定义时,对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。
    
    如果想要其他文件可以引用本地函数,则要在函数定义时使用关键字extern,表示该函数是外部函数,可供其他文件调用。另外在要引用别的文件中定义的外部函数的文件中,使用extern声明要用的外部函数即可。
    
    1. 一个函数中有异步的网络请求如何返回一个值
    1. GCD信号量的方法
    + (NSString *)httpNet
     {
         dispatch_semaphore_t signal = dispatch_semaphore_create(1);
         
         __block NSString *objectID;
      
         // 模拟block异步
        [UIView animateWithDuration:3 animations:^{
             
             objectID = @"222";
             
             dispatch_semaphore_signal(signal);
         }];
         
         dispatch_semaphore_wait(signal, DISPATCH_TIME_FOREVER);
         return objectID;
    2. 直接给函数定义一个block返回
    + (NSString *)httpNetBlock:(void(^)(UIImage *Image))block
    
    1. 将一个函数在主线程执行的4种方法
      •GCD方法,通过向主线程队列发送一个block块,使block里的方法可以在主线程中执行
    dispatch_async(dispatch_get_main_queue(), ^{      
        //需要执行的方法
    });
    

    •NSOperation 方法

    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主队列
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        //需要执行的方法
    }];
    [mainQueue addOperation:operation];
    

    •NSThread 方法

    [self performSelector:@selector(method) onThread:[NSThread mainThread] withObject:nil waitUntilDone:YES modes:nil];
    
    [self performSelectorOnMainThread:@selector(method) withObject:nil waitUntilDone:YES];
    
    [[NSThread mainThread] performSelector:@selector(method) withObject:nil];
    

    •RunLoop方法

    [[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil];
    
    
    1. NSTimer创建后,会在哪个线程运行
      •用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程
      •自己创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程
    2. NSNotification接受通知是在那个线程,接收通知是异步的还是同步的
      •接收通知的线程,和发送通知所处的线程是同一个线程
      •NSNotification是同步的 ,因为 NSNotificationCenter 会一直等待所有的 接收者 (observer)都收到并且处理了通知才会返回到poster
    3. id和NSObject*的区别
    typedef struct objc_object *id
    
    

    •id可以理解为指向对象的指针。所有oc的对象 id都可以指向,编译器不会做类型检查,id调用任何存在的方法都不会在编译阶段报错,当然如果这个id指向的对象没有这个方法,该崩溃还是会崩溃的。
    •NSObject *指向的必须是NSObject的子类,调用的也只能是NSObjec里面的方法否则就要做强制类型转换。
    •不是所有的OC对象都是NSObject的子类,还有一些继承自NSProxy。NSObject *可指向的类型是id的子集。

    1. NSProxy & NSObject
      NSObjetct:
      •NSObject协议组对所有的Object -C下的objects都生效。如果objects遵从该协议,就会波看作是first -class
      objects (- 级类)。另外, 遵从该协议的objects的retain, release, autorelease等 方法也服从objects的管理和在
      Foundation中定义的释放方法。- -些容器中的对象也可以管理这些
      •objects,比如说NSArray和NSDictionary定义的对象。Cocoa的根类也遵循该协议,所以所有继承NSObjects的
      objects都有遵循该协议的特性。

    •NSProXY:NSProxy是一个虚基类,它为一些表现的像是其它对象替身或者并不存在的对象定义一套API。 -般
    的,发送给代理的消息被转发给一个真实的对象或者代理本身load(或者将本身转换成)一个真实的对象。
    •NSProxy的基类可以被用来透明的转发消息或者耗费巨大的对象的lazy初始化

    1. id和instancetype的区别
      •instancetype是clang 3.5开始,clang提供的一个关键字,表示某个方法返回的未知类型的Objective-C对象。

    一、关联返回类型(related result types)

    根据Cocoa的命名规则,满足下述规则的方法:
    1、类方法中,以alloc或new开头
    2、实例方法中,以autorelease,init,retain或self开头
    会返回一个方法所在类类型的对象,这些方法就被称为是关联返回类型的方法

    NSArray *array = [[NSArray alloc] init];  
    

    二、instancetype作用

    • 就是使那些非关联返回类型的方法返回所在类的类型!
    • 能够确定对象的类型,能够帮助编译器更好的为我们定位代码书写问题,比如:

    [[[NSArray alloc] init] mediaPlaybackAllowsAirPlay]; //  "No visible @interface for `NSArray` declares the selector `mediaPlaybackAllowsAirPlay`"  
      
    [[NSArray array] mediaPlaybackAllowsAirPlay]; // (No error)  
    
    

    上例中第一行代码,由于[[NSArray alloc]init]的结果是NSArray*,这样编译器就能够根据返回的数据类型检测出NSArray是否实现mediaPlaybackAllowsAirPlay方法。有利于开发者在编译阶段发现错误。
    第二行代码,由于array不属于关联返回类型方法,[NSArray array]返回的是id类型,编译器不知道id类型的对象是否实现了mediaPlaybackAllowsAirPlay方法,也就不能够替开发者及时发现错误

    三、instancetype和id的异同

    1、相同点
    都可以作为方法的返回类型
    2、不同点
    (1)instancetype可以返回和方法所在类相同类型的对象,id只能返回未知类型的对象;
    (2)instancetype只能作为返回值,不能像id那样作为参数

    1. KVO,NSNotification,delegate及block区别

    •KVO就是cocoa框架实现的观察者模式,一般同KVC搭配使用,通过KVO可以监测一个值的变化,比如View的高度变化。是一对多的关系,一个值的变化会通知所有的观察者。

    •NSNotification是通知,也是一对多的使用场景。在某些情况下,KVO和NSNotification是一样的,都是状态变化之后告知对方。NSNotification的特点,就是需要被观察者先主动发出通知,然后观察者注册监听后再来进行响应,比KVO多了发送通知的一步,但是其优点是监听不局限于属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用也更灵活。

    •delegate 是代理,就是我不想做的事情交给别人做。比如狗需要吃饭,就通过delegate通知主人,主人就会给他做饭、盛饭、倒水,这些操作,这些狗都不需要关心,只需要调用delegate(代理人)就可以了,由其他类完成所需要的操作。所以delegate是一对一关系。

    •block是delegate的另一种形式,是函数式编程的一种形式。使用场景跟delegate一样,相比delegate更灵活,而且代理的实现更直观。

    •KVO一般的使用场景是数据,需求是数据变化,比如股票价格变化,我们一般使用KVO(观察者模式)。delegate一般的使用场景是行为,需求是需要别人帮我做一件事情,比如买卖股票,我们一般使用delegate。
    Notification一般是进行全局通知,比如利好消息一出,通知大家去买入。delegate是强关联,就是委托和代理双方互相知道,你委托别人买股票你就需要知道经纪人,经纪人也不要知道自己的顾客。Notification是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息

    1. synthesize & denamic
      • 通过@sythesize
      指令告诉编译器在编译期间产生getter/setter方法。
      • 通过@dynamic指令,自己实现方法。
      有些存取是在运行时动态创建的,如在CoreData的NSManagedObject类使

    2. imageName和mageWithContextOfFile的区别?哪个性能高
      • 用imageNamed的方式加载时,图片使用完毕后缓存到内存中,内存消耗多,加载速度快。即使生成的对象被autoReleasePool释放了,这份缓存也不释放,如果图像比较大,或者图像比较多,用这种方式会消耗很大的内存,可能会存在内存溢出的情况。
      imageNamed采用了缓存机制,如果缓存中已加载了图片,直接从缓存读就行了,每次就不用再去读文件了,效率会更高。
      • ImageWithContextOfile加载, 图片是不会缓存的,加载速度慢。
      • 大量使用imageNamed方式会在不需要缓存的地方额外增加开销CPU的时间当应用程字需要加载- -张比较大的图片并且使用一次性,那么其实是没有必要去缓存这个图片的,用imageWithContentsOfile是 最为经济的方式,这样不会因为Ullmage元素较多情况下,CPU会被逐个分散在不必要缓存上浪费过多时间

    3. 项目开发中遇到的内存溢出
      • 滑动列表的时候,内存出现莫名的增长,原因可能有如下可能:
      • 没有使用UITableView的reuse机制; 导致每显示一个cell都用autorelease的方式重新alloc一次; 导致cell的内存不断的增加;
      • 每个cell会显示一个单独的UIView, 在UIView发生内存泄漏,导致cell的内存不断增长;
      • 频繁访问图片的时候,内存莫名的增长;
      频繁的访问网络图片,导致iOS内部API,会不断的分配autorelease方式的buffer来处理图片的解码与显示; 利用图片cache可以缓解一下此问题;
      • 频繁打开和关闭SQLite,导致内存不断的增长
      •非ARC模式下, release忘了.
      • 少用[UIImage imageNamed]
      • 后台线程的时候,忘了加AutoReleasePool

    4. 项目开发中遇到的内存泄露
      • 第三方框架的不正当使用
      • block循环引用(正确使用copy)
      • delegate的循环引用(正确使用weak)
      • NSTimer的循环引用(NSTimer的二次封装,或者repeats为NO是正确的释放)
      • 非OC对象的内存处理(CF类CG类)
      • 地图类处理(代理以及大头针之类)
      • 大数量次数的循环内存暴涨(for循环在结束时才会释放,所有把需要循环的代码逻辑放在)

    17.NSCache & NSDcitionary
    NSCache与可变集合有几点不同:
    •NSCache类结合了各种自动删除策略,以确保不会占用过多的系统内存。如果其它应用需要内存时,系统自动执行这些策略。当调用这些策略时,会从缓存中删除一些对象,以最大限度减少内存的占用。
    •NSCache是线程安全的,我们可以在不同的线程中添加、删除和查询缓存中的对象,而不需要锁定缓存区域。
    •不像NSMutableDictionary对象,- 一个缓存对象不会拷贝key对象。
    •NSCache和NSDictionary类似,不同的是系统回收内存的时候它会自动删掉它的内容。
    (1)可以存储(当然是使用内存
    (2)保持强应用,无视垃圾回收. =>这-点同NSMutableDictionary(3)有固定客户.

    18.UILayer & UiView
    •UlView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它本身完全是由CoreAnimation来实现的(Mac下似乎不是这样)。它真正的绘图部分,是由一个叫CAL ayer (Core Animation Layer)的类来管理。UIView本身,更像是-个CAL ayer的管理器,访问它的跟绘图和跟坐标有关的属性,例如frame, bounds等等,实际 上内部都是在访问它所包含的CAL ayer的相关属性。
    •UIView有个重要属性layer,可以返回它的主CAL ayer实例。
    •UIView的CAL ayer类似UIView的子View树形结构,也可以向它的layer上添加子layer,来完成某些特殊的表示。即CALayer层是可以嵌套的。
    •UIView的ayer树形在系统内部,被维护着三份copy。分别是逻辑树,这里是代码可以操纵的;动画树,是一个中间层,系统就在这一层上更改属性,进行各种渲染操作;显示树,其内容就是当前正被显示在屏幕上得内容。

    1. iOS Category 和 Protocol 中的 Property会自动生成setter/getter方法吗
    2. 对象提前释放。野指针,空指针, 僵尸对象
    3. runtime的内存模型(isa、对象、类、metaclass、结构体的存储信息等
    struct objc_class : objc_object {
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
        class_rw_t *data() { 
            return bits.data();
        }
    }
    

    类对象 第一个元素是一个隐藏元素(来自于父类),该属性即为isa,其指向元类;
    第二个元素是Classsuperclass,指向其父类;
    第三个元素是cache_t cache,类的相关缓存;
    第四个元素是class_data_bits_t bits,返回类存储的相关信息;
    Class superclass 同样是一个指针,占用8字节; Class isa 是一个指针,占用8字节;
    cache_t 是一个结构体,其中第一个元素是一个指针占8字节,第二、三个元素都是int32各占4字节,总共占16字节
    类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中
    class_ro_t,其中存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。

    1. 为什么设计metalclass
      可以从 objc_object的结构中看出最终的基类(NSObject)的元类对象isa指向的是自己本身,从而形成一个闭环。
      元类(Meta Class):是一个类对象的类,即:Class的类,这里保存了类方法等相关信息
      metaclass代表的是类对象的对象,它存储了类的类方法,它的目的是将实例和类的相关方法列表以及构建信息区分开来,方便各司其职,符合单一职责设计原则。
      全面的解释请移步这里
    2. class_copyIvarList & class_copyPropertyList&objc_method_list区别
      class_copyIvarList:获得的是class_copyPropertyList修饰的以及在m文件的中@implementation内定义的变量
      class_copyPropertyList:获得的是由@property修饰过的变量,
      objc_method_list:存储了类的方法列表,可以通过class_copyMethodList获取
    3. 消息转发机制,消息转发机制和其他语言的消息机制优劣对比
    // 第一阶段 动态方法解析
    + (BOOL)resolveInstanceMethod:(SEL)selector
    + (BOOL)resolveClassMethod:(SEL)selector //处理的是类方法
    
    // 第二阶段:第三者的处理
    - (id)forwardingTargetForSelector:(SEL)selector
    
    // 第三阶段: 标准消息转发流程
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    - (void)forwardInvocation:(NSInvocation *)invocation
    
    // 第四阶段:报错
    - (void)doesNotRecognizeSelector:(SEL)aSelector{}
    

    全面流程解析请看这里

    1. IMP、SEL、Method的区别和使用场景
      Method = SEL + IMP.
      IMP:是方法的实现,只想函数实现的结构体指针.
      SEL:是方法名
      Method:是objc_method类型指针,它是一个结构体.
    2. weak 和 SideTable的认识
      weak:其实是一个hash表结构,其中的key是所指对象的地址,value是weak的指针数组,weak表示的是弱引用,不会对对象引用计数+1,当引用的对象被释放的时候,其值被自动设置为nil,一般用于解决循环引用的。
      weak的实现原理
      1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
      2、添加引用时:objc_initWeak函数, 调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
      3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
    struct SideTable {
    // 保证原子操作的自旋锁
        spinlock_t slock;
        // 引用计数的 hash 表
        RefcountMap refcnts;
        // weak 引用全局 hash 表
        weak_table_t weak_table;
    }
    
    1. 保证线程顺序执行的方法
      1 dispatch_group_t + dispatch_group_notify
    - (void)runDispatchTest:(NSArray *)images{
            // 需要上传的数据
            // 准备保存结果的数组,元素个数与上传的图片个数相同,先用 NSNull 占位
            NSMutableArray* result = [NSMutableArray array];
            for (UIImage* image in images) {
            [result addObject:[NSNull null]];
            }
    
            dispatch_group_t group =  dispatch_group_create();
    
            for (NSInteger i = 0; i < images.count; i++){
            dispatch_group_enter(group);//通知 group,下个任务要放入 group 中执行了
            dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            //         NSLog(@"队列组:有一个耗时操作完成!");
            [uploadImgae startWithCompletionBlockWithSuccess:^(__kindof YTKBaseRequest * _Nonnull request) {
                    NSLog(@"第 %d 张图片上传成功: %@", (int)i + 1, request.responseObject);
                    @synchronized (result)
                    { // NSMutableArray 是线程不安全的,所以加个同步锁
                        result[i] = request.responseObject;
                    }
                
                    dispatch_group_leave(group);//通知 group,任务成功完成,要移除,与 enter成对出现
            } failure:^(__kindof YTKBaseRequest * _Nonnull request) {
            NSLog(@"第 %d 张图片上传失败: %@", (int)i + 1, request.error);
                 }];
               });
            }
            /**只要任务全部完成了,就会在最后调用*/
            dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            //         NSLog(@"队列组:前面的耗时操作都完成了,回到主线程进行相关操作");
            for (id response in result) {
            NSLog(@"上传完成的数据%@", response);
            }
        });
    }
    

    2 dispatch_group_t + dispatch_group_notify

    提交一个栅栏函数在执行中,它会等待栅栏函数执行完再去执行下一行代码(注意是下一行代码),同步栅栏函数是在主线程中执行的
    dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t blcok);
    
    提交一个栅栏函数在异步执行中,它会立马返回开始执行下一行代码(不用等待任务执行完毕)
    dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t blcok);
    
    1、dispatch_barrier_sync需要等待自己的任务(barrier)结束之后,才会继续添加并执行写在barrier后面的任务(4、5、6),然后执行后面的任务
    2、dispatch_barrier_async将自己的任务(barrier)插入到queue之后,不会等待自己的任务结束,它会继续把后面的任务(4、5、6)插入到queue,然后执行任务。
    
    

    3 NSOperationQueueu 添加依赖或者等待

    - (void)operationQueueMethod {
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        queue.maxConcurrentOperationCount = 2;
        // 第一张图片
        NSBlockOperation *op_one = [NSBlockOperation blockOperationWithBlock:^{     
            NSLog(@"正在下载第一张图片");
        }];  
        // 第二张图片
        NSBlockOperation *op_two = [NSBlockOperation blockOperationWithBlock:^{   
            NSLog(@"正在下载第二张图片");
        }];
        
        /*
        // 添加到队列执行
            [queue addOperation:op1];
            [queue addOperation:op2];
        
            NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        
                UIGraphicsBeginImageContext(CGSizeMake(300, 400));
        
                [self.imageOne drawInRect:CGRectMake(0, 0, 150, 400)];
                [self.imageTwo drawInRect:CGRectMake(150, 0, 150, 400)];
        
                UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
        
                dispatch_async(dispatch_get_main_queue(), ^{
                });
            }];
        
            [operation addDependency: op1];
            [operation addDependency: op2];
            [queue addOperation:operation];/// 不会阻塞主线程
        */
    
        [queue addOperations:@[op_one, op_two] waitUntilFinished:true];// 会阻塞主线程
        [queue addOperationWithBlock:^{
            
            UIGraphicsBeginImageContext(CGSizeMake(300, 300));
            
            [self.imageOne drawInRect:CGRectMake(0, 100, 150, 300)];
            [self.imageTwo drawInRect:CGRectMake(180, 100, 150, 300)];
            
            UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
            UIGraphicsEndImageContext();
            
            dispatch_async(dispatch_get_main_queue(), ^{
                UIImageView *imageView = [[UIImageView alloc] initWithImage:newImage];
                [self.view addSubview:imageView];
                self.textLabel.text = @"图片合成";
            });
            
        }];
    
        NSLog(@"主线程会等到队列执行完,即会阻塞主线程");
    }
    

    中极篇

    1. 寻找两个UIView的最近的公共父类
      这个问的其实是数据结构中的二叉树,查找一个普通二叉树中两个节点最近的公共祖先问题
      假设两个视图为UIViewA、UIViewC,其中UIViewA继承于UIViewB,UIViewB继承于UIViewD,UIViewC也继承于UIViewD;即A->B->D,C->D
      我们将一个路径中的所有点先放进NSSet中.因为NSSet的内部实现是一个hash表,所以查询元素的时间的复杂度变成O(1),我们一共有N个节点,所以总时间复杂度优化到了O(N)
    - (void)viewDidLoad {
        [super viewDidLoad];
        Class commonClass1 = [self commonClass1:[ViewA class] andClass:[ViewC class]];
        NSLog(@"%@",commonClass1);
        // 输出:2018-03-22 17:36:01.868966+0800 两个UIView的最近公共父类[84288:2458900] ViewD
    }
    // 获取所有父类
    - (NSArray *)superClasses:(Class)class {
        if (class == nil) {
            return @[];
        }
        NSMutableArray *result = [NSMutableArray array];
        while (class != nil) {
            [result addObject:class];
            class = [class superclass];
        }
        return [result copy];
    }
    
    - (Class)commonClass1:(Class)classA andClass:(Class)classB {
        NSArray *arr1 = [self superClasses:classA];
        NSArray *arr2 = [self superClasses:classB];
        NSSet *set = [NSSet setWithArray:arr2];
        for (NSUInteger i =0; i<arr1.count; ++i) {
            Class targetClass = arr1[i];
            if ([set containsObject:targetClass]) {
                return targetClass;
            }
        }
        return nil;
    }
    
    

    我的理解如果有错漏请一定指出,非常感谢!

    相关文章

      网友评论

          本文标题:2020-ios-面试总结

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