iOS面试知识点整理

整理一下最近面试问到的知识点。

一、iOS的设计模式有哪些,简单的介绍一下:

MVC模式:Model 设置对应的属性及构造方法。View 主要负责 UI 的实现,而 UIView 所产生的事件都可以采用委托的方式,交给 UIViewController 实现。 Controller 控制器创建视图控件,并将模型数据传递给视图控件。

1)Model 和 View 永远不能相互通信,只能通过 Controller 传递。

2)Controller 可以直接与 Model 对话(读写调用 Model),Model 通过 Notification 和 KVO 机制与 Controller 间接通信。

MVVM模式:viewmodel层,对封装的network进行处理,将请求的网络数据存储在model模型里面。然后使用block带出去,方便在VC中使用处理。

主要思想: 
1 由于展示逻辑被抽取到viewmodel中,所以view中的代码变得非常轻量级。 
2 由于viewmodel中的代码与UI无关,所以它具有良好的测试性。 
3 对于一个封装大量业务逻辑的model来说,改变它可能会比较困难,并且存在一定风险,在这种场景下,viewmodel可以作为model的适配器使用。从而避免对model进行较大的改动。

MVP模式:自己没有用过,有需要的可以了解一下。

 

工厂模式:工厂方法是类方法的一种应用,工厂方法用于生成对象。工厂方法让一个类的实例化到子类中去进行。优点:极大优化代码,不用一直调用alloc方法去创建,提高代码复用性。同时可以将大量操作放到工厂类中去处理。缺点:必须存在继承关系。当所有的类不是继承自同一个父类的时候扩展比较困难。

单例模式:是一种特殊的工厂方法,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,节约系统资源。(使用场合) 在整个应用程序中,共享一份资源(这份资源只需要创建初始化一次). 例如一些网络工具类/沙盒工具类等等。单例的生命周期和操作的生命周期一样长,跟随着AppDelegate的销毁而销毁。(有的面试官也会让手写一下单例)。

 

二、内存管理的一些知识:

野指针:只要有一个对象释放了,我们就称这个对象为“僵尸对象”。当一个指针指向一个僵尸对象,我们就称这个指针为野指针。给野指针发送消息会报错。为了避免报错,一般情况下我们会在这个对象释放后将对象指针设为空指针。  =nil;

空指针:没有指向存储空间的指针(里面存的是nil)。给空指针发消息没有任何反应。

ARC的内存管理:ARC判断一个对象是否需要释放不是通过引用计数来判断的,而是通过强指针来判断的。只要有一个强指针变量指向对象,对象就会保持在内存中。

强指针:默认所有对象的指针都是强指针,被__strong修饰的指针。

弱指针:被__weak修饰的指针。用弱指针保存新创建的对象对象会被立即释放掉。(问题:如果我想用__weak来创建一个对象怎么实现?)

强引用:如果一个指针变量指向了某个对象,那么这个对象就多了一个拥有者,并且不会被程序释放,这种指针特性称为强引用。

弱引用:指针变量不影响其指向对象的拥有者个数。这种不会改变该对象拥有者个数的指针特性称为弱引用。弱引用适合解决强引用循环的内存管理问题。

weak:weak指针不会增加其指向对象的引用计数。当其指向对象销毁后,weak指针自动设置为nil,避免出现野指针。

copy:分为深拷贝和浅拷贝

浅拷贝:也称指针拷贝,副本和源对象是一个对象,源对象的引用计数+1,本质是增加了一个指向源对象的指针。

深拷贝:源对象的引用计数不变,本质是产生新对象。此外复制还有copy和mutableCopy之分。

copy:能接收copy消息的类必须遵循NSCopying协议。对系统的非容器对象,copy是浅拷贝。对系统的容器类,如果是不可变容器,copy是浅拷贝。对系统的容器类,如果是可变容器,copy是深拷贝,但其返回的是不可变对象。

mutableCopy:必须遵循NSMutableCopying协议。对所有的类,NSMutable都是深拷贝。

 

1.字符串的声明用 copy 还是 strong?

 当原字符串是NSString类型时,由于它是不可变类型的,不管是使用strong特性,还是copy特性的对象,它们所指向的地址都跟原字符串是一样的,都指向原字符串对象。也就是说当原字符串是NSString类型时,copy特性的操作,只是做了一次浅拷贝,只是增加了指针指向原字符串所指向的地址。

       当原字符串是NSMutableString类型时,strong特性对象只是增加了原字符串的引用计数,但是copy特性对象则是对原字符串进行了深拷贝,创建了一个新对象,并且指向了这个新对象。此时,copy特性对象是NSString类型的不可变的,strong特性对象是NSMutableString类型的可变的。

但是我们在大多数情况下,在声明NSString属性时,都是希望其不被改变,防止数据出错。所以大多数情况下还是选择copy特性,从而来避免一些无法预估的bug。

2.代理的声明为什么用weak ?

delegate 之所以用weak来修饰,是防止循环引用,weak属性的变量是不为其所属对象持有的,并且在该变量被销毁之后,此weak变量的值会自动被赋值为nil。而assign属性一般是对C基本数据类型成员变量的声明,当然也可以用在对象类型成员变量上,只是其代表的意义只是单纯地拷贝所赋值变量的值。即如果对某assign成员变量B赋值某对象A的指针,则此B只是简单地保存此指针的值,且并不持有对象A,也就意味着如果A被销毁,则B就指向了一个已经被销毁的对象,如果再对其发送消息会引发崩溃。

那关于delegate 既然用weak这么好用,用assign会出现野指针,为什么还会用呢?

weak和strong属性是ARC才引入的,而在MRC情况下,只能使用assign修饰了。weak之所以强大的地方,是当引用的对象被销毁时,它的值也会变为nil,所以推荐使用ARC。

当自己在使用前人写好的MRC代码,那这些delegate怎么来使用呢?

像这样写在ViewController被销毁时,将delegate制空。在ARC模式下,我们不需要继承父类的dealloc方法(不用写[super dealloc])

-(void)dealloc {

    self.XXX.delegate = nil;

 

关于MRC的一些知识可以自己去学习。

 

三、iOS runtime消息机制方法调用流程:

OC 在向一个对象发送消息时,runtime库会根据对象的ISA指针找到改对象的对应的类或其父类中查找方法。如果在顶层父类中依然找不到对应的方法时,程勋会在运行时抛出异常并挂掉。但在这之前,runtime会给出三次拯救机会。(具体ISA指针怎么查找方法,三次拯救机会可自己去学习)。

runtime常用的方法:

1、动态交换两个方法的实现(系统方法拦截)。

2、给分类动态扩展属性。我们知道在分类中不能添加成员属性,虽然用了@property,但是仅仅会自动生成get和set方法的声明,并没有带下划线的属性和方法实现的生成。可以用runtime来实现。

3、runtime字典转模型。

4、动态添加方法。处理一个未实现的方法和去除报错。

5、实现NSCoding的自动归档和解归档。

 

四、简单说一下KVC、KVO

KVC:键值编码,通过键值路径为对象的属性赋值。

KVO:键值监听,当指定的对象属性被修改后,则对象会收到通知。KVO的底层实现原理是系统给当前的类创建子类,在子类setter方法中调用父类的setter方法,通过修改ISA指针指向系统创建的子类实现当前属性值改变的监听。

KVO使用场景:监听模型属性实时更新UI。

如果赋值没有通过setter方法或者KVC ,而是直接修改属性对应的成员变量,例如仅调用 _name = @"newName"; 这时是不触发KVO机制,更不会调用回调方法。

 

五、block

定义block时,用copy修饰。因为block创建时默认是创建在栈上的, 超过作用域后就会被销毁, 只有使用copy才会生成一个堆block, 在作用域外被访问。

block循环引用问题:由于block会对block中的对象进行持有操作,就相当于持有了其中的对象,而如果此时block中的对象又持有了该block,则会造成循环引用。

解决办法:使用__weak修饰self,使其在block中不被持有,打破循环引用。

 

block访问外部变量时,block会对内部变量进行一次临时的拷贝,把栈区的地址拷贝到堆区,block在内部操作的是副本,对block外部变量的真实值不会造成影响。

 

当在Block内部”修改“外部变量,不被允许。如果非要在Block内部修改外部变量,需要使用__block修饰外部变量。__block修饰外部变量作用:使外部变量可以在Block内部修改。被__block标记的外部变量,一旦在Block内部”使用过“,那么Block对外部变量的拷贝就不是临时的了;Block外部变量的真实值就会发生变化,那么这个变量的地址在后续的使用中都是堆区地址。

六、多线程

进程:正在进行中的程序被称为进程,负责程序运行的内存分配;每一个进程都有自己独立的虚拟内存空间。

线程:线程是进程中一个独立的执行路径(控制单元);一个进程中至少包含一条线程,即主线程,线程是进程的最小执行单位。

队列:dispatch_queue_t,一种先进先出的数据结构,线程的创建和回收不需要程序员操作,由队列负责。

           串行:队列中的任务只会顺序执行。

           并行:队列中的任务通常会并发执行。

           全局队列:是系统开发的,直接拿过来(get)用就可以;与并行队列类似,但调试时,无法确认操作所在队列

        dispatch_queue_t q = dispatch_get_global_queue(dispatch_queue_priority_default, 0)。

           主队列:每一个应用程序对应唯一一个主队列,直接get即可;在多线程开发中,使用主队列更新UI

      dispatch_queue_t q = dispatch_get_main_queue();

操作:

          异步:dispatch_async 异步操作,会并发执行,无法确定任务的执行顺序;

          同步:dispatch_sync 同步操作,会依次顺序执行,能够决定任务的执行顺序;

 

GCD 

GCD 的创建 :

// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

对于串行队列,GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)。所有放在主队列中的任务,都会放到主线程中执行。可使用dispatch_get_main_queue()获得主队列。

// 主队列的获取方法
dispatch_queue_t queue = dispatch_get_main_queue();

对于并发队列,GCD 默认提供了全局并发队列(Global Dispatch Queue)。可以使用dispatch_get_global_queue来获取。需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。

// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

GCD任务的创建:

GCD 提供了同步执行任务的创建方法dispatch_sync和异步执行任务创建方法dispatch_async

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 这里放异步执行任务代码
});

GCD线程间的通讯:

在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

/**
 * 线程间通信
 */
- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    
    dispatch_async(queue, ^{
        // 异步追加任务
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });
}

GCD栅栏方法 dispatch_barrier_async :

我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。这就需要用到dispatch_barrier_async方法在两个操作组间形成栅栏。
dispatch_barrier_async函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在dispatch_barrier_async函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。

GCD的延迟执行方法 dispatch_after :

我们经常会遇到这样的需求:在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的dispatch_after函数来实现。
需要注意的是:dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的。

GCD 只执行一次 dispatch_once :

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数。使用
dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。

GCD快速迭代方法 dispatch_apply:

通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_applydispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。

如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是异步队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法

GCD任务组 dispatch_group

有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。(比如同时下载多张图片,最后一张图片下载完成时合并这些图片。)

调用队列组的 dispatch_group_async先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave组合 来实现
dispatch_group_async

调用队列组的 dispatch_group_notify回到指定线程执行任务。或者使用 dispatch_group_wait回到当前线程继续向下执行(会阻塞当前线程)。

dispatch_group_notify : 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。

//下载2张图片,下载完之后合并图片
- (void)downloadImgAndEdit {
    // 创建一个组
    dispatch_group_t group = dispatch_group_create();
    
    // 开启一个任务下载图片1
    __block UIImage *image1 = nil;
    dispatch_group_async(group, global_queue, ^{
        //下载操作
    });
    
    // 开启一个任务下载图片2
    __block UIImage *image2 = nil;
    dispatch_group_async(group, global_queue, ^{
        //下载操作
    });
    
    // 同时执行下载图片1和下载图片2操作
    
    // 等group中的所有任务都执行完毕, 再回到主线程执行其他操作
    dispatch_group_notify(group, main_queue, ^{
        self.imageView1.image = image1;
        self.imageView2.image = image2;
        // 合并
        UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0);
        [image1 drawInRect:CGRectMake(0, 0, 100, 100)];
        [image2 drawInRect:CGRectMake(100, 0, 100, 100)];
        self.bigImageView.image = UIGraphicsGetImageFromCurrentImageContext();
        // 关闭上下文
        UIGraphicsEndImageContext();
    });
}

 

dispatch_group_wait :  暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。

 

NSOperation

操作(Operation):

执行操作的意思,换句话说就是你在线程中执行的那段代码。

在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。

操作队列(Operation Queues):

这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。

操作队列通过设置 最大并发操作数(maxConcurrentOperationCount) 来控制并发、串行。

NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。

NSOperation 实现多线程的使用步骤分为三步:

创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。

创建队列:创建 NSOperationQueue 对象。

将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。

之后呢,系统就会自动将 NSOperationQueue 中的 NSOperation 取出来,在新线程中执行操作。

NSOperation 操作依赖

NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序。NSOperation 提供了3个接口供我们管理和查看依赖。

- (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。

- (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。

@property (readonly, copy) NSArray *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

NSOperation 优先级

NSOperation 提供了queuePriority(优先级)属性,queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。

 

NSOperation与GCD比较的优点:

1、在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);

2、NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;

3、我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;

4、在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;

5、我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

 

多线程的死锁

所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

向一个串行队列中同步添加任务就一定会导致死锁。

 

多线程数据竞争

是指多个线程在没有正确加锁的情况下,同时访问同一块数据,并且至少有一个线程是写操作,对数据的读取和修改产生了竞争,从而导致各种不可预计的问题。

解决办法:使用dispatch_barrier_async 将write操作和前后的read操作屏蔽开。

 

七、RunLoop

 

Runloop是一个对象,这个对象在循环中用来处理程序在运行过程中出现的各种事件(比如触摸,UI刷新,定时器事件,selector事件)从而保证程序的持续运行;而且在程序没有事件处理的时候处于休眠状态,从而节省CPU资源,提高程序性能。

苹果不允许直接创建RunLoop,它提供了两个自动获取的函数: CFRunLoopGetMain()和CFRunLoopGetCurrent(). 线程和RunLoop之间是一一对应的。线程创建时并没有RunLoop,且如果不去获取,那么RunLoop一直都不存在. RunLoop的创建发生在第一次获取的时候( 除了主线程的RunLoop,你只能在一个线程的内部获取自己对应的RunLoop ) ;RunLoop的销毁发生在线程结束后;主线程的RunLoop是程序开始就创建的;子线程的RunLoop在第一次获取RunLoop对象时创建的;

关于定时器与页面滑动:

     很多人在不懂之前,写的定时器与页面滑动的事件相冲突,即页面滑动时,定时器不工作: 主要原因是页面滑动时,主线程的RunLoop会停止,并且以  UITrackingRunLoopMode形式启动,这个时候schedule方式生成的NSTimer处在KCFRunLoopDefaultMode下,所以不会被运行;解决办法时,将NSTimer注册到kCFRunLoopCommonModes下,则NSTimer在Mode切换时仍然可以运行;

 

八、响应者链 (UIResponder)

在iOS中UIResponder类是专门用来响应用户的操作处理各种事件的,包括触摸事件(Touch Events)、运动事件(Motion Events)、远程控制事件(Remote Control Events,如插入耳机调节音量触发的事件)。我们知道UIApplication、UIView、UIViewController这几个类是直接继承自UIResponder,UIWindow是直接继承自UIView的一个特殊的View,所以这些类都可以响应事件。当然我们自定义的继承自UIView的View以及自定义的继承自UIViewController的控制器都可以响应事件。iOS里面通常将这些能响应事件的对象称之为响应者。

响应者链条的传递过程是:由第一响应者(对于触摸事件来说是hist-test view)开始向上传递。如果该视图是控制器的根视图,先传递给控制器,再传递给父视图,如果不是控制器的根视图,直接传递给父视图。
只要在响应者的处理方法里面调用父类的方法,就可以让多个视图和控制器响应同一个事件,响应者链条的根本目的是:共享事件,让多个视图和控制器可以对同一事件做不同的处理。

 

九、socket  TCP于UDP的区别 网络的七层协议 

 

谈到任何联网的协议,我们就必须要谈到OSI(网络七层协议模型),必须遵循这个协议模型,我们的手机和电脑才可以联网通信,首先来看一下OSI。

OSI是一个开放性的通信系统互连参考模型,他是一个定义得非常好的协议规范。OSI模型有7层结构,每层都可以有几个子层。

网络的七层协议:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。

http协议对应于应用层

tcp协议对应于传输层

ip协议对应于网络层

三者本质上没有可比性。  何况HTTP协议是基于TCP连接的。

简单了解OSI之后我们来看一下我们手机与电脑通信,所能够使用的两种数据通信,一种是HTTP请求,一种是Socket通信,HTTP是属于短连接,适合新闻,订票信息等客户端发起请求,每一次请求结束,自动断开连接。而Socket是属于长连接,适合游戏,聊天等实时数据。

 

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket,另一个运行于服务器端,称为ServerSocket。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

建立起一个TCP连接需要经过“三次握手”。断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”。具体的自己去看。

 

TCP 和 UDP 的区别

  TCP UDP
是否连接 面向连接 面向非连接
传输可靠性 可靠 不可靠
应用场合 传输大量数据 传输少量数据
速度

 

 

十、XML跟json的区别,json的底层原理

XML底层原理:XML解析常用的解析方法有两种:DOM解析和SAX解析。DOM采用建立树形结构的方式访问XML文档,而SAX采用的事件模型。DOM解析把XML文档转化为一个包含其内容的树,并可以对树进行遍历。使用DOM解析器的时候需要处理整个XML文档转化为一个包含其内容的树,并可以对树进行遍历。使用DOM解析器的是时候需要处理整个XML文档,所以对性能和内存的要求比较高。SAX在解析xml文档的时候可以出发一系列的事件爱你,当发现给定的tag的时候,他可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX对内存的要求通常会比较低,因为它让开发人员自己来决定要处理的tag。特别是当开发人员只需要处理文档中所包含的部分数据时,SAX这种扩展功能得到了更好的体现。

JSON底层原理:遍历字符串中的字符,最终根据格式规定的特殊字符,比如{}号,[]号,:号等进行区分,{}号是一个字典的开始,[]号是一个数组的开始,:号是字典的键和值的分水岭,最终仍是将json数据转化为字典,字典中值可能是字典,数组,或字符串而已。

1)可读性方面:基本相同,xml的可读性比较好

2)可扩展性方面:都具有很好的扩展性

3)编码难度方面:相对而言:JSON的编码比较容易

4)解码难度:json解码难度基本为零,xml需要考虑子节点和父节点

5)数据体积方面:json相对于xml来讲,数据体积小,传递的速度更快些

6)数据交互方面:json与JavaScrpit的交互更加方便,更容易解析处理,更好的数据交互

7)数据描述方面:xml对数据描述性比较好

8)传输速度方面:json的速度远远快于xml。

 

十一、APP安全相关的

1、不要 在plist文件、项目中的静态文件中 存储关键的信息,如果要保存,记得 对称加密。

2、NSUserdefault 中不要保存关键信息,如果要保存,还是加密吧。sqlite也是这样子的。

3、接口如何保证安全呢?首先用HTTPS,虽然HTTPS已经很安全了,但是数据也是有可能被破解的。所以 接口一定要自己加密。接口如果用对称加密,密钥 放到代码里 是能被反编译出来的。如果你的 APP的安全性很高,就不要把密钥 写到代码里。

4、密钥要定期更换。比如 3个月 或半年换一次,如果密钥是从接口通过非对称加密 获取的,直接修改服务端就可以了。

5、类名方法名混淆,程序代码混淆。

 

十二、NSObject 中 load 于 initialize 方法的区别

 

iOS面试知识点整理_第1张图片

十三、开发中对线上bug的处理

 

在xcode中菜单的window下选择organizer,在打开的窗口中选择Crashes,这样Xcode会开始下载相关的崩溃信息到本地中(网络环境不好时可能要等待一些时间)。在崩溃信息这一栏苹果会按照崩溃数量排序,将崩溃数量最多的排在最前。右侧的详细信息会显示是崩溃时的调用堆栈,可以看到是哪行代码导致的崩溃。选中要解决的崩溃后,可以在窗口右侧选择open in project。在打开的项目中,会直接定位到崩溃的那行代码。在解决完这个crash后可以标记为已经解决。

 

十四、使用autolayout布局获取frame 注意什么:

要调用super view的 layoutIfNeeded 方法。或者在 viewDidLayout方法里获取最好。

 

十五、WKWebView UIWebView相关知识及与JS的交互

UIWebView

JS调OC方法:

1、需要引入系统库JavaScriptCore.framework。2、创建webView。3、创建JSContext桥梁.4、在webViewDidFinishLoad方法中调与后台规定名称的js方法

WKWebView

iOS8之后我们使用WebKit框架中的WKWebView来加载网页。相比UIWebView,在性能、稳定性、占用内存方面有很大提升;允许JavaScript的Nitro库加载并使用(UIWebView中限制);增加加载进度属性:estimatedProgress,不用在自己写假进度条了;支持了更多的HTML的属性。

WKWebView中的WKUIDelegate实现UI弹出框的一些处理(警告面板、确认面板、输入框)。

JS调iOS-JS端必须使用window.webkit.messageHandlers.JS_Function_Name.postMessage(null),其中JS_Function_Name是iOS端提供个JS交互的Name。

如果message.body中没有参数,JS代码中需要传null防止iOS端不会接收到JS的交互。

 

十六、tableViewCell自适应高度

当我们在cell中进行高度计算然后return高度,再在控制器中调用是不可取的,原因在于tableView的代理方法heightForRowAtIndexPath会在cellForRowAtIndexPath之前调用。

方法一:在model中计算cell的高度,然后在heightForRowAtIndexPath返回高度。

方法二:iOS 8之后 设置预估行高,开启cell的自适应,在cell里设置好约束(注意竖向约束要设置好,并且label的左右约束也要设置好),并且注释掉heightForRowAtIndexPath代理方法

 

十七、本地存储的一些方法,SQLit的使用

NSUserDefaults优点:不需要关心文件名;快速进行键值对存储;直接存储基本数据类型。

缺点:不能存储自定义数据;取出的数据都是不可变的。

NSKeyedArchiver、NSKeyedUnarchiver: 存储一些自定义数据对象。归档需要遵循NSCoding协议。

plist存储: plist存储的不是数组就是字典。plist不能存储自定义对象。

CoreData :   

                NSManagedObjectContext 管理对象,上下文,持久性存储模型对象,处理数据与应用的交互。

                NSManagedObjectModel 被管理的数据模型,数据结构。

                NSManagedObject 被管理的数据记录

                NSFetchRequest 数据请求

                NSEntityDescription 表格实体结构

 

 

十八、webView中如果网页内有异步请求或者重定向时,就会多次调用webViewDidFinishLoad方法,怎么解决?

解决方法是使用webView.isLoading属性。

 

十九、tableView delegate调用顺序

1. 有多少个分区。

- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView

2. 估算header和Footer视图高度。(若没有,执行11,14)

-   (CGFloat)tableView:(UITableView*)tableView 

estimatedHeightForHeaderInSection:(NSInteger)section

-   (CGFloat)tableView:(UITableView*)tableView

estimatedHeightForFooterInSection:(NSInteger)section

3. 每个分区有多少行

   - (NSInteger)tableView:(UITableView*)tableView 

numberOfRowsInSection:(NSInteger)section

4. 估算每个cell的高度(若没有,执行8)

- (CGFloat)tableView:(UITableView*)tableView 

estimatedHeightForRowAtIndexPath:(NSIndexPath*)indexPath

-------------------------------------------------------------

5. 右边索引的titles。

 - (nullableNSArray *)sectionIndexTitlesFor

 TableView:(UITableView*)tableView

  —————————————多少行重复执行—————————-———

6.  每个cell的内容。

- (UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath

7.  设置indexPath上的每一行的cell的缩进

- (NSInteger)tableView:(UITableView*)tableView indentationLevelForRowAtIndexPath:(NSIndexPath*)indexPath

8. 依次计算可视区域里每个cell的高度

- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath

9.  某一行是否可以编辑(删除)

- (BOOL)tableView:(UITableView*)tableView canEditRowAtIndexPath:(NSIndexPath*)indexPath

10.将要展示Cell视图

- (void)tableView:(UITableView*)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath

------------------------------------------------------------

11.header视图高度(若没有估算,即2,此方法已执行,不再执行)

- (CGFloat)tableView:(UITableView*)tableView 

heightForHeaderInSection:(NSInteger)section

12.header视图内容(若没有视图,将显示header标题)

- (nullableUIView*)tableView:(UITableView*)tableView 

viewForHeaderInSection:(NSInteger)section

   - (NSString*)tableView:(UITableView*)tableView  

   titleForHeaderInSection:(NSInteger)section

13.将要展示header视图

- (void)tableView:(UITableView*)tableView 

willDisplayHeaderView:(UIView*)view 

forSection:(NSInteger)section

14.footer视图高度(若没有估算,即2,此方法已执行,不再执行)

 - (CGFloat)tableView:(UITableView*)tableView 

heightForFooterInSection:(NSInteger)section

15.footer视图内容(若没有视图,将显示footer标题)

   - (nullableUIView*)tableView:(UITableView*)tableView 

   viewForFooterInSection:(NSInteger)section

   - (NSString*)tableView:(UITableView*)tableView  

   titleForFooterInSection:(NSInteger)section

16.将要展示footer视图

- (void)tableView:(UITableView*)tableView willDisplayFooterView:(nonnullUIView*)view forSection:(NSInteger)section

 

 

 

 

 

你可能感兴趣的:(iOS,面试问题)