2019面试题(中)

209 阅读12分钟

二十八.什么是懒加载?

答:懒加载就是只在用到的时候才去初始化。也可以理解成延时加载。

我觉得最好也最简单的一个例子就是tableView中图片的加载显示了, 一个延时加载, 避免内存过高,一个异步加载,避免线程堵塞提高用户体验。

二十九.类变量的 @public,@protected,@private,@package 声明各有什么含义?

@public 任何地方都能访问;

@protected 该类和子类中访问,是默认的;

@private 只能在本类中访问;

@package 本包内使用,跨包不可以。

三十.在手势对象基础类UIGestureRecognizer的常用子类手势类型中哪两个手势发生后,响应只会执行一次?

答:UITapGestureRecognizer,UISwipeGestureRecognizer是一次性手势,手势发生后,响应只会执行一次。

三十一.什么是谓词?

谓词就是通过NSPredicate给定的逻辑条件作为约束条件,完成对数据的筛选。

//定义谓词对象,谓词对象中包含了过滤条件(过滤条件比较多)
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];
//使用谓词条件过滤数组中的元素,过滤之后返回查询的结果
NSArray *array = [persons filteredArrayUsingPredicate:predicate];

三十二.isa指针问题

isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调 用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。

三十三.如何访问并修改一个类的私有属性?

  1. KVC

我们可以用setValue:的方法设置私有属性,并利用valueForKey:的方法访问私有属性。假设我们有一个类Person,并且这个类有一个私有属性name。看代码:

    Person * ls = [[Person alloc] init];
    [ls setValue:@"wo" forKey:@"name"];
  1. runtime

我们可以利用runtime获取某个类的所有属性(私有属性、非私有属性),在获取到某个类的属性后就可以对该属性进行访问以及修改了。

三十四.一个objc对象的isa的指针指向什么?有什么作用?

isa 指的就是 是个什么,对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。isa帮助一个对象找到它的方法。isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

三十五.isKindOfClass、isMemberOfClass、selector作用分别是什么

isKindOfClass:作用是某个对象属于某个类型或者继承自某类型。

isMemberOfClass:某个对象确切属于某个类型。

selector:通过方法名,获取在内存中的函数的入口地址。

三十六.delegate 和 notification 的区别

  1. 二者都用于传递消息,不同之处主要在于一个是一对一的,另一个是一对多的。

  2. notification通过维护一个array,实现一对多消息的转发。

  3. delegate需要两者之间必须建立联系,不然没法调用代理的方法;notification不需要两者之间有联系。

三十七.什么是block?

闭包(block):闭包就是获取其它函数局部变量的匿名函数。

三十八.block的注意点

  1. 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:__weak typeof(self) weakSelf = self;
  2. 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。__strong typeof(self) strongSelf = weakSelf;
  3. 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。

四十.lldb(gdb)常用的控制台调试命令?

  1. p 输出基本类型。是打印命令,需要指定类型。是print的简写p (int)[[[self view] subviews] count]
  2. po 打印对象,会调用对象description方法。是print-object的简写po [self view]
  3. expr 可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。
  4. bt:打印调用堆栈,是thread backtrace的简写,加all可打印所有thread的堆栈
  5. br l:是breakpoint list的简写

四十一.你一般是怎么用Instruments的?

Instruments里面工具很多,常用:

  1. Time Profiler: 性能分析

  2. Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。

  3. Allocations:用来检查内存,写算法的那批人也用这个来检查。

  4. Leaks:检查内存,看是否有内存泄露。

四十二.iOS中常用的数据存储方式有哪些?

数据存储有四种方案:NSUserDefault、KeyChain、file、DB。

其中File有三种方式:plist、Archive(归档)

DB包括:SQLite、FMDB、CoreData

四十三.iOS的沙盒目录结构是怎样的?

沙盒结构:

  1. Application:存放程序源文件,上架前经过数字签名,上架后不可修改。
  2. Documents:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)
  3. Library:Caches:存放体积大又不需要备份的数据。(常用的缓存路径) Preference:设置目录,iCloud会备份设置信息。
  4. tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。

四十四.iOS多线程技术有哪几种方式?

答:pthread、NSThread、GCD、NSOperation

四十五.GCD 与 NSOperation 的区别:

GCD 和 NSOperation 都是用于实现多线程:

GCD 基于C语言的底层API,GCD主要与block结合使用,代码简洁高效。

NSOperation 属于Objective-C类,是基于GCD更高一层的封装。复杂任务一般用NSOperation实现。

四十六.写出使用GCD方式从子线程回到主线程的方法代码

答:dispatch_sync(dispatch_get_main_queue(), ^{ });

四十七.如何用GCD同步若干个异步调用?(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)

// 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。
// 创建队列组
dispatch_group_t group = dispatch_group_create();
// 获取全局并发队
列dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{ /*加载图片1 */ });
dispatch_group_async(group, queue, ^{ /*加载图片2 */ });
dispatch_group_async(group, queue, ^{ /*加载图片3 */ });
// 当并发队列组中的任务执行完毕后才会执行这里的代码
dispatch_group_notify(group, dispatch_get_main_queue(), ^{        // 合并图片});

四十八.dispatch_barrier_async(栅栏函数)的作用是什么?

函数定义:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

作用:

  1. 在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。

  2. 避免数据竞争

// 1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 2.向队列中添加任务
dispatch_async(queue, ^{  // 1.2是并行的   
    NSLog(@"任务1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"任务2, %@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
    NSLog(@"任务 barrier, %@", [NSThread currentThread]);
});
dispatch_async(queue, ^{   // 这两个是同时执行的
    NSLog(@"任务3, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
    NSLog(@"任务4, %@",[NSThread currentThread]);
});
// 输出结果: 任务1 任务2 ——》 任务 barrier ——》任务3 任务4
// 其中的任务1与任务2,任务3与任务4 由于是并行处理先后顺序不定。

四十九.什么是 RunLoop

从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。

一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。

主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

五十.什么是 Runtime

Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。

五十一.Runtime实现的机制是什么,怎么用,一般用于干嘛?

  1. 使用时需要导入的头文件 <objc/message.h> <objc/runtime.h>

  2. Runtime 运行时机制,它是一套C语言库。

  3. 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。

    比如:

    类转成了 Runtime 库里面的结构体等数据类型,

    方法转成了 Runtime 库里面的C语言函数,

    平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)

    // OC是动态语言,每个方法在运行时会被动态转为消息发送,即:

    objc_msgSend(receiver, selector)。

    // [stu show];  在objc动态编译时,会被转意为:objc_msgSend(stu, @selector(show));

  4. 因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。

    有了Runtime库,能做什么事情呢?

    Runtime库里面包含了跟类、成员变量、方法相关的API。

    比如:

    (1)获取类里面的所有成员变量。

    (2)为类动态添加成员变量。

    (3)动态改变类的方法实现。

    (4)为类动态添加新的方法等。

    因此,有了Runtime,想怎么改就怎么改。

五十二.什么是 Method Swizzle(黑魔法),什么情况下会使用?

  1. 在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法 Method Swizzle。
  2. Method Swizzle 指的是改变一个已存在的选择器对应的实现的过程。OC中方法的调用能够在运行时通过改变,通过改变类的调度表中选择器到最终函数间的映射关系。
  3. 在OC中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用OC的动态特性,可以实现在运行时偷换selector对应的方法实现。
  4. 每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的方法实现。
  5. 我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP。
  6. 我们可以利用 class_replaceMethod 来修改类。
  7. 我们可以利用 method_setImplementation 来直接设置某个方法的IMP。
  8. 归根结底,都是偷换了selector的IMP。

五十三._objc_msgForward 函数是做什么的,直接调用它将会发生什么?

答:_objc_msgForward是 IMP 类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。

五十四.什么是 TCP / UDP ?

  • TCP:传输控制协议。

  • UDP:用户数据协议。

TCP 是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议。

UDP 是面向无连接的,数据传输是不可靠的,它只管发,不管收不收得到。

简单的说,TCP注重数据安全,而UDP数据传输快点,但安全性一般。

五十五.通信底层原理(OSI七层模型)

OSI采用了分层的结构化技术,共分七层:

物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。

五十六.OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?

// 创建线程的方法
 [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]
 [self performSelectorInBackground:nil withObject:nil];
 [[NSThread alloc] initWithTarget:nil selector:nil object:nil];
 dispatch_async(dispatch_get_global_queue(0, 0), ^{});
 [[NSOperationQueue new] addOperation:nil];
 // 主线程中执行代码的方法
 [self performSelectorOnMainThread:nil withObject:nil waitUntilDone:YES];
 dispatch_async(dispatch_get_main_queue(), ^{});
 [[NSOperationQueue mainQueue] addOperation:nil];

五十七.用伪代码写一个线程安全的单例模式

static id _instance;
(id)allocWithZone:(struct _NSZone *)zone {
  static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
     _instance = [super allocWithZone:zone];
  });
    return _instance;
}
(instancetype)sharedData {
    static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
             _instance = [[self alloc] init];
 });
  return _instance;
}
 -(id)copyWithZone:(NSZone *)zone {
  return _instance;
}

五十八.HTTP协议中 POST 方法和 GET 方法有那些区别?

  1. GET用于向服务器请求数据,POST用于提交数据

  2. GET请求,请求参数拼接形式暴露在地址栏,而POST请求参数则放在请求体里面,因此GET请求不适合用于验证密码等操作

  3. GET请求的URL有长度限制,POST请求不会有长度限制

五十九.请简单的介绍下APNS发送系统消息的机制

APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。

APNS的原理:

  1. 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)

  2. 应用程序接收到设备令牌并发送给自己的后台服务器

  3. 服务器把要推送的内容和设备发送给APNS

  4. APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示

今天的分享就到这里了,明天继续