1.说一下OC的反射机制
所有的oc 对象都是继承于nsobject nsobject 提供了如下反射方法
反射方法
//SEL和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
// Class和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
FOUNDATION_EXPORT Class __nullable NSClassFromString(NSString *aClassName);
// Protocol和字符串转换
FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) NS_AVAILABLE(10_5, 2_0);
FOUNDATION_EXPORT Protocol * __nullable NSProtocolFromString(NSString *namestr) NS_AVAILABLE(10_5, 2_0);
通过上面的方法,我们可以动态的去选择创建哪个实例,并选择调用哪个方法,我们可以根据服务器 定制的参数来动态实例化我们的对象。
我们可以使用反射来实现 工厂模式。
2、block的实质是什么?有几种block?分别是怎样产生的?
block的实质就是一个结构体的oc 对象,里面又一个isa指针指向自己的类
根据isa指针,block一共有3种类型的block
- _NSConcreteGlobalBlock 全局静态
- _NSConcreteStackBlock 保存在栈中,出函数作用域就销毁
- _NSConcreteMallocBlock 保存在堆中,
3、__block修饰的变量为什么能在block里面能改变其值?
因为变量通过__block 修饰后 定义后的内存地址和block内部变量的内存地址 指向的是同一个,所以是同一个变量 所以在 block内部能改变值。
4.说一下线程之间的通信
nsthreed
线程间通信常用方法
// waitUntilDone的含义:
// 如果传入的是YES: 那么会等到主线程中的方法执行完毕, 才会继续执行下面其他行的代码
/*
* 回到主线程执行,执行self的showImage方法,参数是image
*/
[self performSelectorOnMainThread:@selector(showImage:) withObject:image waitUntilDone:YES];
/*
* 回到xx线程执行aSelector方法,参数是arg
*/
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
GCD
异步下载 完成后回到主线程 执行
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 1.下载图片(耗时)
dispatch_async(queue, ^{
NSLog(@"%@", [NSThread currentThread]);
// 1.创建URL
NSURL *url = [NSURL URLWithString:@"http://stimgcn1.s-msn.com/msnportal/ent/2015/08/04/7a59dbe7-3c18-4fae-bb56-305dab5e6951.jpg"];
// 2.通过NSData下载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 3.将NSData转换为图片
UIImage *image = [UIImage imageWithData:data];
// 4.更新UI
// self.imageView.image = image;
// NSLog(@"更新UI完毕");
// 如果是通过异步函数调用, 那么会先执行完所有的代码, 再更新UI
// 如果是同步函数调用, 那么会先更新UI, 再执行其它代码
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread currentThread]);
self.imageView.image = image;
NSLog(@"更新UI完毕");
});
NSLog(@"Other");
});
NSOperation
// 1.创建一个新的队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.添加任务(操作)
[queue addOperationWithBlock:^{
//完成操作
...................
//回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.imageView.image = image;
}];
}];
2. 添加依赖
NSOperation之间可以设置依赖来保证执行顺序,⽐如一定要让操作A执行完后,才能执行操作B,可以像下面这么写
[operationB addDependency:operationA]; // 操作B依赖于操作
5.说一下hash算法原理
散列表,它是基于高速存取的角度设计的,也是一种典型的“空间换时间”的做法。顾名思义,该数据结构能够理解为一个线性表,可是当中的元素不是紧密排列的,而是可能存在空隙。
散列表(Hash table,也叫哈希表),是依据关键码值(Key value)而直接进行訪问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来訪问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
6.说一下nsdictionary 的实现原理
NSDictionary(字典)是使用 hash表来实现key和value之间的映射和存储的, hash函数设计的好坏影响着数据的查找访问效率。数据在hash表中分布的越均匀,其访问效率越高。而在Objective-C中,通常都是利用NSString 来作为键值,其内部使用的hash函数也是通过使用 NSString对象作为键值来保证数据的各个节点在hash表中均匀分布。
9、遇到过BAD_ACCESS的错误吗?你是怎样调试的?
BAD_ACCESS 报错属于内存访问错误,会导致程序崩溃,错误的原因是访问了野指针(悬挂指针)。野指针指的是本来指针指向的对象已经释放了,但指向该对象的指针没有置 nil,指针指向随机的未知的内存
1.开启僵尸(NSZombieEnabled)对象诊断 快速找到问题代码所在
2.内存泄漏检测 (Analyze)
10、什么是指针常量和常量指针?
指针常量:指针本身是一个常量。
常量指针:指向的对象是一个常量。
11、不借用第三个变量,如何交换两个变量的值?要求手动写出交换过程。
int a = 4;
int b = 8;
方法一 .算数运算
a = a+b;
b = a - b;
a = a - b;
方法 二 位运算
a = a^b;
b = a^b;
a = a^b;
NSLog(@"%li---%li",a,b);
13、如何去设计一个方案去应对后端频繁更改的字段接口?
14、KVO、KVC的实现原理
kvc: Key-Value Coding: 键值编码 (KVC)
KVC运用了一个isa-swizzling技术. isa-swizzling就是类型混合指针机制, 将2个对象的isa指针互相调换, 就是俗称的黑魔法.
KVC主要通过isa-swizzling, 来实现其内部查找定位的. 默认的实现方法由NSOject提供
[object setValue:@"134567" forKey:@"uid"];
就会被编译器处理成:
// 首先找到对应sel
SEL sel = sel_get_uid("setValue:forKey:");
// 根据object->isa找到sel对应的IMP实现指针
IMP method = objc_msg_lookup (object->isa,sel);
// 调用指针完成KVC赋值
method(object, sel, @"134567", @"uid");
Key-Value Observing 键值观察(KVO), KVO是观察者模式的一种应用
kvo :基于kvc 的基础上添加了通知的思想。
// 添加一个观察者
[self.object addObserver:self forKeyPath:@"uid" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil];
// 观察者监听到之后回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
NSLog(@"keyPath: %@", keyPath);
NSLog(@"object: %@", object);
NSLog(@"change: %@", change);
NSLog(@"context: %@", context);
}
// 移除观察者, 类在销毁前需要销毁
[self.object removeObserver:self forKeyPath:@"uid"];
15、用递归算法求1到n的和
16、category为什么不能添加属性?
在分类里使用@property声明属性,只是将该属性添加到该类的属性列表,但是没有生成相应的成员变量,也没有实现setter和getter方法。
成员变量是一个类的东西,分类本身就不是一个类,分类本来就是OC里面通过运行时动态的为一个类添加的一些方法和属性等,不是一个真正的类,你怎么给他添加成员变量呢?所以必须得用到高大上的运行时了。
总结一下,其实分类中是可以为一个类添加属性的,但是一定做不到添加成员变量,不要混淆了成员变量和属性的概念
其实属性是可以添加的。只是说现在Xcode自动会给属性生成成员变量让大家对这个概念有点混淆。Property是Property,Ivar是Ivar。
分类里面不能添加Ivar是因为分类本身并不是一个真正的类,它并没有自己的ISA。有兴趣可以研究一下类是怎么被创建出来的,类最开始生成了很多基本属性,比如IvarList,MethodList,分类只会将自己的method attach到主类,并不会影响到主类的IvarList。这就是为什么分类里面不能增加成员变量的原因。
17、说一下runloop和线程的关系。
runloop 本质是一个线程死循环,和线程相辅相成的 是为线程而生的 ,为线程提供保活,监听消息唤起线程的执行。没事干就睡觉。
18、说一下autoreleasePool的实现原理。
单个自动释放池的执行过程就是objc_autoreleasePoolPush() —> [object autorelease] —> objc_autoreleasePoolPop(void *)。
void *
objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
看到这里,我们发现这两个函数的实现都是调用了AutoreleasePoolPage类中的方法。于是我们可以断定,AutoreleasePool的是通过AutoreleasePoolPage类来实现的
magic:用来校验 AutoreleasePoolPage 的结构是否完整;
next:指向栈顶,也就是最新入栈的autorelease对象的下一个位置;
thread:指向当前线程;
parent:指向父节点
child:指向子节点
depth:表示链表的深度,也就是链表节点的个数
hiwat:表示high water mark(最高水位标记
通AutoreleasePoolPage类属性,我们可以推断出,这是一个双向链表的节点,AutoreleasePool的内存结构就是一个双向链表。
autoreleasepool的内存结构是一个双向链表栈,会频繁的有入栈和出栈操作,栈中存放的对象也会有增有减,hiwat就记录了入栈对象最多时候对象的个数
19、说一下简单工厂模式,工厂模式以及抽象工厂模式?
简单工厂模式:工厂可以创建同一系列的产品,产品的接口一致,但工厂就要根据参数进行判断到底创建哪种产品
卖早饭的张婆婆:可以做茶叶蛋,包子,稀饭
工厂模式:可以有多种工厂,工厂有共同的接口,一个工厂只能产生一种产品,比起简单工厂,工厂方法就不需要判断,耦合度低了不少
刘老板:只卖包子的包子铺,只卖稀饭的稀饭庄
抽象工厂模式:可以产生多个系列的产品,有2个维度的产品
KFC老板:可乐系列产品、汉堡系列产品,每种系列产品又分大,中,小三种。
如果这样来看应该很容易就能区分他们之间的关系了。
抽象工厂模式的优点:
(1)分离接口和实现
客户端使用抽象工厂来创建需要的对象,而客户端根本就不知道具体的实现是谁,客户端只是面向产品的接口编程而已。也就是说,客户端从具体的产品实现中解耦。
(2)使切换产品族变得容易
因为一个具体的工厂实现代表的是一个产品族,比如上面例子的从Intel系列到AMD系列只需要切换一下具体工厂。
抽象工厂模式的缺点:
不太容易扩展新的产品:如果需要给整个产品族添加一个新的产品,那么就需要修改抽象工厂,这样就会导致修改所有的工厂实现类。
总结
抽象工厂模式是一种极为常见的设计模式。它是最基本的,因为它可以涉及许多类型的对象创建。一系列相关的类,应该作为一种抽象,不为客户端所见。抽象工厂则可以方便的解决这个问题,而不暴露创建过程中任何不必要的细节或所创建对象的确切类型。
20、如何设计一个网络请求库?
21、说一下多线程,你平常是怎么用的?
22、说一下UITableViewCell的卡顿你是怎么优化的?
1.cell 重用机制。
2.提前创建我们的UI对象。
3.避免发生离屏渲染
4.图片异步加载缓存处理
5.对应的cell 高度缓存 避免重复计算。
6.尽量减少子视图创建的数量。
23、看过哪些三方库?说一下实现原理以及好在哪里?
24、说一下HTTP协议以及经常使用的code码的含义。
25、设计一套缓存策略。
26、设计一个检测主线和卡顿的方案。
27、说一下runtime,工作是如何使用的?看过runtime源码吗?
消息转发 objc_sendMessage()
IMP指针是指向实现函数的指针,通过SEL取得IMP,objc_msgSend来执行实现方法
28、说几个你在工作中使用到的线程安全的例子。
1.使用atomic多线程原子性控制,atomic的原理给setter加上锁,只能保证setter方法的完整性。getter不会加锁
2.单例 使用GCD dispatch_once 保证线程安全和初始化唯一
3.多次循环处理 添加 nslock锁 保证线程安全防止内存泄漏。
29、用过哪些锁?哪些锁的性能比较高?
1.NSLock
2.NSConditionLock;条件锁 --- 条件锁是一个互斥锁,可以通过特定值来锁住和解锁
3.NSRecursiveLock;递归锁 --- 对同一线程,可以多次获得(lock)而不会造成死锁
30、说一下HTTP和HTTPs的请求过程?
31、说一下TCP和UDP
TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于广播和细节控制交给应用的通信传输
UDP(User Datagram Protocol)
UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。 即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。
TCP(Transmission Control Protocol)
TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。
小结TCP与UDP的区别:
1.基于连接与无连接;
2.对系统资源的要求(TCP较多,UDP少);
3.UDP程序结构较简单;
4.流模式与数据报模式 ;
5.TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。
32、说一下静态库和动态库之间的区别
静态库:链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝。
动态库:链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用(如系统的UIKit.framework等),节省内存。
33、load和initialize方法分别在什么时候调用的?
load 方法是在程序编译加载的时候调用。
initialize 是在第一次使用的时候 也就是第一次收到消息的时候
详解: https://blog.csdn.net/runintolove/article/details/52350070
34、NSNotificationCenter是在哪个线程发送的通知?
35、用过swift吗?如果没有,平常有学习吗?
36、说一下你对架构的理解?
第一,你必须要有抽象的能力,抽象的能力最基本就是去重,去重在整个架构中体现在方方面面,从定义一个函数,到定义一个类,到提供的一个服务,以及模板,背后都是要去重提高可复用率。
第二, 分类能力。做软件需要做对象的解耦,要定义对象的属性和方法,做分布式系统的时候要做服务的拆分和模块化,要定义服务的接口和规范。
第三, 算法(性能),它的价值体现在提升系统的性能,所有性能的提升,最终都会落到CPU,内存,IO和网络这4大块上。
37、为什么一定要在主线程里面更新UI?
1、在子线程中是不能进行UI 更新的,而可以更新的结果只是一个幻像:因为子线程代码执行完毕了,又自动进入到了主线程,执行了子线程中的UI更新的函数栈,这中间的时间非常的短,就让大家误以为分线程可以更新UI。如果子线程一直在运行,则子线程中的UI更新的函数栈 主线程无法获知,即无法更新
2、只有极少数的UI能,因为开辟线程时会获取当前环境,如点击某个按钮,这个按钮响应的方法是开辟一个子线程,在子线程中对该按钮进行UI 更新是能及时的,如换标题,换背景图,但这没有任何意义
网友评论