2020-07-05
-
1. copy和strong的区别 为什么不可变对象要用copy
strong此特质标明该属性定义了一种拥有关系。为这种属性设定新值时,设置方法会先保留新值再释放旧值,然后再讲新值设置上去。
copy 设置方法并不保留新值而是将其拷贝
当属性类型为NSString*时,经常用此特性来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例这个类是NSString的子类,表示一种可以修改其值得字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以这时就要拷贝一份不可变的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是可变的,就应该在设执行属性是拷贝一份。 -
2.为什么block要使用copy而不是strong或者其他属性修饰?
block本身是像对象一样可以retain,和release。但是,block在创建的时候,它的内存是分配在栈上的,而不是在堆上。他本身的作于域是属于创建时候的作用域,一旦在创建时候的作用域外面调用block将导致程序崩溃。因为栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区.
使用retain也可以,但是block的retain行为默认是用copy的行为实现的,
因为block变量默认是声明为栈变量的,为了能够在block的声明域外使用,所以要把block拷贝(copy)到堆,所以说为了block属性声明和实际的操作一致,最好声明为copy。 -
3.深拷贝和浅拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享一块内存
深拷贝指针和对象的拷贝,新旧对象不会共享一块内存 -
4.有哪些锁
-
5.weak如何实现自动赋nil
-
6.GET和POST的区别
GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
HTTP是什么?HTTP是基于TCP/IP的关于数据如何在万维网中如何通信的协议。 -
7.iOS中nil, Nil, NULL和NSNull的区别
- nil:对象为空
- Nil:类为空
- NULL:基本数据对象指针为空
- NSNull:集合对象无法包含 nil 作为其具体值,如NSArray、NSSet和NSDictionary。相应地,nil值用一个特定的对象 NSNull 来表示。NSNull 提供了一个单一实例用于表示集合对象属性中的的nil值。
-
8、手机适配一些方案
-
9、真机调试、项目上线注意事项
-
10.静态方法是否能被重写
静态方法其实就是方法 可以被重写 -
11.向一个nill对象发送消息会发生什么?
-
12.iOS 类(class)和结构体(struct)有什么区别?
Swift 中,类是引用类型,结构体是值类型。值类型在传递和赋值时将进行复制,而引用类型则只会使用引用对象的一个"指向"。所以他们两者之间的区别就是两个类型的区别。 -
13. KVO 的底层实现原理
(1)KVO 是基于 runtime 机制实现的
(2)当一个对象(假设是person对象,对应的类为 JLperson)的属性值age发生改变时,系统会自动生成一个继承自JLperson的类NSKVONotifying_JLPerson,在这个类的 setAge 方法里面调用
[super setAge:age];
[self willChangeValueForKey:@"age"];
[self didChangeValueForKey:@"age"];
三个方法,而后面两个方法内部会主动调用
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context方法,在该方法中可以拿到属性改变前后的值.
KVO的作用
作用:能够监听某个对象属性值的改变 -
14. 死锁
就是队列任务的循环等待。
四个条件
互斥条件:一个资源只能被一个线程使用.在一段时间内,资源已被占有.如果此时有请求获取该资源,则该请求只能等待.
请求与保持条件:线程已保持了至少一个资源,但又从新提出获取新的资源请求,而该资源已被其他线程占有.此时该请求将被阻塞等待,对于已获得的资源保持不放.
不可剥夺条件:线程获得资源在未使用完成之前,不能被其他线程强行剥夺,只能该线程自己放弃.
循环等待条件:若干线程形成首尾相接循环等待资源的关系. -
15.isKindOfClass和isMemberOfClass的区别
isKindOfClass:确定一个对象是否是一个类的成员,或者是该类的子类成员
isMemberOfClass:只能确定一个对象是否是当前类的成员. -
16 runtime的用途
1.做用户埋点统计
2 处理异常崩溃(NSDictionary, NSMutableDictionary, NSArray, NSMutableArray 的处理)
3 按钮最小点击区设置
4 按钮重复点击设置
5 手势的重复点击处理
6 UIButton点击事件带多参数
7 MJRefresh封装
8 服务端控制页面跳转
9 字典转模型 -
17 说一下响应链的原理
当用户点击屏幕后,UIApplication 先响应事件,然后传递给UIWindow。如果window可以响应。就开始遍历window的subviews。遍历的过程中,如果第一个遍历的view1可以响应,那就遍历这个view1的subviews(依次这样不停地查找,直至查找到合适的响应事件view)。如果view1不可以响应,那就开始对view2进行判断和子视图的遍历。依次类推view3,view4…… 如果最后没有找到合适的响应view,这个消息就会被抛弃。(整个遍历的过程就是树的先序遍历)
通过上面的 hitTest:withEvent: 寻找到第一响应者后,需要逆着寻找第一响应者的方向(从第一响应者->UIApplication)来响应事件。
-
18. hitTest有尝试过重写吗?
重写过,在用runtime给按钮扩大点击区域的分类里面,重写了hittest,在方法里面判断点击区域和自己设置的是否相等,如果相等,返回self,让自己成为第一响应者
19.iOS中内存管理?
iOS中内存管理的方式主要有三大,1.taggedPointer,2.NONPOINTER_ISA,3.散列表。
20. KVO
KVO的实现用了一种叫 isa-swizzling 的技术。
当一个对象的一个属性注册了观察者后,被观察对象的isa指针的就指向了一个系统为我们生成的中间类NSKVONotifying_,而不是我们自己创建的类。在这个类中,系统为我们重写了被观察属性的setter方法。
通过 object_getClass(id obj) 方法可以获得实例对象真实的类(isa指针的指向)。
可以看到 _NSSetObjectValueAndNotify 还是调用了 willChangeValueForKey: 和 didChangeValueForKey: 来进行手动通知的。
21.UIView & CALayer的区别
联系
每一个 view 中都有一个 layer,view 持有并管理这个 layer,且这个 view 是 layer 的代理。
区别
UIView 负责响应事件,参与响应链,为 layer 提供内容。
CALayer 负责绘制内容,动画。
22. 什么是离屏渲染
GPU 在当前屏幕缓冲区之外另开一片内存空间进行渲染操作。
GPU 在绘制时没有回头路,某一个 layer 在被绘制之后就已经与之前的若干层形成一个整体了,无法再次修改。
对于一些有特殊效果的 layer,GPU 无法通过单次遍历就能完成渲染,只能另外申请一片内存区域,借助这个临时区域来完成更加复杂、多次的修改与裁减操作。
大小是屏幕像素点的2.5倍
需要创建新的渲染缓冲区,会存在不小的内存开销
且需要切换渲染上下文 Context Switch,会浪费很多时间
23 http 和https
24.什么是中间人攻击
中间人攻击的定义:中间人攻击是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。
简单来说,攻击者在请求和响应传输途中,拦截并篡改内容
- 24 autoreleasepool及runloop的关系
autoreleasepool简单说是双向链表,每张链表头尾相接,有 parent、child指针
每创建一个池子,会在首部创建一个 哨兵 对象,作为标记
最外层池子的顶端会有一个next指针。当链表容量满了,就会在链表的顶端,并指向下一张表
1.@autoreleasepool展开来其实就是objc_autoreleasePoolPush和objc_autoreleasePoolPop,但是这两个函数也是封装的一个底层对象AutoreleasePoolPage,实际对应的是AutoreleasePoolPage::push和AutoreleasePoolPage::pop
2.autoreleasepool本身并没有内部结构,而是一种通过AutoreleasePoolPage为节点的双向链表结构
3.根据AutoreleasePoolPage双向链表的结构,可以看到当调用objc_autoreleasePoolPush的时候实际上除了初始化poolpage对象属性之外,还会插入一个POOL_SENTINEL哨兵,用来区分不同autoreleasepool之间包裹的对象。
4.当对象调用 autorelease 方法时,会将实际对象插入 AutoreleasePoolPage 的栈中,通过next指针移动。
5.autoreleasePoolPage的结构字段上面有介绍,其中每个双向链表的node节点也就是poolpage对象内存大小为4096,除了基础属性之外,外插一个POOL_SENTINEL,每出现一个@autorelease就会有一个哨兵,剩下的通过begin和end来标识是否存储满,满了就会重新创建一个poolpage来链接链表,按照这个套路,出现一个PoolPush就创建一个哨兵,出现一个对象的autorelease,就增加一个实际的对象,满了就创建新的链表节点这样衍生下去
6.AutoreleasePoolPage::pop那么当调用pop的时候,会传入需要drain的哨兵节点,遍历该内存地址上方所有对象,直到遇到对应的哨兵,然后释放栈中遍历到的对象,每删除一页就修正双向链表的指针,最后两张图很容易理解
7.ARC下,直接调用上面的方法,整个线程都被自动释放池双向链表管理,Push创建的时候插入哨兵对象,当我们在内部写代码的时候,会自动添加Autorelease,对象会加入到在哨兵节点之间,加入到next指针上,一个个往后移,满了4096就换下一个poolPage对象节点来存储,出了释放池,会调用pop,传入自动释放池的哨兵给pop,然后遍历哨兵内存地址之后的所有对象执行release,最后吧next指针移到目标哨兵
App
启动后,苹果在主线程 RunLoop
里注册了两个Observer
,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()
。
第一个 Observer
监视的事件是 Entry(即将进入Loop)
,其回调内会调用 _objc_autoreleasePoolPush()
创建自动释放池。其 order
是 -2147483647
,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer
监视了两个事件: BeforeWaiting
(准备进入休眠) 时调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
释放旧的池并创建新池;Exit(即将退出Loop)
时调用 objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer
的 order
是 2147483647
,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
- 25 访问 __weak 修饰的变量,是否已经被注册在了 @autoreleasePool 中?为什么?
答案是肯定的,__weak修饰的变量属于弱引用,如果没有被注册到 @autoreleasePool 中,创建之后也就会随之销毁,为了延长它的生命周期,必须注册到 @autoreleasePool 中,以延缓释放。
25 categray相关
https://www.jianshu.com/p/78e18cdbe23f
- 26 dispatch_once实现原理
+ (instancetype)sharedInstance
{
/*定义相应类实例的静态变量;
意义:函数内定义静态变量,无论该函数被调用多少次,
在内存中只初始化一次,并且能保存最后一次赋的值
*/
static ClassName *instance = nil;
/*定义一个dispatch_once_t(其实也就是整型)静态变量,
意义:作为标识下面dispatch_once的block是否已执行过。
static修饰会默认将其初始化为0,当值为0时才会执行block。
当block执行完成,底层会将onceToken设置为1,这也就是为什
么要传onceToken的地址(static修饰的变量可以通过地址修改
onceToken的值),同时底层会加锁来保证这个方法是线程安全的
*/
static dispatch_once_t onceToken;
/*只要当onceToken == 0时才会执行block,否则直接返回静态变量instance*/
dispatch_once(&onceToken, ^{
instance = [[ClassName alloc] init];
//...
});
return instance;
}
- 27 散列表 哈希函数
散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来
散列冲突
开放寻址法(open addressing)和链表法(chaining)
我们用装载因子(load factor)来表示空位的多少。
装载因子的计算公式是:
散列表的装载因子 = 填入表中的元素个数 / 散列表的长度
装载因子越大,说明空闲位置越少,冲突越多,散列表的性能会下降。
散列表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中
当数据量比较小、装载因子小的时候,适合采用开放寻址法
基于链表的散列冲突处理方法比较适合存储大对象、大数据量的散列表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表。
哈希算法:
将任意长度的二进制值串映射为固定长度的二进制值串,这个映射的规则就是哈希算法,而通过原始数据映射之后得到的二进制值串就是哈希值
哈希算法应用:
分别是安全加密、唯一标识、数据校验、散列函数、负载均衡、数据分片、分布式存储。
- 28 快速排序
#define MAXSIZE 10000
typedef struct
{
//r[0]用作哨兵或临时变量
int r[MAXSIZE+1];
int length;
}SqList;
void swap(SqList *L, int i, int j) {
int a = L->r[i];
int b = L->r[j];
a ^= b;
b ^= a;
a ^= b;
}
int Partition(SqList *L, int low, int high) {
int pivotkey;
pivotkey = L->r[low];
while (low < high) {
while (low < high && L->r[high] >= pivotkey)
high--;
swa(L, low, high);
while (low < high && L->r[low] <= pivotkey)
low++;
swa(L, low, high);
}
return low;
}
void QSort(SqList *L, int low, int high) {
int pivot;
if (low<high) {
pivot = Partition(L, low, high);
QSort(L, low, pivot-1);
QSortd(L, pivot+1, high);
}
}
void QuickSort3(SqList *L) {
QSortd(L, 0, L->length);
}
- 值类型和引用类型的区别
1、速度上的区bai别
值类型存du取速度快,引用类型zhi存取速度慢。
2、用途上的区别
值类型表示实际数据,引用类dao型表示指向存储在内存堆中的数据的指针或引用。
3、来源上的区别
值类型继承自System.ValueType,引用类型继承自System.Object
4、位置上的区别
值类型的数据存储在内存的栈中,引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。
5、类型上的区别
值类型的变量直接存放实际的数据,而引用类型的变量存放的则是数据的地址,即对象的引用。
6、保存位置上的区别
值类型变量直接把变量的值保存在堆栈中,引用类型的变量把实际数据的地址保存在堆栈中,而实际数据则保存在堆中。