iOS面试珠玑

iOS基础类

  1. 简述iOS中的内存管理方式
  • iOS的内存管理用的是引用计数的方法,分为MRC(手动引用计数)和ARC(自动引用计数)。

  • MRC:开发者手动地进行retain和release操作,对每个对象的retainCount进行+1,-1操作,当retainCount为0时,系统会自动释放对象内存。

  • ARC:开发者通过声明对象的属性为strong,weak,retain,assign来管理对象的引用计数,被strong和retain修饰的属性变量系统会自动


  1. block的分类,__block的作用,block循环引用产生的原因及解决办法
  • blcok分为全局blcok,堆block,栈block。
  • 在 MRC下:只要没有访问外部变量,就是全局block。访问了外部变量,就是栈block。显示地调用[block copy]就是堆block。
  • 在 ARC下:只要没有访问外部变量,就是全局block。如果访问了外部变量,那么在访问外部变量之前存储在栈区,访问外部变量之后存储在堆区。
  • __block的作用:将外部变量的传递形式由值传递变为指针传递,从而可以获取并且修改外部变量的值。同样,外部变量的修改,也会影响block函数的输出。
  • block循环引用问题:当一个类的对象持有block,block里面又引用了这个对象,那么就是一个循环引用的关系。可以用strong-weak-dance的方法解除循环引用。

3.深拷贝与浅拷贝

  • 深拷贝就是开辟一块新的内存空间来存储原来内存空间的内容,对象指针指向新的内存空间。浅拷贝只是重新生成一个指针,指向的还是原来的内存空间。

  • copy方法:如果是非可扩展类对象,则是浅拷贝。如果是可扩展类对象,则是深拷贝。

  • mutableCopy方法:无论是可扩展类对象还是不可扩展类对象,都是深拷贝。

  • 注意:深拷贝和深复制不同,深拷贝的内存空间的元素还是指向原地址,但是深复制会开辟新的内存空间重新复制子元素。


4.iOS中常见的属性和默认的对象属性

  • 常见属性: atomic, nonatomic, assign, retain, strong, weak, copy, readonly, readwrite, unsafe_unretained, getter=, setter= 等。

  • 默认属性: 继承于NSObject类的对象:(atomic, strong), 非继承于NSObject类的对象:(atomic, assign)

  • 属性意义:

    atomic:原子性的,在执行setter和getter方法时可以保证访问变量的线程安全。
    nonatomic:非原子性的,无法保证访问变量的线程安全性,但是变量访问效率会提高。
    assign:主要用于修饰非继承于NSObject类型的对象,例如int, double, NSInteger等,当该对象被其他对象引用时,该对象的引用计数不会自加1。
    retain:一般情况下等同于ARC环境下的strong修饰符,但是在修饰block对象时,retain相当于assign,而strong相当于copy。
    strong:主要用于继承于NSObject类的对象,当strong修饰的对象被其他对象引用时,引用计数会自加1。
    weak:主要用于继承于NSObject类的对象,当strong修饰的对象被其他对象引用时,引用计数不会自加1,且retainCount为0时,指向该对象的指针将会置nil,指向堆栈的底部0x00000000,防止野指针的出现。
    unsafe_unretained:主要用于继承于NSObject类型的对象,当retainCount为0时,指向该对象的指针不会置nil,因此可能会出现野指针,但是效率方面会比weak要高。
    copy:主要适用于NSArray, NSDictionary, NSString, Block等类型的对象,开辟一块新的内存存储原来内存中的元素,对象指针指向新内存的地址。
    readonly: 只读属性
    readwrite: 读写属性
    getter = : 修改读属性名称
    setter = : 修改写属性名称

5.哪些属性需要声明成copy,为什么?

  • NSDictionary, NSArray, NSString, Block需要声明为copy属性修饰,block用copy修饰是因为block只有拷贝到堆上才能保证调用block的时候,block没有被系统提前释放掉。NSDictionary, NSArray, NSString用strong修饰的原因直接上代码(以NSString举例)
@interface ViewController ()
@property (nonatomic, copy)NSString *aStr;
@property (nonatomic, strong)NSString *bStr;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
//    [self buildUI];
    [self testCode];
}

- (void)testCode{
    
    NSMutableString *mutableStr = [NSMutableString stringWithFormat:@"%@", @"abc"];
    self.aStr = mutableStr;
    self.bStr = mutableStr;
    [mutableStr appendString:@"123"];
    NSLog(@"copy修饰的字符串:%@", self.aStr);
    NSLog(@"strong修饰的字符串:%@", self.bStr);
}

打印结果:

2018-05-22 21:13:58.344883 TTFeedBackDemo[1224:245923] copy修饰的字符串:abc
2018-05-22 21:13:58.345004 TTFeedBackDemo[1224:245923] strong修饰的字符串:abc123

用了copy属性修饰之后,可以防止这些类型的对象被引用并且改变内容。


  1. 通知,代理,block,KVO的使用场景分别是什么,有什么区别?
  • 通知: 适用于毫无关联的页面之间或者系统消息的传递,属于一对多的信息传递关系。例如系统音量的改变,系统状态的改变,应用模式的设置和改变,都比较适合用通知去传递信息。

  • 代理: 一对一的信息传递方式,适用于相互关联的页面之间的信息传递,例如push和present出来的页面和原页面之间的信息传递。

  • block: 一对一的信息传递方式,效率会比代理要高(毕竟是直接取IMP指针的操作方式)。适用的场景和代理差不多,都是相互关联页面之间的页面传值。

  • KVO: 属性监听,监听对象的某一属性值的变化状况,当需要监听对象属性改变的时候使用。例如在UIScrollView中,监听contentOffset,既可以用KVO,也可以用代理。但是其他一些情况,比如说UIWebView的加载进度,AVPlayer的播放进度,就只能用KVO来监听了,否则获取不到对应的属性值。


  1. 简述对OC中的isa指针的认识
  • isa指针:首先,贴出NSObject.h文件,大家宏观感受一下。

    Ojective-C语言是基于C语言的封装,它实现了将面向过程的语言向面向对象的语言的转变。而OC中绝大部分类又是继承于NSObject类的,所以研究清楚NSObject类的构成,对于理解OC语言很有帮助。

    NSObject类引入了两个头文件:#include <objc/objc.h>#include <objc/NSObjCRuntime.h>,第一个头文件引入的是objc结构体的构成方式即isa指针,第二个头文件引入的是Runtime消息查找机制。
    接下来又引入了三个类的声明:@class NSString, NSMethodSignature, NSInvocation;
    NSMethodSignature和NSInvacation和OC的方法转发机制有关,而NSString是与NSObject的+ (NSString *)description方法有关。

    isa指针: 代码如下

    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    

    NSObject对象其实包含了一个Class类型的isa指针。 这个和id类型是一样的,代码如下:

    struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    /// A pointer to an instance of a class.
    typedef struct objc_object *id;
    

    即id类型其实是objc_object指针类型的别称。objc_object结构体同样也是包含了一个指向Class类型的指针。 继续看一下,Class isa指针的内部结构,如下

    struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;//指向元类的Class指针
    
    #if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;//指向父类的Class指针
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;//类名
    long version                                             OBJC2_UNAVAILABLE;//类的版本信息,默认为0
    long info                                                OBJC2_UNAVAILABLE;//运行期使用的一些位标识
    long instance_size                                       OBJC2_UNAVAILABLE;//该类的实例变量大小
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;//属性列表
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;//方法列表
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;//缓存方法列表
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;//协议列表
    #endif
    
    } OBJC2_UNAVAILABLE;
    
    typedef struct objc_class *Class;
    

    Class其实就是一个objc_class类型的结构体指针。objc_class的结构体变量构成见上图,下面是objc_class类的super_class指针和isa元类指针的具体指向关系,请大家分清对象,对象的类,元类,根元类这些概念。


  1. 简述OC中的消息转发机制
  • 当objc_msgSend方法调用找不到响应的函数名称时就会进行消息转发,主要分为3步:

    1、动态方法解析

    调用方法+(BOOL)resolveInstanceMethod:(SEL)sel(实例方法动态解析)和+ (BOOL)resolveClassMethod:(SEL)sel(类方法动态解析)。

    2、备援接收者

    调用方法 - (id)forwardingTargetForSelector:(SEL)aSelector

    3、完全转发

    调用方法- (void)forwardInvocation:(NSInvocation *)anInvocation- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

    具体的转发流程见下图:


  1. 响应链原理
  • 当用户触摸屏幕时,触碰屏幕产生事件UIEvent并存入UIApplication中的事件队列中, 并且在整个视图结构中自上而下的进行分发,如下图所示:

  • 这里着重介绍两个方法

//判断当前点击事件是否存在最优响应者(First Responder)
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
//判断当前点击是否在控件的Bounds之内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event;

第一个方法返回一个可以响应event触摸事件的UIView,第二个方法判断触摸点位在不在可相应范围之内的BOOL值。所以会衍生出一些问题,比如说“如何让一个父视图以外的子视图响应点击事件”,“如何只让一个UIView的圆形区域响应触摸事件”等等,在此由于篇幅限制,不再一一展开详述。下面这幅图简述了系统查找响应事件控件的流程。


  1. 手写一个block结构体声明
  • 这就是考察一名iOS开发人员的写码基本功了,大家加深印象即可: typedef void(^RBBlogDemoHandler)(void);

  1. RunLoop原理,RunLoop与线程的关系
  • RunLoop其实就是一个do-while循环,Runloop的存在保证了程序一直在前台运行。RunLoop和线程是一一对应的关系,即开启一个线程,就会创建一个RunLoop对线程进行处理和管理,直到Runloop中没有需要处理的item,RunLoop才会进入休眠状态,如果休眠一段时间没有被唤醒的话,RunLoop将会被销毁掉。RunLoop的执行逻辑见下图:

  1. GCD与NSOperation两种管理多线程方式的异同点
  • GCD是用C语言实现的,而NSOperation是用OC实现的。
  • NSOperation可以设置最大线程并发数,可以设置线程依赖关系,可以设置线程的优先级。
  • GCD方式管理多线程是一种对开发者非常友好的开发方式,开发者只需要关注同步异步,串行并发这些线程关系就可以轻松地进行线程管理了。另外,线程中执行的任务是通过Block的形式调用的,所以执行效率也是非常高的。

  1. GCD的常用api
  • dispatch_sync:同步线程函数
  • dispatch_async:异步线程函数
  • dispatch_group_async:群线程函数
  • dispatch_barrier_async:栅栏函数,阻塞所属的队列的线程
  • dispatch_barrier_sync:栅栏函数,阻塞当前线程
  • dispatch_apply:该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

  1. GCD中的同步异步,串行并发的概念,GCD常见的线程死锁问题
  • 同步:针对于线程而言的概念,阻塞当前线程,不执行结束,当前线程就不会继续往下执行。
  • 异步:针对于线程而言的概念,不阻塞当前线程,当前线程会继续往下执行。
  • 串行:针对于队列而言,串行队列遵守FIFO(first-in-first-out)原则,只能一个任务一个任务地顺序执行。
  • 并发:针对于队列而言,并发队列可以在同一时间执行多个任务。
  • 类似于
dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"%@", @"123");
    });

这样的函数,就是典型的GCD死锁函数。 因为dispatch_sync阻塞的当前的线程,而当前线程是main_queue,也就是说是一个串行线程,当前线程只有先执行NSLog(@"%@", @"123")才能继续运行下去,但是当前线程又被阻塞掉了,无法向下继续执行,所以这就是一个死锁的GCD执行函数了。


  1. iOS中常用的线程锁有哪些,分别具有哪些特点?
  • @synchronized 关键字加锁 互斥锁,性能较差不推荐使用
  • NSLock 互斥锁 不能多次调用 lock方法,会造成死锁
  • NSRecursiveLock递归锁,NSRecursiveLock类定义的锁可以在同一线程多次lock,而不会造成死锁。递归锁会跟踪它被多少次lock。每次成功的lock都必须平衡调用unlock操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
  • NSConditionLock条件锁,顾名思义,这个锁对象有一个condition属性,只有condition的值相同,才能获取到该锁,并且执行解锁操作。
  • POSIX互斥锁,POSIX是Unix/Linux平台上提供的一套条件互斥锁的API。用法和特点与NSLock类似。
  • dispatch_semaphore信号量实现加锁,当信号量为0时,线程将会被卡死,通过信号量的增减来达到控制线程个数的目的。
  • OSSpinLock自旋锁,用法类似于NSLock,可以自动检查线程锁是否已经打开,效率比较高,但是被证明不是线程安全的。
  • GCD线程阻断dispatch_barrier_async/dispatch_barrier_syncdispatch_barrier_async/dispatch_barrier_sync在一定的基础上也可以做线程同步,会在线程队列中打断其他线程执行当前任务。两个的区别是dispatch_barrier_sync阻塞的是当前队列的线程,而dispatch_barrier_async阻塞的是任务所插入队列的线程。
  • 各种线程锁的执行效率对比如下图:

  1. Father的子类Son,分别写出NSStringFromClass([self class]),NSStringFromClass([super class]),NSStringFromClass(self.superClass)的打印值
  • [self class] : Son
  • [super class] : Son
  • self.superClass : Father
  • 第一个就不用解释了,打印本类的类名。
  • [super class]找到NSObject中class方法以后,reciever不变。实际上是因为super只是一个“编译器指示符”,它和self指向的是相同的receiver。这里要深刻理解super所指代的上下文环境。
  • self.superClass是取到了本类的superClass指针,所以打印的是父类的类名。

  1. KVO的原理
  • 例如,A类的实例的name属性被B类的实例监听了。这时,OC的runtime机制生成了一个KVONotifying_A的类来替代原来的A类,重写了+ (Class)class方法,返回[A Class],从而把自己伪装成A类。重写了A类属性name的setter方法加入了NSObject的两个方法:willChangeValueForKey:(值改变之前)和didChangevlueForKey:(值改变之后)。在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而observeValueForKey:ofObject:change:context:也会被调用。

  1. runtime的机制和应用
  • Objective-C是一门动态强类型语言,它会将一些工作放在代码运行时才处理而并非编译时。也就是说,有很多类和成员变量在我们编译的时是不知道的,而在运行时,我们所编写的代码会转换成完整的确定的代码运行。因此,我们还需要一个运行时系统(Runtime system)来处理编译后的代码。例如OC中的类对象,都是在程序的运行期才知道这个对象是哪个类的对象,该类具有哪些特征,等等。我们执行的函数例如:[foo doSomeThing],其实是通过函数objc_msgSend(foo, doSomeThing)来调用的,编译器将会在foo类的方法树中进行查找。
  • 具体的应用:JSON转Model,Model转数据库语言,方法交换Method Swizzing,消息转发,等等。

19.MJExtension, MJRefresh, SDWebImage的实现原理

  • MJExtension:通过运行时,拿到Model对应的PropertyName,然后通过KVC,将字典中的值传入Model。
  • MJRefresh:所有的View继承于父类MJRefreshComponent,通过监听SCrollView的contentOffset来判断Refresh状态,从而触发各种状态的函数方法。
  • SDWebImage:SDWebImageManager拿到需要请求的URL,会进行三级缓存的查找:NSCache, Memery磁盘,服务器。SDWebImageDownLoader会下载图片对应的Data,开启CGRefImage上下文进行图片异步渲染绘制得到UIImage然后传入UIImageView。

  1. NSTimer计时器是准确的吗,为什么?
  • NSTimer计时器不是准确的。
  • 原因:定时器被添加在主线程中,由于定时器在一个RunLoop中被检测一次,所以如果在这一次的RunLoop中做了耗时的操作,当前RunLoop持续的时间超过了定时器的间隔时间,那么下一次定时就被延后了。

  1. 类的分类和类的扩展的区别,类的分类的实现原理。
  • 类的分类可以动态添加方法(运行时),类的扩展可以添加更多的属性变量(编译期)。  
  • 类的分类的实现原理:在运行时过程中,本类的方法加载完毕之后,会查询是否有类的分类,如果有类的分类,就会再去加载类的分类的方法,把这些方法全部存储到objc_class结构体的methodLists数组中。注意,这里的类的分类的方法是插入到数组的第一个元素位置的,也就是说类的分类的方法会覆盖本类的同名方法。如果有多个类的分类都包含同名函数,那么最后一个被加载进compile sources的类的分类文件中的方法将会覆盖其他的同名方法。

  1. iOS动态关联属性(objc_setAssociatedObject,objc_getAssocicatedObject)的实现原理
  • AssociationsManager 是顶级的对象,维护了一个从spinlock_t 锁到AssociationsHashMap哈希表的单例键值对映射;
  • AssociationsHashMap是一个无序的哈希表,维护了从对象地址到 ObjectAssociationMap的映射;
  • ObjectAssociationMap 是一个 C 中的 map ,维护了从 key 到 ObjcAssociation 的映射,即关联记录;
  • ObjcAssociation是一个C的类,表示一个具体的关联结构,主要包括两个实例变量,_policy 表示关联策略,_value 表示关联对象。

  1. Masonry的抗压缩属性和抗拉伸属性
  • 一般情况下,用Masonry设置两个平行的自适应Label(左面为Label1,右面为Label2),那么效果图应该如下:
  • 但是我想达到这样的效果,应该怎么设置呢?
  • 可以设置抗拉伸属性优先级,代码如下:
[label1 setContentHuggingPriority:UILayoutPriorityRequired
                          forAxis:UILayoutConstraintAxisHorizontal];
[label2 setContentHuggingPriority:UILayoutPriorityDefaultLow
                          forAxis:UILayoutConstraintAxisHorizontal];
  • 当文字过多的时候,一般效果如下:
  • 那么我想实现这样的效果,应该怎么办呢?
  • 可以设置抗压缩属性,代码如下:
[label1 setContentCompressionResistancePriority:UILayoutPriorityDefaultLow
                                        forAxis:UILayoutConstraintAxisHorizontal];
[label2 setContentCompressionResistancePriority:UILayoutPriorityRequired
                                        forAxis:UILayoutConstraintAxisHorizontal];
  1. 加密的种类,对称加密和非对称加密
  • 加密种类:
  • 对称加密算法:MD5,DES,AES。
  • 非对称加密算法:RSA等加密方式。
  • 对称加密:对称加密是最快速、最简单的一种加密方式,加密与解密用的是同样的密钥,这种方法在密码学中叫做对称加密算法。
  • 非对称加密:非对称加密为数据的加密与解密提供了一个非常安全的方法,它使用了一对密钥,公钥和私钥。私钥只能由一方安全保管,不能外泄,而公钥则可以发给任何请求它的人。非对称加密使用这对密钥中的一个进行加密,而解密则需要另一个密钥。

  1. 解释一下七层网络结构,三次握手协议和四次挥手协议
  • 七层网络协议:

    由低到高:物理层、数据链路层、网络层、传输层、表示层、会话层、应用层。
  • 三次握手协议:

    第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
    第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
    第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
  • 四次挥手协议:

    1.第一次挥手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
    2.第二次挥手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我也没有数据要发送了,可以进行关闭连接了;
    3.第三次挥手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入CLOSE_WAIT状态;
    4.第四次挥手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

  1. http和https的区别
  • https在http的基础上增加了SSL数据传输安全性认证层。具体关系如下图:
  • https协议需要到ca申请证书。http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。http的连接很简单,是无状态的。HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议。

  1. https双向验证原理
  • 1.浏览器将自己支持的一套加密规则发送给网站。
  • 2.网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。
  • 3.浏览器获得网站证书之后浏览器要做以下工作:
    a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
    b) 如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
    c) 使用约定好的HASH算法计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。
  • 4.网站接收浏览器发来的数据之后要做以下的操作:
    a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
    b) 使用密码加密一段握手消息,发送给浏览器。
  • 5.浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。
  • 具体的流程见下图:


  1. HTTP常用的头部字段,常见的返回状态码和意义
  • 常见的头部字段:

    host头域
    Host头域指定请求资源的Intenet主机和端口号,必须表示请求url的原始服务器或网关的位置。HTTP/1.1请求必须包含主机头域,否则系统会以400状态码返回。
    Referer头域
    Referer头域允许客户端指定请求uri的源资源地址,这可以允许服务器生成回退链表,可用来登陆、优化cache等。他也允许废除的或错误的连接由于维护的目的被追踪。如果请求的uri没有自己的uri地址,Referer不能被发送。如果指定的是部分uri地址,则此地址应该是一个相对地址。
    User-Agent头域
    User-Agent头域的内容包含发出请求的用户信息,User Agent也简称UA。用较为普通的一点来说,是一种向访问网站提供你所使用的浏览器类型、操作系统及版本、CPU类型、浏览器渲染引擎、浏览器语言、浏览器插件等信息的标识。
    Cache-Control头域
    Cache-Control指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached,响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age。
    Date头域
    Date头域表示消息发送的时间,时间的描述格式由rfc822定义。例如,Date:Mon,31Dec200104:25:57GMT。Date描述的时间表示世界标准时,换算成本地时间,需要知道用户所在的时区。
  • 返回的状态码:

    200(成功) 服务器已成功处理了请求。通常,这表示服务器提供了请求的网页。
    201(已创建) 请求成功且服务器已创建了新的资源。
    202(已接受) 服务器已接受了请求,但尚未对其进行处理。
    203(非授权信息) 服务器已成功处理了请求,但返回了可能来自另一来源的信息。
    204(无内容) 服务器成功处理了请求,但未返回任何内容。
    205(重置内容) 服务器成功处理了请求,但未返回任何内容。与 204 响应不同,此响应要求请求者重置文档视图(例如清除表单内容以输入新内容)。
    206(部分内容) 服务器成功处理了部分 GET 请求。
    300(多种选择) 服务器根据请求可执行多种操作。服务器可根据请求者 来选择一项操作,或提供操作列表供其选择。
    301(永久移动) 请求的网页已被永久移动到新位置。服务器返回此响应时,会自动将请求者转到新位置。您应使用此代码通知搜索引擎蜘蛛网页或网站已被永久移动到新位置。
    302(临时移动) 服务器目前正从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。会自动将请求者转到不同的位置。但由于搜索引擎会继续抓取原有位置并将其编入索引,因此您不应使用此代码来告诉搜索引擎页面或网站已被移动。
    303(查看其他位置) 当请求者应对不同的位置进行单独的 GET 请求以检索响应时,服务器会返回此代码。对于除 HEAD ##### 304(未修改) 自从上次请求后,请求的网页未被修改过。服务器返回此响应时,不会返回网页内容。如果网页自请求者上次请求后再也没有更改过,您应当将服务器配置为返回此响应。由于服务器可以告诉 搜索引擎自从上次抓取后网页没有更改过,因此可节省带宽和开销。
    305(使用代理) 请求者只能使用代理访问请求的网页。如果服务器返回此响应,那么,服务器还会指明请求者应当使用的代理。
    307(临时重定向) 服务器目前正从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求。会自动将请求者转到不同的位置。但由于搜索引擎会继续抓取原有位置并将其编入索引,因此您不应使用此代码来告诉搜索引擎某个页面或网站已被移动。
    400(错误请求) 服务器不理解请求的语法。
    401(身份验证错误) 此页要求授权。您可能不希望将此网页纳入索引。
    403(禁止) 服务器拒绝请求。
    404(未找到) 服务器找不到请求的网页。例如,对于服务器上不存在的网页经常会返回此代码。例如:http://www.0631abc.com/20100aaaa,就会进入404错误页面。
    405(方法禁用) 禁用请求中指定的方法。
    406(不接受) 无法使用请求的内容特性响应请求的网页。
    407(需要代理授权) 此状态码与 401 类似,但指定请求者必须授权使用代理。如果服务器返回此响应,还表示请求者应当使用代理。
    408(请求超时) 服务器等候请求时发生超时。
    409(冲突) 服务器在完成请求时发生冲突。服务器必须在响应中包含有关冲突的信息。服务器在响应与前一个请求相冲突的 PUT 请求时可能会返回此代码,以及两个请求的差异列表。
    410(已删除) 请求的资源永久删除后,服务器返回此响应。该代码与 404(未找到)代码相似,但在资源以前存在而现在不存在的情况下,有时会用来替代 404 代码。如果资源已永久删除,您应当使用 301 指定资源的新位置。
    411(需要有效长度) 服务器不接受不含有效内容长度标头字段的请求。
    412(未满足前提条件) 服务器未满足请求者在请求中设置的其中一个前提条件。
    413(请求实体过大) 服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。
    414(请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。
    415(不支持的媒体类型) 请求的格式不受请求页面的支持。
    416(请求范围不符合要求) 如果页面无法提供请求的范围,则服务器会返回此状态码。
    417(未满足期望值) 服务器未满足"期望"请求标头字段的要求。
    500(服务器内部错误) 服务器遇到错误,无法完成请求。
    501(尚未实施) 服务器不具备完成请求的功能。例如,当服务器无法识别请求方法时,服务器可能会返回此代码。
    502(错误网关) 服务器作为网关或代理,从上游服务器收到了无效的响应。
    503(服务不可用) 目前无法使用服务器(由于超载或进行停机维护)。通常,这只是一种暂时的状态。
    504(网关超时) 服务器作为网关或代理,未及时从上游服务器接收请求。
    505(HTTP 版本不受支持) 服务器不支持请求中所使用的 HTTP 协议版本。

  1. @class和import以及include的区别
  • import会引入整个.h头文件。
  • @class只是告诉编译器该类中可以使用这个class类名。
  • include和import的作用类似,但是可能造成重复引用的问题,一般用判断宏定义是否存在的方式来防止循环引用。

  1. weak对象的管理方式
  • weak是弱引用,所引用对象的计数器不会加一,并在引用对象被释放的时候自动被设置为nil。runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。

  • 1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。

  • 2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。

  • 3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。


  1. iOS的retain和release的操作是在编译期还是运行时进行的
  • retain和release是在编译期由编译器自动生成的代码,例如:
- (void) setUserName:(UITextField *)userName { 
    [_userName release]; 
    _userName = [userName retain]; 
}

  1. +(void)load方法和+(void)initial方法的异同
  • +(void)load方法:
  • 对于加入运行期系统的类及分类,必定会调用此方法,且仅调用一次。 iOS会在应用程序启动的时候调用load方法,在main函数之前调用 执行子类的load方法前,会先执行所有超类的load方法,顺序为父类->子类->分类 在load方法中使用其他类是不安全的,因为会调用其他类的load方法,而如果关系复杂的话,就无法判断出各个类的载入顺序,类只有初始化完成后,类实例才能进行正常使用 load 方法不遵从继承规则,如果类本身没有实现load方法,那么系统就不会调用,不管父类有没有实现(跟下文的initialize有明显区别) 尽可能的精简load方法,因为整个应用程序在执行load方法时会阻塞,即,程序会阻塞直到所有类的load方法执行完毕,才会继续 load 方法中最常用的就是方法交换method swizzling。
  • +(void)initial方法:
  • 在首次使用该类之前由运行期系统调用,且仅调用一次 惰性调用,只有当程序使用相关类时,才会调用 运行期系统会确保initialize方法是在线程安全的环境中执行,即,只有执行initialize的那个线程可以操作类或类实例。其他线程都要先阻塞,等待initialize执行完。如果类未实现initialize方法,而其超类实现了,那么会运行超类的实现代码,而且会运行两次。initialize方法是线程安全的,可以用来设置内部数据,比如,某个全局状态,如数组、字典等无法在编译期初始化,可以放在initialize里面。

  1. UIViewController的生命周期方法调用顺序
  • 当一个视图控制器被创建,并在屏幕上现实的时候。代码的执行顺序:

    1.alloc       创建对象,分配空间。
    2.init       初始化对象,初始化数据。
    3.loadView    从nib载入视图,通常这一步不需要去干涉。除非你没有使用xib文件创建视图
    4.viewDidLoad   载入完成,可以进行自定义数据以及动态的创建其他空间。
    5.viewWillAppear  视图将出现在屏幕之前。
    6.viewDidAppear  视图在屏幕上渲染完成。
  • 当一个视图被移除屏幕并且销毁的时候执行顺序:

    1.viewWillDisappear  视图被移除之前。
    2.viewDidDisappear   视图被移除之后。
    3.dealloc        销毁视图。

  1. iOS中各类控件的继承树关系
  • 一张图告诉你所有的继承关系:

  1. 如何化解NSTimer的循环引用关系
  • 首先要理解NSTimer为什么会引起循环引用:NSTimer和使用Timer的ViewController相互持有。
  • 解决办法有两个:
    1. 在ViewContoller的viewWillDisappear生命周期中注销Timer。
    2. 引入第三方NSObject(如HWWeakTimer)管理和持有Timer,让Timer持有第三方的成员变量。这样就打破了互相引用的循环关系。

  1. 怎样管理第三方SDK,CocoaPods和Carthage的异同
  • iOS中一般使用CocoaPods和Carthage来管理第三方SDK。

  • 两者的比较:

    1. Carthage只支持iOS 8及以上版本使用。
    2. CocoaPods默认会自动创建并更新你的应用程序和所有依赖的Xcode workspace。Carthage使用xcodebuild来编译框架的二进制文件,但如何集成它们将交由用户自己判断。CocoaPods的方法更易于使用,但Carthage更灵活并且是非侵入性的。
    我们创建Carthage的原因是想要一种尽可能简单的工具——一个只关心本职工作的依赖管理器,而不是取代部分Xcode的功能,或者需要让框架作者做一些额外的工作。CocoaPods提供的一些特性很棒,但由于附加的复杂性,它们将不会被包含在Carthage当中。

  1. -(BOOL)isKindOfClass和-(BOOL)isMemberOfClass的区别
  • -(BOOL) isKindOfClass: classObj 判断是否是这个类或者这个类的子类的实例

  • -(BOOL) isMemberOfClass: classObj 判断是否是这个类的实例


  1. 数据持久化的几种方式和对应的应用场景
  • plist文件(属性列表):即直接拖拽plist文件到程序目录当中。由NSBundle获取本地plist资源。存储一些本地的,且不会改变的数据到程序当中。
  • preference(偏好设置):即NSUserDefaults,存储一些小型数据,设置参数,开关属性等等。
  • NSKeyedArchiver(归档):存储一些不涉及增删改查的字典数组或者NSObject等,存储的对象一定要遵循NSCoder和NSDecoder协议。
  • SQLite 3:存储一些涉及增删改查的字段数据。
  • CoreData:效率比较高,存储一些涉及增删改查的且体积非常大的数据。

  1. 如何实现一个完整的单例
  • 实现了单例的初始化之后,一定要重写+(id) allocWithZone:(struct _NSZone *)zone,-(id) copyWithZone:(NSZone *)zone,-(id) mutablecopyWithZone:(NSZone *)zone这三个方法,代码如下:
#import "Singleton.h"
@interface Singleton()<NSCopying,NSMutableCopying>
@end
 
@implementation Singleton
 
static Singleton* _instance = nil;
 
+(instancetype) shareInstance
{
    static dispatch_once_t onceToken ;
    dispatch_once(&onceToken, ^{
        _instance = [[super allocWithZone:NULL] init] ;
        //不是使用alloc方法,而是调用[[super allocWithZone:NULL] init] 
        //已经重载allocWithZone基本的对象分配方法,所以要借用父类(NSObject)的功能来帮助出处理底层内存分配的杂物
    }) ;
     
    return _instance ;
}
 
+(id) allocWithZone:(struct _NSZone *)zone
{
    return [Singleton shareInstance] ;
}
 
-(id) copyWithZone:(NSZone *)zone
{
    return [Singleton shareInstance] ;//return _instance;
}
 
-(id) mutablecopyWithZone:(NSZone *)zone
{
    return [Singleton shareInstance] ;
}
@end

  1. iOS的系统单例有哪些?
  • [UIScreen mainScreen] (应用程序窗口)

  • [UIDevice currentDevice] (当前设备)

  • [UIApplication sharedApplication] (应用程序实例)

  • [NSNotificationCenter defaultCenter] (消息中心):

  • [NSFileManager defaultManager] (文件管理):

  • [NSUserDefaults standardUserDefaults] (应用程序设置):

  • [NSURLCache sharedURLCache] (请求缓存):

  • [NSHTTPCookieStorage sharedHTTPCookieStorage] (应用程序cookies池)


  1. APP启动主要流程
  • APP启动主要流程: 点击icon -> 加载动态链接库等 -> 映像文件加载imageLoader -> runtime -> load -> main -> delegate。

  1. iOS的沙盒机制
  • 出于安全考虑,iPhone对于安装在上面的应用程序有所限制,这个限制就是应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文本文件等。总体来说沙盒就是一种独立、安全、封闭的空间。沙盒(sandbox)的核心内容是:sandbox对应用程序执行各种操作的权限限制。

  • 沙盒的特点:

    1. 每个应用程序都有自己的存储空间。
    2. 每个应用程序都不可以翻过自己的围墙去访问别的存储空间的内容。(已经越狱的除外)
    3. 在访问别人沙盒内的数据时需要访问权限。
  • 应用程序沙盒目录下有三个文件夹Documents、Library(下面有Caches和Preferences目录)、tmp。

    Documents:保存应用运行时生成的需要持久化的数据iTunes会自动备份该目录。苹果建议将在应用程序中浏览到的文件数据保存在该目录下。
    Library/Caches:一般存储的是缓存文件,例如图片视频等,此目录下的文件不会再应用程序退出时删除,在手机备份的时候,iTunes不会备份该目录。
    Library/Preferences:保存应用程序的所有偏好设置iOS的Settings(设置),我们不应该直接在这里创建文件,而是需要通过NSUserDefault这个类来访问应用程序的偏好设置。iTunes会自动备份该文件目录下的内容。
    tmp:临时文件目录,在程序重新运行的时候,和开机的时候,会清空tmp文件夹。

以上是Fabric自己总结的一些面试心经,有兴趣的朋友可以加我微信共同讨论:justlikeitRobert。各位老铁看了我这篇文章之后,如果找到了满意的工作,别忘了和Fabric一起分享成功喜悦,一分也是爱,哈哈!

猜你喜欢

转载自juejin.im/post/5b03936a6fb9a07ac162bfe0
今日推荐