最新面试总结

时间:2020-05-26
本文章向大家介绍最新面试总结,主要包括最新面试总结使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

UIView和CAlayer之间的关系

UIView 负责响应事件 CALayer负责绘制UI
UIView对CALayer封装属性 我们一般访问的frame center 等属性 其实内部会访问相应的CALayer属性 但是设置阴影 圆角 还是得考直接访问CALayer的属性来完成
每个UIView 都持有一个CALayer 并且是CALayer的代理

事件的传递和视图传递链

只有继承于UIResponder的类 才可以相应事件 处理事件
常用的UIApplication UIVIew UIViewController 都继承于UIResponder 都可以传递和处理事件

1.当iOS程序中发生触摸事件后 系统会将事件加入到UIapplication管理的一个任务队列中

2.UIApplication将处于任务队列最前端的事件向下分发 即UIWindow。

3.UIWindow将事件向下分发 即UIView

4.UIVIew首先看自己能否处理这个事件 触摸点是否在自己身上 如果都能满足 则继续向下传递 继续寻找子视图

5.遍历子控件 重复第四步

6.如果子控件不能处理这个事件 那么就自己处理  如果自己也不能处理 那么不做任何处理。

其中 UIVIew不接受事件处理的情况主要有以下三种 alpha < 0.01  userInteractionEnabled = NO  hidden = YES

那么如何返回处理时间的最佳VIew呢 其实是通过


// 此方法返回的View是本次点击事件需要的最佳View
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event

// 判断一个点是否落在范围内
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event

事件传递给窗口或者控件后 就调用上述两个方法寻找更合适的事件响应者 如果子控件是合适的 则在子控件上再调用上述方法寻找更合适的事件响应者 直到找到最合适的响应者,或者废弃事件。

利用第二个方法还可以扩大按钮的点击范围 响应范围。

图像显示原理

https://blog.csdn.net/qq_42347755/article/details/103101766

分类和扩展

分类是OC的特有语法 它是表示一个指向分类的结构体的指针。原则是只能增添方法 不能添加成员变量 具体原因可以看源码组成:

Category
Category 是表示一个指向分类的结构体的指针,其定义如下:
typedef struct objc_category *Category;
struct objc_category {
  char *category_name                          OBJC2_UNAVAILABLE; // 分类名
  char *class_name                             OBJC2_UNAVAILABLE; // 分类所属的类名
  struct objc_method_list *instance_methods    OBJC2_UNAVAILABLE; // 实例方法列表
  struct objc_method_list *class_methods       OBJC2_UNAVAILABLE; // 类方法列表
  struct objc_protocol_list *protocols         OBJC2_UNAVAILABLE; // 分类所实现的协议列表
}

可以看到里面没有属性列表。所以原则上来说是不能添加属性的。

但是分类是可以添加@property的。并且运行时不会报错 但是编译时会有警告.

分类可以访问原有类中.h中的属性。

如果分类中有河原有类同名的方法 会优先调用分类中的方法,就是说会忽略掉原有类的方法。所以同名方法的调用优先级为

分类->本类>父类,因此开发中尽量不要有原有类的同名方法.

如果多个分类中都有和原有类的同名方法,那么调用该方法的时候 执行谁由编译器决定 编译器执行最后一个参与编译的分类中的方法.

为什么在分类中声明属性时 运行不会出错呢?既然分类不让添加属性 那为什么写了@property仍然还可以编译通过呢?

我们在一个类中使用@property声明属性时,编译器会自动帮我们生成_成员变量和setter/getter方法,但是在分类结构体中由于没有分类列表,所以不会生成_成员变量和setter/getter方法.

所以 我们可以声明 编译和运行都会通过 只要不使用这个变量  但是如果调用了成员变量 或者setter/getter方法 就会闪退.

我们可以通过手动添加getter和setter方法来解决这个问题。最简单的方式是通过RunTime来实现

//运行时实现setter方法
- (void)setNameWithSetterGetter:(NSString *)nameWithSetterGetter {
        objc_setAssociatedObject(self, &nameWithSetterGetterKey, nameWithSetterGetter, OBJC_ASSOCIATION_COPY);
}

//运行时实现getter方法
- (NSString *)nameWithSetterGetter {
    return objc_getAssociatedObject(self, &nameWithSetterGetterKey);
}

还有手动添加getter和setter方法 但是实现起来比较麻烦

类扩展:

类扩展是Category的一个特例.类扩展与分类知识少了分类的名称 所以称为匿名分类。

作用: 为一个类添加额外的原来没有的变量 方法 和 属性 一般写到.m文件中  一般的私有小户型写到.m文件的类扩展中。

类别和类扩展的区别:

(1)类别 原则上只能增加方法(能添加属性 但是要自己实现getter和setter方法)

(2)类扩展不仅可以增加方法 还可以增加实例变量(属性)

(3)类扩展中声明的方法没有实现 编译器会报警 但是在类别中的方法没被实现 是不会报警的。这是因为类扩展是在编译阶段被添加到类中

而类扩展是在运行时通过RunTime添加到类中。

(4)类扩展不能像类别那样拥有独立的实现部分(@implementation部分).也就是说 类扩展所声明的方法必须依托对应类的实现部分来实现。

(5)定义在.m文件中的类扩展方法为私有的 定义在.h的类扩展方法是公有的 类扩展在.m文件中声明私有方法的一个非常好的方式。

代理

代理是一种设计模式 以@protocol形式体现。一般是一对一传递。用weak关键字以规避循环引用。

通知

使用观察者模式来实现的用于跨层传递消息的机制。传递方式是一对多的。

代理和通知的区别

(1) 效率:代理比通知高

(2) 关联:delegate是强关联 委托和代理双方互相知道 通知是弱关联 不需要知道谁发出的

也不需要知道谁接收的。

(3) 代理是一对一的关系 通知是一对多的关系 delegate一般是行为需要别人来完成。通知是全局通知。

(4) 代理要实现对多个类发出消息可以通过将代理者加入集合后遍历 或者 通过消息转发来实现.

KVO

KVO 即Key-Value Observing 也就是我们常说的键值观察 他是一种机制 允许将其他对象的指定属性的更改通知给指定对象。

KVO 机制对应用程序中模型层和控制器层之间的通信特别有用,控制器对象通常观察模型对象的属性,而视图对象通过控制器观察模型对象的属性,

模型对象。

当我们要使用KVO观察某个对象的时候 必须得确保该对象符合KVO的使用标准才可以。

该类和属性必须要符合KVC 因为KVO的实现依托于KVC。KVO支持的数据类型与KVC相同,包括OC对象以及基本的数据类型。

该类能为该属性发出KVO更改的通知。当有依赖关系的时候 注册合适的依赖的key

KVO 的实现:

KVO是基于运行时实现的  当观察某个对象A时 KVO机制动态的创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter方法.

setter会负责在调用原setter方法之前和之后,通知所观察对象属性值的改变情况.

KVC(key-value coding)

-(id)valueForKey:(NSString *)key;

-(void)setValue:(id)value forKey:(NSString *)key;

KVC就是指iOS开发中 可以允许开发者通过key名直接访问对象的属性,或者给对象的属性赋值。

而不需要调用明确的存取方法。这样就可以在运行时动态的访问和修改对象的属性。而不是在编译时确定。

KVC的底层实现原理:

当一个对象调用setValue方法时 方法内部会做以下操作:

(1) 检查是都存在相应的key的setter方法 如果存在就调用set方法

(2) 如果setter方法不存在 就会查找与key相同名称并且带下划线的成员变量,如果有 则直接给成员变量属性赋值.

(3) 如果没有找到_key 就会查找相同名称的属性key 如果有就直接赋值

(4) 如果还没有找到 则调用valueForUnderfinedKey:和setValue:forUnderfinedKey方法.

属性关键字

常用的属性关键字 assign weak unsafe_unretained strong retain copy readonly readwrite nonatomic natomic __weak __block

@synthesize 和 @dynamic

atomic 原子操作 默认的就是atomic 会在getter和setter方法加锁 保证使用setter和getter方法的时候的线程安全。但是消耗比较大

noatomic 非原子操作属性 一般不需要多线程支持的时候就用它。这样在并发访问的时候效率会高。我们最常用的就是他。

assign 修饰基本数据类型 修饰对象类型时 不改变其引用计数 但是修饰的对象在对象释放后 assign仍然指向对象的内存地址 会产生野指针

weak 不改变被修饰对象的引用计数 所指的对象在被释放后 weak指针会自动置nil

retain(MRC)/Strong(ARC) 表示持有特性 setter方法将传入参数先保留 再赋值 引用计数会+1

copy 所修饰的属性必须为OC对象 拥有对象所有权 分为浅拷贝和深拷贝 常用来修饰 NSString NSArray NSDictory

浅拷贝:对内存地址的复制 让目标对象指针和原对象指向同一片内存空间 会增加引用计数

深拷贝:复制引用对象本身 内存中存在了两份独立对象本身 当修改A时 A_copy不变。

内存布局

栈区:由编译器自动分配释放 存放函数的参数值 局部变量等 栈是系统数据结构 对应线程/进程是唯一的。连续的 高地址向低地址扩展优点是快速高效 缺点是有限制 数据不灵活

先进后出

栈空间分为静态分配和动态分配两种

堆区: 由程序员分配和释放 如果程序员不释放 程序结束时 可能会由操作系统回收 比如在iOS中alloc都是存放在堆中。是离散的 低地址向高地址扩展

优点是灵活方便 数据适应面广泛 但是效率有一定降低。

堆是函数库内部数据结构 不一定唯一 不同堆分配的内存无法互相操作 堆空间的分配是动态的。

全局区(静态区)(static) 全局变量和静态变量的存储时放在一块的 初始化的全局变量和静态变量存放在一块区域  未初始化的全局变量和静态变量在相邻的另一块区域 程序结束后由系统释放。

代码区 存放函数的二进制代码

内存管理方案

https://www.jianshu.com/p/0b62649c6e49

MRC 和 ARC 的区别

https://www.jianshu.com/p/5eac83471b23

循环引用

循环引用的实质:多个对象相互之间有强引用 不能释放让系统回收。

如何解决循环引用?

RunTIme消息传递机制

首先 通过 obj的ISA指针扎到他的class

在class的method list 找方法

如果class中没找到 继续向他的superClass 中找

一旦找到这个函数 就去执行实现它的IMP

但是 如果每次都循环遍历整个method_list 效率太低 因为每个类常用的方法并不是很多

因此每个类也会有一个objc_cache 是对常用方法的缓存 因此每次执行方法 会先寻找objc_cache

中存放的方法 如果找不到 再去遍历method_list 找到后 调用并缓存到objc_cache中.

(method_name为key method_Imp为value)

类对象中元数据存储的都是如何创建一个实例的相关信息 那么类对象和类方法应该从哪里创建呢?

就是从isa指针指向的及饿哦固体创建 类对象的isa指针指向的我们称之为元类(metaclass) 元类中保存了创建类对象

以及类方法的所需的所有信息。

对象的isa指向superClass class的isa指针指向元类 类对象的superClass 指向父类的类对象

元类的super Class指针指向了父亲的元类 那元类的isa指针又指向了自己

元类中保存了创建类对象以及类方法所需的所有信息

动态方法解析:

首先OC运行时会调用+resolveInstanceMethod:或者+reloveClassMethod: 让你有机会提供一个函数实现

如果你添加了函数并返回YES 那么运行时系统会重新启动一次函数发送过程。

如果resolve返回NO 运行时就会移到系一部forwardingTargetForSelectd方法

这个方法可以返回一个消息接收对象(就是可以执行这个方法的对象)

如果返回nil 则会调用methodSignatureForSelector:方法 返回nil 则程序找不到方法闪退

返回一个函数签名 则会创建一个NSInvocation 对象并发送

-forwardInvocation:消息给目标对象。

RunLoop

保持程序持续运行 程序一启动 就会开一个主线程 主线程一开起来就会开启一个对应RunLoop RunLoop保证线程不会被销毁

也就保证了程序的持续运行

处理APP中的各种事件(比如:触摸事件 定时器事件 Selector事件等)

节省CPU资源 提高程序性能 程序运行起来时 当什么操作都没有做的时候 RunLoop就会告诉CPU休息

当有事情时RunLoop就会立马起来去做事情

RunLoop 包含了若干个Model 每个Model对应几个不同的事件源

事件源分为 Input Sources 和 timer sources

input Sources 包含基于port的内核事件 和 custome自定义事件 和 一些performSelector 方法等

timer Sources 就是一些定时器事件

source1 基于Port的线程之间的通信

Source0 触摸事件 PerformSelectors

webView 通信

在shouldStartLoad 方法里面 拦截URL 做一些逻辑处理

在WebViewDidFinishLoad 中 通过

webView stringByEvaluatingJavaScriptFromString:方法 调用JS方法

使用JavaScriptCore 的JSContext注入OC方法 但是需要和JS中的方法同名

WKWebView 通信

JS 调用OC

通过WKWebViewConfiguration()对象 添加一个JS的方法

在didReceive 方法中 通过方法名message.name 来实现调用OC的方法

OC调用JS

self.webView evaluateJavascript:方法名 completionHandler

3通过JavaScriptCore做交互

https://www.jianshu.com/p/d39a5eee48d7

 

原文地址:https://www.cnblogs.com/huanying2000/p/12928804.html