一、设计模式是什么?你知道哪些设计模式,并简要叙述?
设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一类类型的事情。
1. MVC模式:Model View Control,把模型、视图、控制器层进行解耦编写
2. MVVM模式:Model View ViewModel 把模型、视图、业务进行解耦编写
3. 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。
4. 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。
5. 委托模式:代理 + 协议的组合。实现1对1的反向传值操作。
6. 工厂模式:通过一个类方法,批量的根据已有的模板生产对象。
二、MVC和MVVM的区别
1. MVVM是对胖模型进行的拆分,其本质是给控制器减负,将一些弱业务逻辑放到VM中去处理。
2. MVC是一切设计的基础,所有新的设计模式都是基于MVC进行的改进。
三、#import跟 #include 有什么区别,@class呢,#import<> 跟 #import””有什么区别?
1. #import 是OC导入头文件的关键字, #include是C/C++导入狗文件的关键字,使用#import头文件会自动导入一次,不会重复导入。
2. @class告诉编辑器某个类的声明,当执行时,才去查看类的实现文件,可以解决头文件的相互包含。
3. #import<> 用来包含系统的头文件,#import""用来包含用户头文件。
四、@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的
@property = ivar + getter + setter;
“属性” (property)有两大概念:ivar(实例变量)、getter+setter(存取方法)
五、@property中有哪些属性关键字?/ @property 后面可以有哪些修饰符?
属性可以拥有的特质分为四类:
1.原子性--- nonatomic 特质
2.读/写权限---readwrite(读写)、readonly (只读)
3.内存管理语义---assign、strong、 weak、unsafe_unretained、copy
4.方法名---getter=<name> 、setter=<name>
5.不常用的:nonnull,null_resettable,nullable
六、属性关键字 readwrite,readonly,assign,retain,copy,nonatomic 各是什么作用,在那种情况下用?
1). readwrite 是可读可写特性。需要生成getter方法和setter方法。
2). readonly 是只读特性。只会生成getter方法,不会生成setter方法,不希望属性在类外改变。
3). assign 是赋值特性。setter方法将传入参数赋值给实例变量;仅设置变量时,assign用于基本数据类型。
4). retain(MRC)/strong(ARC) 表示持有特性。setter方法将传入参数先保留,再赋值,传入参数的retaincount会+1。
5). copy 表示拷贝特性。setter方法将传入对象复制一份,需要完全一份新的变量时。
6). nonatomic 非原子操作。决定编译器生成的setter和getter方法是否是原子操作,atomic表示多线程安全,一般使用nonatomic,效率高。
7). atomic读写安全,但效率低,不是绝对的安全,比如操作数组,增加或移除,这种情况可以使用互斥锁来保证线程安全
8). weak不改变修饰对象的引用计数,对象释放后,weak指针自动置为空
七、什么情况使用 weak 关键字,相比 assign 有什么不同?
1.在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性。
2.自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong。
IBOutlet连出来的视图属性为什么可以被设置成weak?
因为父控件的subViews数组已经对它有一个强引用。
不同点:
assign 可以用非 OC 对象,而 weak 必须用于 OC 对象。
weak 表明该属性定义了一种“非拥有关系”。在属性所指的对象销毁时,属性值会自动清空(nil)。
八、怎么用 copy 关键字?
用途:
1. NSString、NSArray、NSDictionary 等等经常使用copy关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary;
2. block 也经常使用 copy 关键字。
说明:
block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。
九、用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?
用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作(就是把可变的赋值给不可变的),为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。
1. 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本。
2. 如果我们使用是 strong ,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
//总结:使用copy的目的是,防止把可变类型的对象赋值给不可变类型的对象时,可变类型对象的值发送变化会无意间篡改不可变类型对象原来的值。
例如:
使用strong关键字
1607666476653.jpg结果为:
allDataMArr = (
1,
2,
3,
e
)
dataArr = (
1,
2,
3,
e
)
不可变的数组使用了strong关键字,当给可变数组添加了一个字符串的时候,不可变数组同时变了。
这是由于当不可变数组使用strong关键字的时候,allDataMArr给dataArr赋值是dataArr对象的指针指向的内存区域是原来allDataMArr指针指向的内存区域,只是在该内存区域的引用计数上加1,并没有重新拷贝这块内存区域,所以当allDataMArr修改了这块内存区域的时候,dataArr打印的值同样发生了变化。
使用copy关键字
1607666332990.jpg结果为:
allDataMArr = (
1,
2,
3,
e
)
dataArr = (
1,
2,
3
)
不可变的数组使用了copy关键字,当给可变数组添加了一个字符串的时候,不可变数组不影响。
这是由于当不可变数组使用copy关键字的时候,allDataMArr给dataArr赋值是dataArr对象的指针指向的内存区域拷贝了allDataMArr指针指向的内存区域,和allDataMArr没有关系了这时,所以当allDataMArr修改了这块内存区域的时候,dataArr打印的值不会发生了变化。
十、浅拷贝和深拷贝的区别?
浅拷贝:只复制指向对象的指针,而不复制引用对象本身。
深拷贝:复制引用对象本身。内存中存在了两份独立对象本身,当修改A时,A_copy不变。
十一、copy和mutableCopy
1. copy返回的是不可变独享
2. mutableCopy 返回的是可变对象
一、非集合对象的copy与mutableCopy
在非集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;
对可变对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:
NSString *str = @"hello word!";
NSString *strCopy = [str copy] // 指针复制,strCopy与str的地址一样
NSMutableString *strMCopy = [str mutableCopy] // 内容复制,strMCopy与str的地址不一样
NSMutableString *mutableStr = [NSMutableString stringWithString: @"hello word!"];
NSString *strCopy = [mutableStr copy] // 内容复制
NSMutableString *strMCopy = [mutableStr mutableCopy] // 内容复制
二、集合类对象的copy与mutableCopy (同上)
在集合类对象中,对不可变对象进行copy操作,是指针复制,mutableCopy操作是内容复制;
对可变对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对集合内的对象元素仍然是指针复制。(即单层内容复制)
NSArray *arr = @[@[@"a", @"b"], @[@"c", @"d"];
NSArray *copyArr = [arr copy]; // 指针复制
NSMutableArray *mCopyArr = [arr mutableCopy]; //单层内容复制
NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArr = [mutableArr copy]; // 单层内容复制
NSMutableArray *mCopyArr = [mutableArr mutableCopy]; // 单层内容复制
【总结一句话】:
只有对不可变对象进行copy操作是指针复制(浅复制),其它情况都是内容复制(深复制)!
十二、这个写法会出什么问题:@property (nonatomic, copy) NSMutableArray *arr;
问题:添加,删除,修改数组内的元素的时候,程序会因为找不到对应的方法而崩溃。
原因:是因为 copy 就是复制一个不可变 NSArray 的对象,不能对 NSArray 对象进行添加/修改。
十三、如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
具体步骤:
1. 需声明该类遵从 NSCopying 协议
2. 实现 NSCopying 协议的方法。
// 该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
// 注意:使用 copy 修饰符,调用的是copy方法,其实真正需要实现的是 “copyWithZone” 方法。
十四、MRC中写一个 setter 方法用于完成 @property (nonatomic, retain) NSString *name,写一个 setter 方法用于完成 @property (nonatomic, copy) NSString *name
// retain
- (void)setName:(NSString *)str {
[str retain];
[_name release];
_name = str;
}
// copy
- (void)setName:(NSString *)str {
id t = [str copy];
[_name release];
_name = t;
}
十五、@synthesize 和 @dynamic 分别有什么作用?
@property有两个对应的词,一个是@synthesize(合成实例变量),一个是@dynamic。
如果@synthesize和@dynamic都没有写,那么默认的就是 @synthesize var = _var;
// 在类的实现代码里通过 @synthesize 语法可以来指定实例变量的名字。(@synthesize var = _newVar;)
1. @synthesize 的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
2. @dynamic 告诉编译器,属性的setter与getter方法由用户自己实现,不自动生成(如,@dynamic var)。
十六、NSInteger和int区别
NSInteger是基本数据类型Int或者Long的别名(NSInteger的定义typedef long NSInteger),它的区别在于,NSInteger会根据系统是32位还是64位来决定是本身是int还是long。
十七、KVC的底层实现?
当一个对象调用setValue方法时,方法内部会做以下的操作:
1. 检查是否存在相应的key和set方法,如果存在,就调用set方法
2. 如果set方法不存在,就会查找key相同名称并且带下划线的成员变量,如果有,则直接给成员变量属性赋值。
3. 如果没有找到_key, 就会查找相同名称的属性key,如果有就直接赋值。
4. 如果还没有找到,则调用vauleForUndefinedKey:和
setVaule:forUndefinedKey:方法。
十八、推送原理
1.由App向iOS设备发送一个注册通知,用户需要同意系统发送推送。
2.iOS向APNs远程推送服务器发送App的Bundle Id和设备的UDID。
3.APNs根据设备的UDID和App的Bundle Id生成deviceToken再发回给App。
4.App再将deviceToken发送给远程推送服务器(自己的服务器), 由服务器保存在数据库中。
5.当自己的服务器想发送推送时, 在远程推送服务器中输入要发送的消息并选择发给哪些用户的deviceToken,由远程推送服务器发送给APNs。
6.APNs根据deviceToken发送给对应的用户。
APNs 服务器就是苹果专门做远程推送的服务器。
deviceToken是由APNs生成的一个专门找到你某个手机上的App的一个标识码。
deviceToken 可能会变,如果你更改了你项目的bundle Identifier或者APNs服务器更新了可能会变。
十九、KVO?
KVO(key-Value-Observing):键值观察机制 他提供了观察某一属性变化的方法,极大的简化了代码。
有四种基本使用方式:
1.KVO的基本使用
[self.a addObserver:self forKeyPath:@“name” options:NSKeyValueObservingOptionNew context:nil];
2. KVO的自动触发模式和手动触发模式
是否使用自动触发模式取决于下面方法的返回值
+ (BOOL)automaticallyNotifiesObserversOfName {
return NO;
}
当此方法如果返回NO,表示关闭了自动触发模式,需要手动触发需要调用如下:
[self.a willChangeValueForKey:@“name”];
[self.a didChangeValueForKey:@“name”];
3.KVO的属性依赖?
就是需要观察的对象中有一个自定义的对象属性,如果要观察这个自定义对象属性的属性的时候就需要进行属性依赖?
@interface Animal : NSObject
///
@property (nonatomic, strong) NSString *name;
///
@property (nonatomic, strong) Cat *cat;
///
@property (nonatomic, strong) NSMutableArray *arr;
@end
@implementation Animal
/// 当观察cat对象的时候,添加cat对象的所有属性路径
+ (NSSet<NSString *> *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"cat"]) {
NSArray *arrKeyPaths = @[@"_cat.age", @"_cat.name"];
keyPaths = [keyPaths setByAddingObjectsFromArray:arrKeyPaths];
}
return keyPaths;
}
@end
@interface Cat : NSObject
///
@property (nonatomic, assign) int age;
///
@property (nonatomic, strong) NSString *name;
@end
然后在需要观察的地方添加观察者即可
[self.a addObserver:self forKeyPath:@"cat" options:NSKeyValueObservingOptionNew context:nil];
接收方法如下
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
// NSLog(@"%@===%@===%@",keyPath,object,change);
if ([keyPath isEqualToString:@"cat"]) {
Cat *cat = change[@"new"];
NSLog(@"%d",cat.age);
}
}
如果我们不做上面的操作,想直接添加观察者也是可以的,比如下面
[_animal addObserver:self forKeyPath:@"_cat.age" options:NSKeyValueObservingOptionNew context:nil];
但是当有cat对象有很多个属性的时候,这里需要写多次观察者的,这样写起来很麻烦。
4.容器类的观察
由于KVO是观察的set方法,所以当属性时容器类的时候,容器类的内容进行改变观察不到,所以这时需要进行相关的处理操作。
例如:对象Animal中的arr数组,直接进行addObject操作不会被KVO观察到。此时苹果为我们提供了一种方法[self.a mutableArrayValueForKey:@“arr”];获取到此数组,对此数组进行addObject操作就可以观察到容器类的变化
static int i = 0;
I++;
NSMutableArray *tempArr = [_animal mutableArrayValueForKey:@"arr"];
[tempArr addObject:@(i)];
这时由于使用mutableArrayValueForKey得到的新的数组之后,如果新数组发生了变化,那么新数组就会把老数组替代了,可以说是执行了set方法。
二十、KVO的底层原理?
1. 创建了一个当前类的子类,注册此子类
2. 给创建的子类添加set方法。
3. 改变isa指针,令self的isa指针指向创建的子类
4. 在步骤2中添加的set方法中调用父类的set方法,在调用observeValueForKeyPath: ofObject: change: context:方法。
二十一、空指针和野指针
空指针:表示指针变量里面没有保存地址。这就是为什么要把对象置为nil的原因。
image.png
野指针:表示指针变量指向的内存地址已经被回收,此时,指针变量保存的内存地址就是垃圾地址。
二十二、runTime如何实现weak变量自动置为nil?
runtime对注册的类会进行布局,会将weak对象放入一个hash表中,用weak指向的对象的内存地址作为key。当此对象的引用计数为0的时候,会在这个weak hash表中通过key找到weak对象,将其置为nil
二十三、方法和选择器有何不同?
selector是一个方法的名称,方法是一个组合体,包含了名字和实现。
二十四、runtime如何通过selector找到对应的IMP地址?
1.每一个类对象中都有一个对象方法列表。(对象方法缓存)
2.类方法列表是存放类对象中isa指针指向的元类对象中。(类方法缓存)
3.方法列表中每个方法结构体中记录着方法的名称,方法实现,以及参数类型,其实selector是方法名称。通过这个名称
二十五、ViewController生命周期
按照执行顺序排列:
1. initWithCoder:通过nib文件初始化时触发。
2. awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
3. loadView:开始加载视图控制器自带的view。
4. viewDidLoad:视图控制器的view被加载完成。
5. viewWillAppear:视图控制器的view将要显示在window上。
6. updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
7. viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
8. viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
9. viewDidAppear:视图控制器的view已经展示到window上。
10. viewWillDisappear:视图控制器的view将要从window上消失。
11. viewDidDisappear:视图控制器的view已经从window上消失。
二十六、你是否接触过OC中的反射机制?简单聊一下概念和使用
1. class反射
通过类名的字符串形式实例化对象。
Class class = NSClassFromString(@"Student")
Student *stu = [[class alloc] init];
将类名变为字符串
Class class = [Student class];
NSString *className = NSStringFromClass(class);
2.SEL的反射
通过方法的字符串形式实例化方法。
SEL selector = NSSelectorFromString(@"setName");
将方法变成字符串
NSStringFromSelector(@selector*(setName:))
二十七、类变量的 @public,@protected,@private,@package 声明各有什么含义?
@public 任何地方都能访问。
@protected 该类和子类中访问,是默认的
@private 只能在本类中访问
@package 本包内使用,挎包不可用
二十九、isa指针
isa:是一个Class类型的指针。每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa指针,指向metelClass(元类)。元类中保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向父类查找该方法。同时注意的是:元类(meteClass)也是类,他也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass)。根元类的isa指针指向本身。这样形成了一个封闭的内循环。
三十、下面的代码输出什么?
@implementation Son : Father
- (id)init {
if (self = [super init]) {
NSLog(@"%@", NSStringFromClass([self class])); // Son
NSLog(@"%@", NSStringFromClass([super class])); // Son
}
return self;
}
@end
// 解析:
self 是类的隐藏参数,指向当前调用方法的这个类的实例。
super是一个Magic Keyword,它本质是一个编译器标示符,和self是指向的同一个消息接收者。
不同的是:super会告诉编译器,调用class这个方法时,要去父类的方法,而不是本类里的。
上面的例子不管调用[self class]还是[super class],接受消息的对象都是当前 Son *obj 这个对象。
三十一、isKindOfClass、isMemberOfClass作用分别是什么
isKindOfClass: 作用是某个对象属于某个类型或者继承自某类型。
isMemberOfClass: 某个对象确切属于哪个类型。
三十二、block的注意点
1). 在block内部使用外部指针且会造成循环引用情况下,需要用__week修饰外部指针:
__weak typeof(self) weakSelf = self;
2). 在block内部如果调用了延时函数还使用弱指针会取不到该指针,因为已经被销毁了,需要在block内部再将弱指针重新强引用一下。
__strong typeof(self) strongSelf = weakSelf;
3). 如果需要在block内部改变外部栈区变量的话,需要在用__block修饰外部变量。
三十三、响应者链?
先执行事件链,找到合适的view,在执行响应链
1.事件链
UIApplication -> window -> view -> view ……..->view
a. 当iOS程序员中发生触摸事件后,系统会将事件加入到UIApplication管理的一个任务队列中
b. UIApplication 将处于任务最前端的事件向下分发,即UIWindow
c. UIWindow将事件向下分发,即UIView
d. UIView首先查看自己是否能处理事件(hidden = NO, userInteractionEnabled = YES, alpha >= 0.01),触摸点是否在自己身上。如果能,那么继续寻找子视图
e. 便利子控件(从后往前遍历),重复上面的步骤。
f. 如果没有找到,那么自己就是事件处理者,如果自己不能处理,那就不做任何事。
事件链的过程其实就是 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
函数的执行过程。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1.判断当前控件能否接收事件
if (self.userInteractionEnabled == NO || self.hidden == YES || self.alpha <= 0.01) {
return nil;
}
// 2.判断点在不在当前控件
if ([self pointInside:point withEvent:event] == NO) {
return nil;
}
// 3.从后往前遍历自己的子控件
NSInteger count = self.subviews.count;
for (NSInteger i = count - 1; i >= 0; i--) {
UIView *childView = self.subviews[I];
// 把当前控件上的坐标系转换成子控件上的坐标系
CGPoint childPoint = [self convertPoint:point toView:childView];
// 重复上面的工作
UIView *fitView = [childView hitTest:childPoint withEvent:event];
if (fitView != nil) { // 寻找最合适的view
return fitView;
}
}
// 循环结束,表示没有比自己更合适的view
return self;
}
2.响应链
响应链是从最合适的view开始传递,处理事件传递给下一个响应者,响应链的传递是事件链传递相反的。如果所有响应者都不处理事件,则事件被丢弃。通常获取上级响应者是通过nextResponder方法的。
三十四、UIView与CALayer的区别?
-
UIView继承于UIResponder,可以响应用户事件,CALayer继承于NSObject不可以响应用户事件。
-
UIView侧重于显示内容的管理,CALayer侧重于对内容的绘制。
-
UIView与CALayer是相互依赖的关系。UIView的显示依赖于CALayer提供的内容,CALayer依赖UIView的容器来显示绘制的内容。
三十五、图像显示原理?
image.png- CPU输出位图
- GPU图层渲染、纹理合成
- 把结果放到帧缓冲区中
- 再由视图控制器根据vsync(垂直同步信号)在指定时间之前去提取帧缓冲区的屏幕显示内容
- 5.显示到屏幕上
三十六、UI卡顿掉帧的原因?
image.pngiOS设备会发出垂直同步信号,然后App的CPU会去计算屏幕要显示的内容,之后将计算好的内容提交到GPU去渲染。随后,GPU将渲染的结果提交到帧缓冲区,等到下一个垂直同步信号到来时将缓冲区的帧显示到屏幕上。一帧的显示是CPU和GPU共同决定的。
每隔16.7毫秒会产生一帧画面。当CPU + GPU处理的时间超过16.7毫秒就会造成掉帧甚至卡顿。
三十七、离屏渲染?
- 当前屏幕渲染:指的是GPU的渲染操作时在当前用于显示的屏幕缓冲区中进行。
- 离屏渲染:分为GPU的离屏渲染和CPU的离屏渲染。GPU离屏渲染指的是GPU在当前屏幕缓冲区外开辟了一个缓冲区进行渲染操作。
应当尽量避免GPU的离屏渲染。
GPU的离屏渲染何时触发?
圆角、图层蒙版、阴影等设置。
- 为什么要避免GPU的离屏渲染?
因为GPU的离屏渲染需要额外开辟一块缓冲区进行渲染,然后绘制到当前屏幕的过程中还需要做onscreen(当前屏幕)和offscreen(非当前屏幕)进行上下文的切换。由于这个切换过程消耗资源,而且每一帧都会进行切换。所以处理不当会对性能产生影响。还会增加GPU的工作量。
三十八、消息传递?
其实就是调用 objc_msgSend 函数
流程:缓存中查找—> 当前类查找 —> 父类逐级查找
1. 调用方法之前,先去查找缓存,看看缓存中是否有对应选择器的方法实现。如果有,就去调用函数,完成消息传递(缓存查找:给定值SEL,目标是查找对应的IMP)
2.如果缓存中没有,会根据当前实例的isa指针查找当前类对象的方法列表,看看是否有同样名称的方法,如果有,就去调用函数,完成消息传递(当前来中查找:对于已经排序好的方法列表采用二分法查找,对于没有排序好的列表,采用一般遍历)
3.如果当前类对象的方法列表中没有,就会逐级父类方法列表中查找,如果找到,就去调用函数,完成消息传递。(父类逐级查找:先判断父类是否为nil,为nil则结束,否则就继续进行缓存中查找 —> 当前类查找 ——> 父类逐级查找流程)
4.如果一直查到根类亦然没有查到,则进入消息转发流程,完成消息传递。
三十九、消息转发?
1. + (BOOL) resolveInstanceMethod:(SEL)sel; 为对象方法进行决议
+ (BOOL) resolveClassMethod:(SEL)sel; 为类方法进行决议
2. - (id) forwardingTargetForSelector:(SEL)aSelector; 方法转发目标
3. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void) forwardInvocation:(NSInvocation *)anInvocation;
最后如果还是没有对消息进行处理,则会调用 - (void) doesNotRecognizeSelector:(SEL)aSelector方法。
如果前两步还没有处理掉,则进入完整的消息转发。methodSignatureForSelector方法。
如果methodSignatureForSelector返回nil则程序挂掉。如果返回了一个方法签名,rutime会创建一个NSInvocation对象并发送forwardInvocation消息给目标对象。
四十、runTime的实现机制是什么,在实际中的应用
1.使用时需要导入头文件<objc/message.h> <objc/runtime.h>
2.Runtime运行时机制,它是一套C语言库。
3.实际上我们编写所有OC代码,最终都是转成了runtime库的东西。
比如:
类转换成了Runtime库里面的结构体等数据类型。
方法转成了 Runtime库里面的C语言函数‘
平时调方法都是转成了objc_msgSend函数(所以说OC有个消息发送机制)
4.因此,可以说Runtime是OC的底层实现,是OC的膜厚执行者。
实际中的应用:
1.动态给分类添加属性
2.方法交换
3.字典转模型
4.获取所有的私有属性和方法
5.对私有属性进行修改
6.动态添加方法
7.动态创建类
四十一、什么是 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。
四十二、runLoop?
1.概念:
runloop是通过内部维护的事件循环(EventLoop)来对事件/消息进行管理的一个对象。
- 没有消息处理时,休眠已避免资源占用,由用户状态切换到内核状态
- 有消息处理时,立即被唤醒,由内核状态切换到用户状态。
每条线程都有一个runloop,但是默认都不开启runloop
2.目的
1.保证runloop所在线程不退出
2.负责监听事件(触摸、timer、内核)
为什么咱们的main函数不会退出?
由于UIApplicationMain函数内部默认开启了主线程的RunLoop,保证运行循环。
UIApplicationMain函数一直没有返回,而是不断的接收处理消息以及等待休眠,所以运行程序才会保持持续运行的状态。
3.RunLoop的数据结构?
NSRunLoop(Foundation)是CFRunLoop(CoreFoundation)的封装,提供了面向对象的API。
主要设计五个类:
- CFRunLoop:RunLoop对象
- CFRunLoopMode:运行模式
- CFRunLoopSource:输入源/事件源
- CFRunLoopTimer:定时源
- CFRunLoopObserver:观察者
CFRunLoopMode由name、source0(非基于port的)、source1(基于port的)、observers、timers构成
CFRunLoopSource分为source0(用户触发的事件。需要手动唤醒线程,将当前线程从内核状态切换到用户状态)和source1(接收系统分发事件)
CFRunLoopTimer:基于事件的触发器,基本上来说就是NSTimer。NSTimer不准确的原因就是当前线程正在处理过多的繁重任务,这时Timer会延迟。
CFRunLoopObserver监听以下的时间点:
- 1.KCFRunLoopEntry: runloop准备启动
- 2.KCFRunLoopBeforeTimers:runloop将要处理一些Timer相关事件
- 3.KCFRunLoopBeforeSources:runloop将要处理一些source事件
- 4.KCFRunLoopBeforeWaiting:runloop将要进入休眠状态,即由用户态切换为内核态
- 5.KCFRunLoopAfterWaiting:runloop被唤醒,即将从内核态切为用户态
- 6.KCFRunLoopExit:runloop退出
- 7.KCFRunLoopAllActivities:监听所有状态
4.CFRunLoopMode:总共5种,程序员一般用3种
- KCFRunLoopDefaultMode:默认模式,主线程是在这个运行模式下运行的
- UITrackingRunLoopMode:跟踪用户交互事件,优先级最高,只能被触摸事件唤醒
- KCFRunLoopCommonModes:伪模式,不是一种真正的运行模式,是同步source/timer/observer到多个mode中的解决方案。
- UIInitialzationRunLoopMode:在启动app的时进入的第一个mode,启动完成后不再使用
- EventReceiveRunLoopMode:接收系统内部事件
5.runloop的实现机制?
image.png A. 通知观察者runLoop即将启动
B. 通知观察者runLoop即将处理Timer事件
C. 通知观察者runLoop即将处理source0事件
D. 处理source0事件
E. 如果有source1事件,则处理唤醒时收到的消息,之后再回到通知观察者即将处理timer事件
F. 通知观察者,线程即将休眠
G. 通知观察者线程即将唤醒
H. 处理唤醒时收到的消息,之后再跳转到通知观察者,即将处理timer事件
I. 通知观察者,即将runloop结束
6.一般应用?
-
1.NSTimer事件中的应用
-
2.创建一个常驻线程
-
3.保证子线程数据回来时不打断用户的滑动操作
我们可以将更新UI的事件放到主线程的NSDefaultRunLoopMode上执行,这样当用户不在滑动的时候才去进行刷新UI。 -
4.runLoop处理tableView快速滑动时,掉帧的问题。比如:cell上加载超级大的图片,它是个消耗性能的过程。这时可能造成卡顿。因此可以使用runLoop进行优化。
我们需要在一个RunLoop循环加载一张图片,这时使用 NSRunLoop当然是不行的,因为他没有提供对应的接口。所以需要CFRunLoop了,创建一个CFRunLoop,mode使用commonMode。传入一个timer维持runloop循环,加载一个图片是一个任务,在每个runloop循环时调用block这个任务进入加载图片。
四十三、Block?
block是将函数及其执行上下文封装起来的对象。
__block_impl 结构体为
image.png
block内部有isa指针,所以说其本质是OC对象。
block的几种形式?
A. 全局block:不使用外部变量的block是全局block
B. 栈block:使用外部变量但未进行copy操作的的block是栈block
C. 堆block:对栈block进行copy就会copy到堆区,对堆区的block进行copy就会增加引用计数
block变量截取:
A.局部变量截取的是值截获
B.局部静态变量是指针截获
C.类对象的全局变量是指针截获
block使用?
A.作为属性使用
B.作为函数参数使用
C.作为返回值使用(类似Masonry)
四十四、NSOperation和GCD的主要区别?
-
1.GCD的核心是C语言写的系统服务,执行和操作简单高效,因此NSOperation底层也通过GCD实现,换个说法就是NSOperation是对GCD更高层次的抽象,这是他们之间的本质区别。
-
2.依赖关系,NSOperation可以设置两个NSOperation之间的依赖,第二个任务依赖第一个任务执行,GCD无法设置依赖关系,但是可以通过dispatch_barrier_async来实现这种效果。
-
3.优先级,NSOperation可以设置自身的优先级,但是优先级高的不一定先执行,GCD只能设置队列的优先级,无法在执行的block设置优先级。
-
4.继承,NSOperation是一个抽象类,实际开发中常用的两个类是NSInvocationOperation和NSBlockOperation,同样我们可以自定义NSOperation,GCD执行任务可以自由组装,没有继承那么高的代码复用度。
-
5.效率,直接使用GCD效率确实会更高效,NSOperation会多一点开销,但是NSOperation可以设置依赖,优先级,最大并发数,继承,线程自主管理的优势。
NSOperation中没有串行和并行的名词。它是GCD特有的,NSOperatio通过设置最大并发数为1,达到串行的目的。
四十五、HTTP协议?
HTTP协议是超文本传输协议。它是基于TCP的应用层协议。
网络七层模型从上到下分别是:
- 1.应用层
- 2.表示层
- 3.会话层
- 4.传输层
- 5.网络层
- 6.数据链路层
- 7.物理层
1.请求报文和响应报文?
image.png请求报文?
请求头:POST someDir/page.html HTTP/1.1
包含了:请求方法、URL、HTTP版本号
请求头部:Host:www.baidu.com 域名
Content-Type: application/x-www.form-ulencoded
Connection: Keep-Alive 连接方式(Keep-Alive告诉服务器使用持续连接)
User-agent: 向服务器发送请求的浏览器类型
Accept-lauguage: 接收的语言,如果没有就使用默认的
空行:空行分割header和请求内容
请求体:
响应报文?
状态行:HTTP/1.1 200 OK
包含了:协议版本、状态码、短语
响应头部:Content-Type: text/html 实体对象类型
Connection: close连接方式(close告诉客户端,发送完报文后将关闭TCP连接)
Content-Length: 122 发送对象中的字节数
Server: Apache/2.2.3 向客户端发送服务器类型
Data: Sat, 31 Dec 2005 23:59:59 GMT 服务器从文件系统中检索到该对象,插入到响应报文,并发送响应报文的时间
空行:
响应体:
2.HTTP的请求方式?
GET、POST、PUT、Delete、Head、Options
1.Get和Post方式的区别?
从语法上看:
Get的请求参数一般以“?”分割拼接到URL后面,POST请求参数在Body里面
Get参数长度限制为2048个字符,POST一般是没有限制的
Get请求参数由于裸露在URL中,是不安全的,Post请求相对安全,如果Post请求被抓包,则Post一样不安全。
从语义的角度看:
Get:获取资源是安全的,幂等的(只读的,同一个请求方法执行多次和一次的效果是一样的)、可缓存的
Post:获取资源时非安全的,非幂等的,不可缓存的
3.GET和POST本质上都是TCP连接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程找那个体现出一些不同。
在响应时,Get产生了一个TCP数据包,POST产生了两个TCP数据包。
对于Get方式的请求,浏览器会把Header和实体主体一并发送出去,服务器响应200返回数据
对于Post,浏览器先发送Header,服务器响应100 Continue,浏览器再发送实体,服务器响应200返回数据。
4.Get相对于Post的优势是什么?
1.最优势就是方便。Get的URL可以直接手输,从而Get请求中的Url可以被存到书签中。
2.可以被缓存,大大减轻服务器的负担。
四十六、三次握手?
- SYN:代表请求创建连接,所以在三次握手中前两次要SYN=1,表示两次用于建立连接。
- FIN: 代表请求关闭连接。一次FIN只能关闭一个方向的连接。
- ACK:代表确认接收,三次握手和四次挥手时都会加上ACK=1,表示消息接收到了。
- seq:序列号,当发送一个数据时,数据被拆分成多个数据包发送,序列号就是对每个数据包进行编码,这样接收方才能对数据包进行再次拼接。初始序列号是随机生成的。
- ack:代表下一个数据包的编号,所以第二个请求时,ack = seq + 1
过程如下:
第一步:客户端向服务端发送一个SYN包,客户端进入SYN_SENT状态。
第二步:服务端收到SYN数据包后,进入SYN_RCVD状态,同时向客户端发送一个SYN_ACK数据包。
第三步:客户端收到SYN_ACK数据包后,客户端进入established(建立连接)状态,同时向服务端发送一个数据包,服务端收到该数据包也进入established状态。这时三次握手完成。
四十七、四次挥手?
参与TCP连接的两个进程中的任何一个都能终止该连接。
image.png以客户端发起终止连接为例:
- 第一步:客户端应用发出一个关闭连接的指令。FIN置为1,同时客户端进入FIN_WAIT_1状态,等待服务端的带有确认的TCP报文段。
- 第二步:服务端收到报文段后向客户端发送一个确认报文段。服务端进入Close_Wait状态,对应客户端的time_wait状态,表示被动被动关闭。客户端收到该报文后进入FIN_Wait_2状态,等待服务端的FIN置为1的报文。
- 第三步:服务端发送自己的终止报文段,服务端进入Last_ACK状态,等待客户端最后的确认报文段。
- 第四部:客户端收到服务端的终止报文段后,向服务端发送一个确认报文段。同时客户端进入time_wait状态最后进入close状态。服务端收到该报文段后,同样关闭,重新进入close状态。
四十八、为什么建立连接只用三次握手,而断开连接需要四次挥手?
- 1.首先,当客户端数据发送完毕后,且知道服务端也全部接收到了时,就会断开。
- 2.服务端接收到客户端的FIN,为了表示接收到了,就会向客户端发送了ACK
- 3.但是此时,服务端可能还在发送数据,并没有关闭TCP窗口的意思,所以服务端的FIN和ACK并不是同步发送的,只有当数据发送完毕了,才会发送FIN
*答:服务端的FIN和ACK需要分开发送,并不像三次握手那样,SYN可以和ACK同步发送,所以需要四次握手。
四十九、在四次挥手中,客户端为什么在time_wait后必须等待2ML时间呢?
最后客户端发送给服务端的ACK报文段可能丢失,这样服务端在2MSL时间内收不到ACK报文段就会重复发送FIN报文段。
客户端在2MSL(1到4分钟)时间内收不到重传的FIN报文段在发送ACK报文段,直到服务端收到,客户端和服务端就会进入到closed状态,关闭TCP连接
答:为了保证客户端发送的最后一个ACK报文段能够达到服务端
五十、TCP在创建连接时,为什么需要三次握手而不是两次握手?
答:两次握手会可能导致连接没有建立起来,但是服务器以为建立起来了,这样服务端在给客户端发送数据时造成了服务端资源的浪费。四次握手浪费资源,没必要。
五十一、HTTP和HTTPS的区别?
HTTPS协议 = HTTP协议 + SSL/TLS协议
SSL全称是Secure Socket Layer,即安全套接层协议,是为网络通信提供安全及数据完整性的一种安全协议。
TLS的全称是Transport Layer Security,即安全传输层协议。
即HTTPS就是安全的HTTP。
五十二、HTTPS的连接建立流程?
HTTPS为了兼顾安全与效率,同时使用了对称加密和非对称加密。在传输的过程中涉及到了三个秘钥:
服务端的公钥和私钥,用来进行非对称加密。
客户端的随机秘钥,用来进行对称加密。
网友评论