美文网首页
iOS基础总结

iOS基础总结

作者: 傲骨天成科技 | 来源:发表于2020-12-11 16:18 被阅读0次

一、设计模式是什么?你知道哪些设计模式,并简要叙述?

设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一类类型的事情。
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
    1. CPU输出位图
    1. GPU图层渲染、纹理合成
    1. 把结果放到帧缓冲区中
    1. 再由视图控制器根据vsync(垂直同步信号)在指定时间之前去提取帧缓冲区的屏幕显示内容
  • 5.显示到屏幕上

三十六、UI卡顿掉帧的原因?

image.png

iOS设备会发出垂直同步信号,然后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消息给目标对象。

image.png image.png

四十、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
image.png

过程如下:

第一步:客户端向服务端发送一个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为了兼顾安全与效率,同时使用了对称加密和非对称加密。在传输的过程中涉及到了三个秘钥:
服务端的公钥和私钥,用来进行非对称加密。
客户端的随机秘钥,用来进行对称加密。

image.png

相关文章

  • iOS 动画基础总结篇

    iOS 动画基础总结篇 iOS 动画基础总结篇

  • iOS 开发之 GCD 不同场景使用

    iOS 开发之 GCD 不同场景使用 本文在iOS 开发值 GCD 基础 的基础上,继续总结了 GCD 的一些AP...

  • 文章汇总

    ios iOS 网络篇 网络基础总结 支付宝集成 iOS支付宝支付集成 - demo例子集 - 博客园 理解 iO...

  • GeekBand - iOS 多线程和RunLoop 总结

    iOS 开发高级进阶 第三周 多线程 Runloop iOS 多线程以及 RunLoop 学习总结 基础知识 什么...

  • ios基础总结

    1.导航控制器 1.导航控制器是UIVIewController的子类,导航控制器想当于一个管理控制器的Contr...

  • iOS基础总结

    一、设计模式是什么?你知道哪些设计模式,并简要叙述? 二、MVC和MVVM的区别 三、#import跟 #incl...

  • iOS 面试各要点 暂记

    ## iOS常用问题总结#### iOS基础知识回顾##### 1、为什么说Objective-C是一门动态的语言...

  • 2016年度总结

    2016年—年终总结 学习工作 1.iOS: ****1月-3月****:学习了 iOS 基础知识,包括 UITa...

  • iOS中NSLog输出格式大全

    本文的内容是总结了一下iOS开发中NSLog输出格式大全,虽然比较基础,但有总结毕竟会各位正在学习iOS开发的朋友...

  • iOS数据持久化

    记录一下别人总结的,嘿嘿嘿【IOS学习基础】归档和解档

网友评论

      本文标题:iOS基础总结

      本文链接:https://www.haomeiwen.com/subject/kijjgktx.html