iOS面试知识点整理二

iOS面试知识点整理一

二、 第三方框架

  1. AFNetworking 底层原理分析

    AFNetworking主要是对NSURLSession和NSURLConnection(iOS9.0废弃)的封装,其中主要有以下类:
    1). AFHTTPRequestOperationManager:内部封装的是 NSURLConnection, 负责发送网络请求, 使用最多的一个类。(3.0废弃)
    2). AFHTTPSessionManager:内部封装是 NSURLSession, 负责发送网络请求,使用最多的一个类。
    3). AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境发生改变之后,这个工具类就可以检测到。
    4). AFSecurityPolicy:网络安全的工具类, 主要是针对 HTTPS 服务。
     
    5). AFURLRequestSerialization:序列化工具类,基类。上传的数据转换成JSON格式
    (AFJSONRequestSerializer).使用不多。
    6). AFURLResponseSerialization:反序列化工具类;基类.使用比较多:
    7). AFJSONResponseSerializer; JSON解析器,默认的解析器.
    8). AFHTTPResponseSerializer; 万能解析器; JSON和XML之外的数据类型,直接返回二进制数据.对服务器返回的数据不做任何处理.
    9). AFXMLParserResponseSerializer; XML解析器;

  2. 描述下SDWebImage里面给UIImageView加载图片的逻辑

    SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。
     
    加载图片的过程大致如下:
    1.首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
    2.如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
    3.如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
    4.下载后的图片会加入缓存中,并写入磁盘中
    5.整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
     
    SDWebImage原理:
    调用类别的方法:
    1. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。
    2. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
    3. 从网络上获取,使用,缓存到内存,缓存到沙盒。

参考:SDWebImage内部实现过程

  1. 友盟统计接口统计的所有功能

    APP启动速度,APP停留页面时间等

三、算法

1.不用中间变量,用两种方法交换A和B的值

    // 1.中间变量
    void swap(int a, int b) {
       int temp = a;
       a = b;
       b = temp;
    }

    // 2.加法
    void swap(int a, int b) {
       a = a + b;
       b = a - b;
       a = a - b;
    }

    // 3.异或(相同为0,不同为1\. 可以理解为不进位加法)
    void swap(int a, int b) {
       a = a ^ b;
       b = a ^ b;
       a = a ^ b;
    } 

2.求最大公约数

    /** 1.直接遍历法 */
    int maxCommonDivisor(int a, int b) {
        int max = 0;
        for (int i = 1; i <=b; i++) {
            if (a % i == 0 && b % i == 0) {
                max = I;
            }
        }
        return max;
    }

    /** 2.辗转相除法 */
    int maxCommonDivisor(int a, int b) {
        int r;
        while(a % b > 0) {
            r = a % b;
            a = b;
            b = r;
        }
        return b;
    }

    // 扩展:最小公倍数 = (a * b)/最大公约数

3.模拟栈操作

/**
     *  栈是一种数据结构,特点:先进后出
     *  练习:使用全局变量模拟栈的操作
     */
    #include 
    #include 
    #include 
    //保护全局变量:在全局变量前加static后,这个全局变量就只能在本文件中使用
    static int data[1024];//栈最多能保存1024个数据
    static int count = 0;//目前已经放了多少个数(相当于栈顶位置)

    //数据入栈 push
    void push(int x){
        assert(!full());//防止数组越界
        data[count++] = x;
    }
    //数据出栈 pop
    int pop(){
        assert(!empty());
        return data[--count];
    }
    //查看栈顶元素 top
    int top(){
        assert(!empty());
        return data[count-1];
    }

    //查询栈满 full
    bool full() {
        if(count >= 1024) {
            return 1;
        }
         return 0; 
    }

    //查询栈空 empty
    bool empty() {
        if(count <= 0) {
            return 1;
        }
        return 0;
    }

    int main(){
        //入栈
        for (int i = 1; i <= 10; i++) {
            push(i);
        }

        //出栈
        while(!empty()){
            printf("%d ", top()); //栈顶元素
            pop(); //出栈
        }
        printf("\n");

        return 0;
    } 

4.排序算法

    选择排序、冒泡排序、插入排序三种排序算法可以总结为如下:

    *   都将数组分为已排序部分和未排序部分。
        1\. 选择排序将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。
        2\. 冒泡排序将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。
        3\. 插入排序将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。 
* 选择排序 /** * 【选择排序】:最值出现在起始端 * * 第1趟:在n个数中找到最小(大)数与第一个数交换位置 * 第2趟:在剩下n-1个数中找到最小(大)数与第二个数交换位置 * 重复这样的操作...依次与第三个、第四个...数交换位置 * 第n-1趟,最终可实现数据的升序(降序)排列。 * */ void selectSort(int *arr, int length) { int temp; for (int i = 0; i < length - 1; i++) { //趟数 for (int j = i + 1; j < length; j++) { //比较次数 if (arr[i] > arr[j]) { temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } } * 冒泡排序 /** * 【冒泡排序】:相邻元素两两比较,比较完一趟,最值出现在末尾 * 第1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n个元素位置 * 第2趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第n-1个元素位置 * …… …… * 第n-1趟:依次比较相邻的两个数,不断交换(小数放前,大数放后)逐个推进,最值最后出现在第2个元素位置 */ void bublleSort(int *arr, int length) { int temp; for(int i = 0; i < length - 1; i++) { //趟数 for(int j = 0; j < length - i - 1; j++) { //比较次数 if(arr[j] > arr[j+1]) { temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; } } } }

参考:
8大排序算法图文讲解
常用算法OC实现
给定一个数组,从中查找是否存在两个数的和等于一个给定的x
参考一
参考二


5.折半查找(二分查找)

    /**
     *  折半查找:优化查找时间(不用遍历全部数据)
     *
     *  折半查找的原理:
     *   1> 数组必须是有序的
     *   2> 必须已知min和max(知道范围)
     *   3> 动态计算mid的值,取出mid对应的值进行比较
     *   4> 如果mid对应的值大于要查找的值,那么max要变小为mid-1
     *   5> 如果mid对应的值小于要查找的值,那么min要变大为mid+1
     *
     */ 

    // 已知一个有序数组, 和一个key, 要求从数组中找到key对应的索引位置 
    int findKey(int *arr, int length, int key) {
        int min = 0, max = length - 1, mid;
        while (min <= max) {
            mid = (min + max) / 2; //计算中间值
            if (key > arr[mid]) {
                min = mid + 1;
            } else if (key < arr[mid]) {
                max = mid - 1;
            } else {
                return mid;
            }
        }
        return -1;
    } 

5.求n的阶乘n!

long fac(int n) {
  if (n == 1) return 1;
  return n * fac(n-1);
}

四、编码格式(优化细节)

参考:请Review下面的代码,并根据iOS的编码规范做出正确的修改

1.在 Objective-C 中,enum 建议使用 NS_ENUMNS_OPTIONS 宏来定义枚举类型。

    //定义一个枚举(比较严密)
    typedef NS_ENUM(NSInteger, BRUserGender) {
        BRUserGenderUnknown,    // 未知
        BRUserGenderMale,       // 男性
        BRUserGenderFemale,     // 女性
        BRUserGenderNeuter      // 无性
    }; 

    @interface BRUser : NSObject

    @property (nonatomic, readonly, copy) NSString *name;
    @property (nonatomic, readonly, assign) NSUInteger age;
    @property (nonatomic, readonly, assign) BRUserGender gender;

    - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age gender:(BRUserGender)gender;

    @end

    //说明:
    //既然该类中已经有一个“初始化方法” ,用于设置 name、age 和 gender 的初始值: 那么在设计对应 @property 时就应该尽量使用不可变的对象:其三个属性都应该设为“只读”。用初始化方法设置好属性值之后,就不能再改变了。
    //属性的参数应该按照下面的顺序排列: (原子性,读写,内存管理)

2.避免使用C语言中的基本数据类型,建议使用 Foundation 数据类型,对应关系如下:

int -> NSInteger
unsigned -> NSUInteger
float -> CGFloat
动画时间 -> NSTimeInterval

五、其它知识点

  1. HomeKit,是苹果2014年发布的智能家居平台。

  2. 什么是 OpenGL、Quartz 2D?

    Quatarz 2d 是Apple提供的基本图形工具库。只是适用于2D图形的绘制。
    OpenGL,是一个跨平台的图形开发库。适用于2D和3D图形的绘制。

  3. ffmpeg框架:

    ffmpeg 是音视频处理工具,既有音视频编码解码功能,又可以作为播放器使用。

  4. 谈谈 UITableView 的优化

    1). 正确的复用cell;
    2). 设计统一规格的Cell;
    3). 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
    4). 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
    5). 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
    6). 减少子视图的层级关系;
    7). 尽量使所有的视图不透明化以及做切圆操作;
    8). 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示;
    9). 使用调试工具分析问题。

  5. 如何实行cell的动态的行高

    如果希望每条数据显示自身的行高,必须设置两个属性,1.预估行高,2.自定义行高。
    设置预估行高 tableView.estimatedRowHeight = 200。
    设置定义行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。
    如果要让自定义行高有效,必须让容器视图有一个自下而上的约束。

  6. 说说你对 block 的理解

    栈上的自动复制到堆上,block 的属性修饰符是 copy,循环引用的原理和解决方案。
     
    block的循环引用;block的代码实现;为什么会造成循环引用;block是如何强引用self的;
    参考:Block 代码块中用到了self引用问题,导致循环引用

  7. 什么是野指针、空指针?

    野指针:不知道指向了哪里的指针叫野指针。即指针指向不确定,指针存的地址是一个垃圾值,未初始化。
    空指针:不指向任何位置的指针叫空指针。即指针没有指向,指针存的地址是一个空地址,NULL。

  8. 什么是 OOA / OOD / OOP ?

    OOA(Object Oriented Analysis) --面向对象分析
    OOD(Object Oriented Design) --面向对象设计
    OOP(Object Oriented Programming)--面向对象编程

  9. 多线程是什么

    多线程是个复杂的概念,按字面意思是同步完成多项任务,提高了资源的使用效率,从硬件、操作系统、应用软件不同的角度去看,多线程被赋予不同的内涵,对于硬件,现在市面上多数的CPU都是多核的,多核的CPU运算多线程更为出色;从操作系统角度,是多任务,现在用的主流操作系统都是多任务的,可以一边听歌、一边写博客;对于应用来说,多线程可以让应用有更快的回应,可以在网络下载时,同时响应用户的触摸操作。在iOS应用中,对多线程最初的理解,就是并发,它的含义是原来先做烧水,再摘菜,再炒菜的工作,会变成烧水的同时去摘菜,最后去炒菜。

  10. iOS 中的多线程

    iOS中的多线程,是Cocoa框架下的多线程,通过Cocoa的封装,可以让我们更为方便的使用线程,做过C++的同学可能会对线程有更多的理解,比如线程的创立,信号量、共享变量有认识,Cocoa框架下会方便很多,它对线程做了封装,有些封装,可以让我们创建的对象,本身便拥有线程,也就是线程的对象化抽象,从而减少我们的工程,提供程序的健壮性。
     
    GCD是(Grand Central Dispatch)的缩写 ,从系统级别提供的一个易用地多线程类库,具有运行时的特点,能充分利用多核心硬件。GCD的API接口为C语言的函数,函数参数中多数有Block,关于Block的使用参看这里,为我们提供强大的“接口”,对于GCD的使用参见本文
     
    NSOperation与Queue
     
    NSOperation是一个抽象类,它封装了线程的细节实现,我们可以通过子类化该对象,加上NSQueue来同面向对象的思维,管理多线程程序。具体可参看这里:一个基于NSOperation的多线程网络访问的项目。
     
    NSThread
     
    NSThread是一个控制线程执行的对象,它不如NSOperation抽象,通过它我们可以方便的得到一个线程,并控制它。但NSThread的线程之间的并发控制,是需要我们自己来控制的,可以通过NSCondition实现。
     
    参看 iOS多线程编程之NSThread的使用 
     
    其他多线程
     
    在Cocoa的框架下,通知、Timer和异步函数等都有使用多线程,(待补充).

  11. 在项目什么时候选择使用GCD,什么时候选择NSOperation?

    项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。
     
    项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

  12. 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是弱关联,利好消息发出,你不需要知道是谁发的也可以做出相应的反应,同理发消息的人也不需要知道接收的人也可以正常发出消息。

  13. 将一个函数在主线程执行的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. 如何让计时器调用一个类方法

    * 计时器只能调用实例方法,但是可以在这个实例方法里面调用静态方法。
    * 使用计时器需要注意,计时器一定要加入RunLoop中,并且选好model才能运行。scheduledTimerWithTimeInterval方法创建一个计时器并加入到RunLoop中所以可以直接使用。
    * 如果计时器的repeats选择YES说明这个计时器会重复执行,一定要在合适的时机调用计时器的invalid。不能在dealloc中调用,因为一旦设置为repeats 为yes,计时器会强持有self,导致dealloc永远不会被调用,这个类就永远无法被释放。比如可以在viewDidDisappear中调用,这样当类需要被回收的时候就可以正常进入dealloc中了。

     [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
    
    -(void)timerMethod
    {
    //调用类方法
        [[self class] staticMethod];
    }
    
    -(void)invalid
    {
        [timer invalid];
        timer = nil;
    }
    
  2. 如何重写类方法

    • 1、在子类中实现一个同基类名字一样的静态方法
    • 2、在调用的时候不要使用类名调用,而是使用[self class]的方式调用。原理,用类名调用是早绑定,在编译期绑定,用[self class]是晚绑定,在运行时决定调用哪个方法。
  3. NSTimer创建后,会在哪个线程运行

    • 用scheduledTimerWithTimeInterval创建的,在哪个线程创建就会被加入哪个线程的RunLoop中就运行在哪个线程
    • 自己创建的Timer,加入到哪个线程的RunLoop中就运行在哪个线程。
  4. id和NSObject*的区别

    • id是一个 objc_object 结构体指针,定义是
    typedef struct objc_object *id
    
    • id可以理解为指向对象的指针。所有oc的对象 id都可以指向,编译器不会做类型检查,id调用任何存在的方法都不会在编译阶段报错,当然如果这个id指向的对象没有这个方法,该崩溃还是会崩溃的。

    • NSObject *指向的必须是NSObject的子类,调用的也只能是NSObjec里面的方法否则就要做强制类型转换。

    • 不是所有的OC对象都是NSObject的子类,还有一些继承自NSProxy。NSObject *可指向的类型是id的子集。

  5. static关键字的作用

  • 回答一:

    1.在函数体内定义的static他的作用域为该函数体,该变量在内存中只被分配一次,因此,其值在下次调用时仍维持上次的值不变;
    2.在模块内的static全局变量可以被模块内所有函数访问,但是不能被模块外的其他函数访问;
    3.在模块内的staic全局变量可以被这一模块内的其他函数调用,这个函数的使用范围被限制在这个模块内;
    4.在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝,也就是说只要是该类的对象,那么该对象的中被static修饰的成员变量都指向同一块地址。

  • 回答二:

    修饰局部变量:
    1.延长局部变量的生命周期,程序结束才会销毁。
    2.局部变量只会生成一份内存,只会初始化一次。
    3.改变局部变量的作用域。
     
    修饰全局变量:
    1.只能在本文件中访问,修改全局变量的作用域,生命周期不会改
    2.避免重复定义全局变量

  • 在OC中static关键字使用误区

    1.使用static修饰实例变量是不被允许的
    2.使用static修饰了方法,也是错误的

参考:
如何正确使用const,static,extern
OC中 static 与 const 的作用

  1. 使用 Swift 语言编程的优缺点
    总的来说,我认为使用 Swift 来作为编程语言的优点还是要远远大于缺点的,而且很多缺点苹果也在逐渐改善。
  • 优点:

    1、简洁的语法
    2、更强的类型安全
    3、函数式编程的支持
       Swift 语言本身提供了对函数式编程的支持。
       Objc 本身是不支持的,但是可以通过引入 ReactiveCocoa 这个库来支持函数式编程。
    4、编写 OS X 下的自动化脚本

  • 缺点

    1、App体积变大
        使用Swift 后, App 体积大概增加 5-8 M 左右,对体积大小敏感的慎用。
        体积变大的原因是因为 Swift 还在变化,所以 Apple 没有在 iOS 系统里放入 Swift 的运行库,反而是每个 App 里都要包含其对应的 Swift 运行库。
    2、Xcode 支持不够好
        如果你是使用 Xcode经常卡住或者崩溃想必你是肯定碰到过了,这个是目前使用 Swift 最让人头疼的事情,即使是到现在XCode 9, 有时候也会遇到这种问题,所以要看你的承受力了……
    3、第三方库的支持不够多
        目前确实 Swift 编写的第三方库确实不多,但可以通过桥接的方式来使用 Objc 的三方库,基本上没有太大问题。现在已经改善很多了…
    4、语言版本更新带来的编译问题
       语言本身还在发展,所以每次版本更新后都会出现编译不过的情况(至少到目前为止还是),但是自从 4.0 版本发布后,改动没有 beta 时候那么大了,而且根据 Xcode 提示基本就可以解决语法变动导致的编译问题了。

你可能感兴趣的:(iOS面试知识点整理二)