iOS基础知识汇总
- 1. #import和#include的区别? import和@class的区别? < x.h > 和 “x.h”的区别
- 2. 浅拷贝和深拷贝的区别?
- 3. OC中分类和扩展的区别?
- 4. Objective-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方法用继承好还是分类好?为什么?
- 5. Objective-C中堆和栈的区别?
- 6. 内存管理的几个原则是什么?
- 7. NSMuatableArray *array = [NSMuatableArray array] 需要释放么?
- 8. @property本质是什么
- 9. 属性关键字各是什么作用,在那种情况下用?
- 10. 什么情况使用 weak 关键字,相比 assign 有什么不同?
- 11. 用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?
- 12. 这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr;
- 13. 如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
- 14. 写一个 setter 方法用于完成 @property (nonatomic, retain) NSString *name,写一个 setter 方法用于完成 @property (nonatomic, copy) NSString *name
- 15. @synthesize 和 @dynamic 分别有什么作用?
- 16. delegate用什么属性修饰,ARC下与MRC下有何不同,为什么?
- 17. 什么时候用delete,什么时候用Notification?
- 18. “空”的不同表示方法
- 19. __block与__weak的区别是什么?
- 20. 设计模式是什么? 你知道哪些设计模式,并简要叙述?
- 21. frame 和 bounds 有什么不同?
- 22. Objective-C 中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?
- 23. GCD 与 NSOperation 的区别:
- 24. 写出使用GCD方式从子线程回到主线程的方法代码
- 25. 如何用GCD同步若干个异步调用?
- 26. dispatch_barrier_async(栅栏函数)的作用是什么?
- 27. 什么是 KVO 和 KVC?
- 28. KVC的底层实现?
- 29. KVO的底层实现?
- 30. ViewController生命周期
- 31. 如何对iOS设备进行性能测试?
- 32. 开发项目时你是怎么检查内存泄露?
- 33. 类变量的 @public,@protected,@private,@package 声明各有什么含义?
- 34. 什么是 Runtime
- 35. Runtime实现的机制是什么,怎么用,一般用于干嘛?
- 36. isa指针问题
- 37. 什么是block?
- 38. block的注意点
- 39. lldb(gdb)常用的控制台调试命令?
- 40. 你一般是怎么用Instruments的?
- 41. iOS中常用的数据存储方式有哪些?
- 42. iOS的沙盒目录结构是怎样的?
- 43. 什么是 RunLoop
- 44. tableView的重用机制?
- 45. 用伪代码写一个线程安全的单例模式
- 46. 请简单的介绍下APNS发送系统消息的机制
- 47. AFNetworking 底层原理分析
- 48. 描述下SDWebImage里面给UIImageView加载图片的逻辑
- 49. 谈谈 UITableView 的优化
- 50. 如何实行cell的动态的行高
- 51. 什么是野指针、空指针?
- 52. 什么是 OOA / OOD / OOP ?
初级iOS工程师
知道怎么做
就可以了,但是高级iOS工程师
就需要知道为什么这么做
。以下列出了一些基础知识的问题,后续慢慢完善答案。同时其中好多也是面试必问题哦!
#import和#include的区别? import和@class的区别? < x.h > 和 “x.h”的区别
#import和#include的区别?
#include
: C, C++ 中的预编译指令
在使用#include的时候要注意处理重复引用(这也是OC中#import与#include的区别)
例如: ClassA 与 ClassB 同时引用了 ClassC,不做重复引用处理的时候在 ClassD 中同时引用ClassA, ClassB 编译会提示对 ClassC 重复引用的错误。我们可以:
#ifndef _CLASSC_H
#define _CLASSC_H
#include "ClassC"
#endif
这样处理在编译时就不会有重复引用的错误出现(在Objective-C中#import解决了这个问题,这是它们的区别)
#import
: Objective-C 中的预编译指令
#import大部分功能和#include是一样的,但是他处理了重复引用的问题,我们在引用文件的时候不用再去自己进行重复引用处理.
#import和@class的区别?
#import
会链入该头文件的全部信息,包括实体变量和方法等;@class
只是告诉编译器,其后面声明的名称是类的名称,至于这些类是如何定义的,暂时不用考虑。在头文件中, 一般只需要知道被引用的类的名称就可以了。 不需要知道其内部的实体变量和方法,所以在头文件中一般使用@class来声明这个名称是类的名称。 而在实现类里面,因为会用到这个引用类的内部的实体变量和方法,所以需要使用#import来包含这个被引用类的头文件。
在编译效率方面,如果你有100个头文件都#import了同一个头文件,或者这些文件是依次引用的,如A–>B, B–>C, C–>D这样的引用关系。当最开始的那个头文件有变化的话,后面所有引用它的类都需要重新编译,如果你的类有很多的话,这将耗费大量的时间。而用@class则不会。
在循环依赖关系方面,如:A–>B, B–>A这样的相互依赖关系,如果使用#import来相互包含,那么就会出现编译错误,如果使用@class在两个类的头文件中相互声明,则不会有编译错误出现。
所以,一般来说,@class是放在interface中的,只是为了在interface中引用这个类,把这个类作为一个类型来用的。 在实现这个接口的实现类中,如果需要引用这个类的实体变量或者方法之类的,还是需要import在@class中声明的类进来。
< x.h > 和 “ x.h “的区别
<x.h>
: 它用于对系统自带的头文件的引用,编译器会在系统文件目录下去查找该文件。“x.h”
: 用户自定义的文件用双引号引用,编译器首先会在用户目录下查找,然后到安装目录中查找,最后在系统文件中查找。
浅拷贝和深拷贝的区别?
浅拷贝
:只复制指向对象的指针,而不复制引用对象本身。深拷贝
:复制引用对象本身。
意思就是说我有个A对象,复制一份后得到A_copy对象后,对于浅拷贝来说,A和A_copy指向的是同一个内存资源,拷贝的只不过是一个指针,对象本身资源还是只有一份,那如果我们对A_copy执行了修改操作,那么发现A引用的对象同样被修改。深拷贝就好理解了,内存中存在了两份独立对象本身。
通俗的话将就是:
浅拷贝好比你和你的影子,你完蛋,你的影子也完蛋
深拷贝好比你和你的克隆人,你完蛋,你的克隆人还活着。
OC中分类和扩展的区别?
分类/Category
- 就是给类添加方法的,尤其是不想生成一个新子类的情况下。
- 分类只能给指定的类添加方法,可以是自定义的类,也可以是系统的类。
- 分类中只能添加方法,不可以声明实例变量,所以属性也就没有意义,但可以访问主类的实例变量、属性。
- 分类增加的方法如果与主类的方法同名,会覆盖主类的方法,因为Category的优先级更高!
扩展/Extension
- 是分类的一种特殊形式,扩展是没有名字的分类,亦被称之为匿名分类。
- 两种创建方式:
1.创建单独的扩展头文件.h
,在其中添加方法、成员变量、属性,在被扩展的.m
文件中实现(最常见的创建方式);
2.直接在被需要扩展的.m文件中添加,在@interface中添加方法、声明成员变量、属性,在实现部分实现(也就是通常所说的私有方法、私有变量、私有属性)。
分类和扩展其实就是把原本一个文件能完成的工作根据需要拆分开,区别就是拆分的方式不一样而已。
主要有两个用途:
1.优化代码结构;
2.打包库时选择性对外暴漏接口。
Objective-C的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方法用继承好还是分类好?为什么?
Objective-C的类不可以多重继承;可以实现多个接口(协议);Category是类别;一般情况用分类好,用Category去重写类的方法,仅对本Category有效,不会影响到其他类与原有类的关系。
Objective-C中堆和栈的区别?
管理方式
- 堆:释放工作由程序员控制,容易产生memory leak。
- 栈:是由编译器自动管理,无需我们手工控制;
申请大小
- 堆:是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
- 栈:是向低地址扩展的数据结构,是一块连续的内存的区域。也就是说栈顶的地址和栈的最大容量是系统预先规定好的,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
碎片问题
- 堆:频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。
- 栈:不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出
分配方式
- 堆:是动态分配的,没有静态分配的堆。
- 栈:有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率
- 堆:则是C/C++函数库提供的,它的机制是很复杂的。
- 栈:是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
堆和栈的区别可以用如下的比喻来看出:
- 使用堆 就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
- 使用栈 就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
内存管理的几个原则是什么?
详见 “ 理解iOS的内存管理 ”
NSMuatableArray *array = [NSMuatableArray array] 需要释放么?
不需要
@property本质是什么
@property本质是:ivar(实例变量)、getter+setter(存取方法)
|
|
对setName方法的解释:
- 为何要加
name!=newName
的判断?
如果要是不加,传过来的参数如果是相同的,则newName会一直retain- 为何
[name release]
?
如果不release,直接name=[newName retain],则name原先所指向的内存则会泄露,因为没有释放
属性关键字各是什么作用,在那种情况下用?
readwrite
是可读可写特性。需要生成getter方法和setter方法。readonly
是只读特性。只会生成getter方法,不会生成setter方法,不希望属性在类外改变。assign
是赋值特性。setter方法将传入参数赋值给实例变量;仅设置变量时,assign用于基本数据类型。retain(MRC)
/strong(ARC)
表示持有特性。setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1。copy
表示拷贝特性。setter方法将传入对象复制一份,需要完全一份新的变量时。nonatomic
非原子操作。决定编译器生成的setter和getter方法是否是原子操作,atomic表示多线程安全,一般使用nonatomic,效率高。
什么情况使用 weak 关键字,相比 assign 有什么不同?
- 在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。
- 自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
IBOutlet连出来的视图属性为什么可以被设置成weak?
因为父控件的subViews数组已经对它有一个强引用。不同点:
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)。
用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?
用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
- 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
- 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。
这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr;
添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。
- 如:-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x7fcd1bc30460
- copy后返回的是不可变对象(即 arr 是 NSArray 类型,NSArray 类型对象不能调用 NSMutableArray 类型对象的方法)
原因:是因为 copy 就是复制一个不可变 NSArray 的对象,不能对 NSArray 对象进行添加/修改。
如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。具体步骤:
- 需声明该类遵从 NSCopying 协议
- 实现 NSCopying 协议的方法。
|
|
写一个 setter 方法用于完成 @property (nonatomic, retain) NSString *name,写一个 setter 方法用于完成 @property (nonatomic, copy) NSString *name
|
|
@synthesize 和 @dynamic 分别有什么作用?
@property有两个对应的词,一个是@synthesize(合成实例变量),一个是@dynamic。
如果@synthesize和@dynamic都没有写,那么默认的就是 @synthesize var = _var;
在类的实现代码里通过 @synthesize 语法可以来指定实例变量的名字。(@synthesize var = _newVar;)
@synthesize
的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。@dynamic
告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic var)。
delegate用什么属性修饰,ARC下与MRC下有何不同,为什么?
delegate
之所以用weak
来修饰,是防止循环引用,weak属性的变量是不为其所属对象持有的,并且在该变量被销毁之后,此weak
变量的值会自动被赋值为nil
。- 而
assign
属性一般是对基本数据类型成员变量的声明,当然也可以用在对象类型成员变量上,只是其代表的意义只是单纯地拷贝所赋值变量的值。即如果对某assign
成员变量B赋值某对象A的指针,则此B只是简单地保存此指针的值,且并不持有对象A,也就意味着如果A被销毁,则B就指向了一个已经被销毁的对象,如果再对其发送消息会引发崩溃。 - 那关于
delegate
既然用weak
这么好用,用assign
会出现野指针,为什么还会用呢?weak
和strong
属性是ARC
才引入的,而在MRC
情况下,只能使用assign
修饰了。weak
之所以强大的地方,是当引用的对象被销毁时,它的值也会变为nil
,所以推荐使用ARC
。
什么时候用delete,什么时候用Notification?
Delegate
(委托模式):1对1的反向消息通知功能。Notification
(通知模式):只想要把消息发送出去,告知某些状态的变化。但是并不关心谁想要知道这个。
“空”的不同表示方法
标志 | 值 | 含义 |
---|---|---|
NULL | (void *)0 | C 指针的字面空值 |
nil | (id)0 | Objective-C 对象的字面空值 |
Nil | (Class)0 | Objective-C 类的字面空值 |
NSNull | [NSNull null] | 用来表示空值的 Objective-C 对象 |
Lua
+nginx
框架中如果某一字段返回位null,直接判断过滤会失效。如:
返回字段位"Ads": null
if res_jg[“Ads”] ~= ngx.null then
__block与__weak的区别是什么?
|
|
Block、NSTime等会持有self造成循环引用的情况下使用
weakSelf
代替self
即可
设计模式是什么? 你知道哪些设计模式,并简要叙述?
设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。
MVC模式
:Model View Control,把模型 视图 控制器 层进行解耦合编写。MVVM模式
:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。单例模式
:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。观察者模式
:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。委托模式
:代理+协议的组合。实现1对1的反向传值操作。工厂模式
:通过一个类方法,批量的根据已有模板生产对象。
MVVM是对胖模型进行的拆分,其本质是给控制器减负,将一些弱业务逻辑放到VM中去处理。
frame 和 bounds 有什么不同?
frame
指的是该view在父view坐标系统中的位置和大小。(参照点是父view的坐标系统)bounds
指的是该view在本身坐标系统中的位置和大小。(参照点是本身坐标系统)
Objective-C 中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码、方法又是什么?
线程创建有三种方法:
- 使用NSThread创建
- 使用GCD的dispatch
- 使用子类化的NSOperation,然后将其加入NSOperationQueue
在主线程执行代码,方法是performSelectorOnMainThread
如果想延时执行代码可以用performSelector:onThread:withObject:waitUntilDone:
GCD 与 NSOperation 的区别:
GCD 和 NSOperation 都是用于实现多线程:
GCD
是纯C语言的API,NSOperation
是基于GCD的OC版本封装GCD
只支持FIFO的队列,NSOperation
可以很方便地调整执行顺序,设置最大并发数量NSOperationQueue
可以轻松在operation间设置依赖关系,而GCD
需要些很多代码才能实现。如:[operation2 addDependency:operation1]; //任务二依赖任务一
*NSOperationQueue
支持KVO,可以检测operation是否正在执行(isExecuted),是否结束(isFinisn),是否取消(isCancel)GCD
的执行速度比NSOperation
快
写出使用GCD方式从子线程回到主线程的方法代码
dispatch_sync(dispatch_get_main_queue(), ^{ });
如何用GCD同步若干个异步调用?
(如根据若干个url异步加载多张图片,然后在都下载完成后合成一张整图)
|
|
dispatch_barrier_async(栅栏函数)的作用是什么?
函数定义:dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
作用:
1.在它前面的任务执行结束后它才执行,它后面的任务要等它执行完成后才会开始执行。
2.避免数据竞争
|
|
什么是 KVO 和 KVC?
KVC(Key-Value-Coding)
:键值编码 是一种通过字符串间接访问对象的方式(即给属性赋值)
|
|
KVO(key-Value-Observing)
:键值观察机制 他提供了观察某一属性变化的方法,极大的简化了代码。KVO只能被KVC触发,包括使用setValue:forKey:方法和点语法。
|
|
KVC 和 KVO 的 keyPath 可以是属性、实例变量、成员变量。
KVC的底层实现?
当一个对象调用setValue方法时,方法内部会做以下操作:
- 检查是否存在相应的key的set方法,如果存在,就调用set方法。
- 如果set方法不存在,就会查找与key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
- 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值。
- 如果还没有找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。
这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
KVO的底层实现?
KVO基于runtime机制实现。
我们注册监听的时候,会对注册者动态的创建一个子类对象,然后底层找方法的的isa指针就变成指向新创建的子类对象。当改变注册对象某个属性的时候,就重写属性的set方法来进行监听。
ViewController生命周期
按照执行顺序排列:
initWithCoder
:通过nib文件初始化时触发。awakeFromNib
:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。loadView
:开始加载视图控制器自带的view。viewDidLoad
:视图控制器的view被加载完成。viewWillAppear
:视图控制器的view将要显示在window上。updateViewConstraints
:视图控制器的view开始更新AutoLayout约束。viewWillLayoutSubviews
:视图控制器的view将要更新内容视图的位置。viewDidLayoutSubviews
:视图控制器的view已经更新视图的位置。viewDidAppear
:视图控制器的view已经展示到window上。viewWillDisappear
:视图控制器的view将要从window上消失。viewDidDisappear
:视图控制器的view已经从window上消失。
如何对iOS设备进行性能测试?
Profile-> Instruments ->Time Profiler
开发项目时你是怎么检查内存泄露?
- 静态分析 analyze。
- instruments工具里面有个leak可以动态分析。
类变量的 @public,@protected,@private,@package 声明各有什么含义?
@public
任何地方都能访问;@protected
该类和子类中访问,是默认的;@private
只能在本类中访问;@package
本包内使用,跨包不可以。
什么是 Runtime
Runtime又叫运行时,是一套底层的C语言API,其为iOS内部的核心之一,我们平时编写的OC代码,底层都是基于它来实现的。
Objective-C 是一门动态语言,它会将一些工作放在代码运行时才处理而并非编译时。也就是说,有很多类和成员变量在我们编译的时是不知道的,而在运行时,我们所编写的代码会转换成完整的确定的代码运行。
因此,编译器是不够的,我们还需要一个运行时系统(Runtime system)来处理编译后的代码。
Runtime实现的机制是什么,怎么用,一般用于干嘛?
- 使用时需要导入的头文件
- Runtime 运行时机制,它是一套C语言库。
- 实际上我们编写的所有OC代码,最终都是转成了runtime库的东西。
比如:
类转成了 Runtime 库里面的结构体等数据类型,
方法转成了 Runtime 库里面的C语言函数,
平时调方法都是转成了 objc_msgSend 函数(所以说OC有个消息发送机制)
|
|
因此,可以说 Runtime 是OC的底层实现,是OC的幕后执行者。
有了Runtime库,能做什么事情呢?
Runtime库里面包含了跟类、成员变量、方法相关的API。
比如:
- 获取类里面的所有成员变量。
- 为类动态添加成员变量。
- 动态改变类的方法实现。
- 为类动态添加新的方法等。
因此,有了Runtime,想怎么改就怎么改。
isa指针问题
isa
:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调 用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身,这样形成了一个封闭的内循环。
什么是block?
闭包(block):闭包就是获取其它函数局部变量的匿名函数。
block的注意点
- 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:
|
|
- 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。
|
|
- 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。
lldb(gdb)常用的控制台调试命令?
p
输出基本类型。是打印命令,需要指定类型。是print的简写
p (int)[[[self view] subviews] count]po
打印对象,会调用对象description方法。是print-object的简写
po [self view]expr
可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。bt
打印调用堆栈,是thread backtrace的简写,加all可打印所有thread的堆栈br l
是breakpoint list的简写
你一般是怎么用Instruments的?
Instruments里面工具很多,常用:
- Time Profiler: 性能分析
- Zombies:检查是否访问了僵尸对象,但是这个工具只能从上往下检查,不智能。
- Allocations:用来检查内存,写算法的那批人也用这个来检查。
- Leaks:检查内存,看是否有内存泄露。
iOS中常用的数据存储方式有哪些?
数据存储有四种方案:NSUserDefault、KeyChain、file、DB。
其中File有三种方式:plist、Archive(归档)
DB包括:SQLite、FMDB、CoreData
iOS的沙盒目录结构是怎样的?
沙盒结构:
Application
:存放程序源文件,上架前经过数字签名,上架后不可修改。Documents
:常用目录,iCloud备份目录,存放数据。(这里不能存缓存文件,否则上架不被通过)Library
:Caches:存放体积大又不需要备份的数据。(常用的缓存路径) Preference:设置目录,iCloud会备份设置信息。
tmp
:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能。
什么是 RunLoop
从字面上讲就是运行循环,它内部就是do-while循环,在这个循环内部不断地处理各种任务。
一个线程对应一个RunLoop,基本作用就是保持程序的持续运行,处理app中的各种事件。通过runloop,有事运行,没事就休息,可以节省cpu资源,提高程序性能。
主线程的run loop默认是启动的。iOS的应用程序里面,程序启动后会有一个如下的main()函数
|
|
tableView的重用机制?
UITableView 通过重用单元格来达到节省内存的目的: 通过为每个单元格指定一个重用标识符,即指定了单元格的种类,当屏幕上的单元格滑出屏幕时,系统会把这个单元格添加到重用队列中,等待被重用,当有新单元格从屏幕外滑入屏幕内时,从重用队列中找看有没有可以重用的单元格,如果有,就拿过来用,如果没有就创建一个来使用。
用伪代码写一个线程安全的单例模式
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;
}
请简单的介绍下APNS发送系统消息的机制
APNS优势:杜绝了类似安卓那种为了接受通知不停在后台唤醒程序保持长连接的行为,由iOS系统和APNS进行长连接替代。
APNS的原理:
- 应用在通知中心注册,由iOS系统向APNS请求返回设备令牌(device Token)
- 应用程序接收到设备令牌并发送给自己的后台服务器
- 服务器把要推送的内容和设备发送给APNS
- APNS根据设备令牌找到设备,再由iOS根据APPID把推送内容展示
AFNetworking 底层原理分析
AFNetworking主要是对NSURLSession和NSURLConnection(iOS9.0废弃)的封装,其中主要有以下类:
- AFHTTPRequestOperationManager:内部封装的是 NSURLConnection, 负责发送网络请求, 使用最多的一个类。(3.0废弃)
- AFHTTPSessionManager:内部封装是 NSURLSession, 负责发送网络请求,使用最多的一个类。
- AFNetworkReachabilityManager:实时监测网络状态的工具类。当前的网络环境发生改变之后,这个工具类就可以检测到。
AFSecurityPolicy:网络安全的工具类, 主要是针对 HTTPS 服务。
AFURLRequestSerialization:序列化工具类,基类。上传的数据转换成JSON格式
(AFJSONRequestSerializer).使用不多。- AFURLResponseSerialization:反序列化工具类;基类.使用比较多:
- AFJSONResponseSerializer; JSON解析器,默认的解析器.
- AFHTTPResponseSerializer; 万能解析器; JSON和XML之外的数据类型,直接返回二进
制数据.对服务器返回的数据不做任何处理. - AFXMLParserResponseSerializer; XML解析器;
描述下SDWebImage里面给UIImageView加载图片的逻辑
SDWebImage 中为 UIImageView 提供了一个分类UIImageView+WebCache.h, 这个分类中有一个最常用的接口sd_setImageWithURL:placeholderImage:,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后再替换占位图片。
加载图片的过程大致如下:
- 首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以url 作为数据的索引先在内存中寻找是否有对应的缓存
- 如果缓存未找到就会利用通过MD5处理过的key来继续在磁盘中查询对应的数据, 如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
- 如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片
- 下载后的图片会加入缓存中,并写入磁盘中
- 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来
SDWebImage原理:
调用类别的方法:
- 从内存(字典)中找图片(当这个图片在本次使用程序的过程中已经被加载过),找到直接使用。
- 从沙盒中找(当这个图片在之前使用程序的过程中被加载过),找到使用,缓存到内存中。
- 从网络上获取,使用,缓存到内存,缓存到沙盒。
坑:如果后台把图片素材换了,但是名称路径相同,app之前有缓存,则此时加载的是旧的缓存图片,此时只要app清下缓冲,或重新安装app就可以了。
谈谈 UITableView 的优化
- 正确的复用cell。
- 设计统一规格的Cell
- 提前计算并缓存好高度(布局),因为heightForRowAtIndexPath:是调用最频繁的方法;
- 异步绘制,遇到复杂界面,遇到性能瓶颈时,可能就是突破口;
- 滑动时按需加载,这个在大量图片展示,网络加载的时候很管用!
- 减少子视图的层级关系
- 尽量使所有的视图不透明化以及做切圆操作。
- 不要动态的add 或者 remove 子控件。最好在初始化时就添加完,然后通过hidden来控制是否显示。
- 使用调试工具分析问题。
如何实行cell的动态的行高
如果希望每条数据显示自身的行高,必须设置两个属性,1.预估行高,2.自定义行高。
设置预估行高 tableView.estimatedRowHeight = 200。
设置定义行高 tableView.estimatedRowHeight = UITableViewAutomaticDimension。
如果要让自定义行高有效,必须让容器视图有一个自下而上的约束。
什么是野指针、空指针?
野指针
:不知道指向了哪里的指针叫野指针。即指针指向不确定,指针存的地址是一个垃圾值,未初始化。空指针
:不指向任何位置的指针叫空指针。即指针没有指向,指针存的地址是一个空地址,NULL。
什么是 OOA / OOD / OOP ?
OOA(Object Oriented Analysis) –面向对象分析
OOD(Object Oriented Design) –面向对象设计
OOP(Object Oriented Programming)–面向对象编程