2019面试题(下)

479 阅读12分钟

第三方框架

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解析器;

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

SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。

加载图片的过程大致如下:

  1. 首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
  2. 如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
  3. 如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
  4. 下载后的图片会加入缓存中,并写入磁盘中
  5. 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来

SDWebImage原理:

调用类别的方法:

  1. 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。

  2. 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。

  3. 从网络上获取,使用,缓存到内存,缓存到沙盒。

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

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


算法

求最大公约数

/** 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)/最大公约数

模拟栈操作

/**
 *  栈是一种数据结构,特点:先进后出
 *  练习:使用全局变量模拟栈的操作
 */
 #include <stdio.h>
 #include <stdbool.h>
 #include <assert.h>
 //保护全局变量:在全局变量前加static后,这个全局变量就只能在本文件中使用
 //栈最多能保存1024个数据
 static int data[1024];
 //目前已经放了多少个数(相当于栈顶位置)
 static int count = 0;
 //数据入栈 push
 void push(int x){
 //防止数组越界
   assert(!full());
   data[count++] = x;
  }
 //查看栈顶元素 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;
}

排序算法

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

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

都将数组分为已排序部分和未排序部分。

  1. 选择排序将已排序部分定义在左端,然后选择未排序部分的最小元素和未排序部分的第一个元素交换。
  2. 冒泡排序将已排序部分定义在右端,在遍历未排序部分的过程执行交换,将最大元素交换到最右端。
  3. 插入排序将已排序部分定义在左端,将未排序部分元的第一个元素插入到已排序部分合适的位置。

选择排序

冒泡排序

折半查找(二分查找)

/** *	折半查找:优化查找时间(不用遍历全部数据) 
*	折半查找的原理: 
*   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;
}

编码格式(优化细节)

在 Objective-C 中,enum 建议使用 NS_ENUM 和 NS_OPTIONS 宏来定义枚举类型。

//定义一个枚举(比较严密)
typedef NS_ENUM(NSInteger, BRUserGender) {
    BRUserGenderUnknown,	
    // 未知
    BRUserGenderMale,		
    // 男性
    BRUserGenderFemale,		
    // 女性
    BRUserGenderNeuter		
    // 无性
};
@interface BRUser : NSObject<NSCopying>
@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 时就应该尽量使用不可变的对象:其三个属性都应该设为“只读”。用初始化方法设置好属性值之后,就不能再改变了。
//属性的参数应该按照下面的顺序排列: (原子性,读写,内存管理)

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

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

其它知识点

一.什么是 OpenGL、Quartz 2D?

Quatarz 2d 是Apple提供的基本图形工具库。只是适用于2D图形的绘制。

OpenGL,是一个跨平台的图形开发库。适用于2D和3D图形的绘制。

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

谈谈 UITableView 的优化

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

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

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

三.说说你对 block 的理解

栈上的自动复制到堆上,block 的属性修饰符是 copy,循环引用的原理和解决方案。

四.说说你对 runtime 的理解

主要是方法调用时如何查找缓存,如何找到方法,找不到方法时怎么转发,对象的内存布局。

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

野指针:不知道指向了哪里的指针叫野指针。即指针指向不确定,指针存的地址是一个垃圾值,未初始化。

空指针:不指向任何位置的指针叫空指针。即指针没有指向,指针存的地址是一个空地址,NULL。

六.什么是 OOA / OOD / OOP ?

OOA(Object Oriented Analysis) --面向对象分析

OOD(Object Oriented Design) --面向对象设计

OOP(Object Oriented Programming)--面向对象编程

七.多线程是什么

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

八. 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和异步函数等都有使用多线程,(待补充).

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

项目中使用NSOperation的优点是NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用。

项目中使用GCD的优点是GCD本身非常简单、易用,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用。

十 KVO,NSNotification,delegate及block区别

  1. NSNOtification通知是一对多的关系,一个对象向所有观察者提供变更通知;
  2. Block是一对一的回调机制,更加简洁;但是当通信事件比较多时,建议使用代理;
  3. Delegate也是一对一的回调机制,需要协议方法。代理对象实现协议方法,并且需要建立代理关系才能进行通信;
  4. KVO观察者模式,是当被观察者对象的属性发生改变时,会向观察者发送一条改变的通知的设计模式。

十一 将一个函数在主线程执行的4种方法

dispatch_async(dispatch_get_main_queue(), ^{
    //需要执行的方法
});
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
//主队列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
    //需要执行的方法
}];
[mainQueue addOperation:operation];
[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];
[[NSRunLoop mainRunLoop] performSelector:@selector(method) withObject:nil];

小编这呢,给大家推荐一个优秀的iOS交流平台,平台里的伙伴们都是非常优秀的iOS开发人员,我们专注于技术的分享与技巧的交流,大家可以在平台上讨论技术,交流学习。欢迎大家的加入(想要加入的可加小编微信15673450590)。

原文链接:www.jianshu.com/p/99e7ff283…
有未完善之处已补充