KVO 内部实现原理
- KVO是基于runtime机制实现的。
- 当某个类的对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。
- 派生类在被重写的 setter 方法实现真正的通知机制(Person NSKVONotifying_Person)
不用中间变量,用两种方法交换 A 和 B 的值
A=A+B;
B=A-B;
A=A-B;
或者
A = A^B;
B = A^B;
A = A^B;
runtime 实现的机制是什么
runtime,运行时机制,它是一套C语言库。
实际上我们编写的所有OC代码,最终都是转成了runtime库的东西,
比如:
类转成了runtime库里面的结构体等数据类型,
方法转成了 runtime库里面的C语言函数,平时调方法都是转成了objc_msgSend 函数(所以说OC有个消息发送机制)
因此,可以说runtime是OC的底层实现,是OC的幕后执行者 有了runtime库,能做什么事情呢?
runtime库里面包含了跟类、成员 变量、方法相关的API,比如获取类里面的所有成员变量,为类动态 添加成员变量,动态改变类的方法实现,为类动态添加新的方法等 因此,有了runtime,想怎么改就怎么改。
是否使用 Core Text 或者 Core Image
CoreText
- 随意修改文本的样式
- 图文混排(纯C语言)
- 国外:Niumb
Core Image(滤镜处理)
- 能调节图片的各种属性(对比度, 色温, 色差等)
NSNotification、KVO、delegate 的区别和用法是什么?
通知比较灵活:1个通知能被多个对象接收, 1个对象能接收多个通知
KVO性能不好(底层会动态产生新的类),只能监听某个对象属性的改 变, 不推荐使用(1个对象的属性能被多个对象监听, 1个 对象能监听 多个对象的其他属性)
代理比较规范,但是代码多(默认是1对1)
delegate 代理的作用?
代理的目的是改变或传递控制链。
允许一个类在某些特定时刻通知到其他类,而不 需要获取到那些类的指针。可以减少框架复杂度。
另外一点,代理可以理解为java 中的回调监听机制的一种类似。
简单说一下事件响应的流程?
- 一个 UIView 发出一个事件之后,首先上传给其父视图;
- 父视图上传给其所在的控制器;
- 如果其控制器对事件进行处理,事件传递将终止,否则继续上传父视图;
- 直到遇到响应者才会停止,否则事件将一直上传,直到 UIWindow。
用@property 声明的 NSString(或 NSArray, NSDictionary)经常使用 copy 关键字,为什么? 如果改用 strong 关键字,可能造成什么问题?
因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个 不可变的副本。
如果我们使用是 strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。
【复制详解】
浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指 针复制。
深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有 一层是深复制。
完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都 是对象复制。
非集合类对象的 copy 与 mutableCopy [不可变对象 copy] // 浅复制 [不可变对象 mutableCopy] //深复制
[可变对象 copy] //深复制 [可变对象 mutableCopy] //深复制
集合类对象的 copy 与 mutableCopy [不可变对象 copy] // 浅复制 [不可变对象 mutableCopy] //单层深复制
[可变对象 copy] //单层深复制
[可变对象 mutableCopy] //单层深复制
这里需要注意的是集合对象的内容复制仅限于对象本身,对象元素仍然是 指针复制
这个写法会出什么问题: @property (copy) NSMutableArray *array;
因为 copy 策略拷贝出来的是一个不可变对象,然而却把它当成可变对象使用,很容 易造成程序奔溃。
这里还有一个问题,该属性使用了同步锁,会在创建时生成一些额外的代码用于帮 助编写多线程程序,这会带来性能问题,通过声明 nonatomic 可以节省这些虽然
很小但是不必要额外开销,在 iOS 开发中应该使用 nonatomic 替代 atomic
如何让自定义类可以用 copy 修饰符?如何重写带 copy 关键字的 setter?
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对 象分为可变版本与不可变版本,那么就要同时实现 NSCopyiog 与 NSMutableCopying 协议,不过一般没什么必要,实现 NSCopying 协议就够了
// 实现不可变版本拷贝
- (id)copyWithZone:(NSZone *)zone; // 实现可变版本拷贝
- (id)mutableCopyWithZone:(NSZone *)zone;
// 重写带 copy 关键字的 setter
- (void)setName:(NSString *)name {
_name = [name copy];
}
+(void)load; +(void)initialize;有什 么用处?
+(void)load; 当类对象被引入项目时, runtime 会向每一个类对象发送 load 消息。
load 方法会在每一个类甚至分类被引入时仅调用一次,调用的顺序:父类优先于子 类, 子类优先于分类
由于 load 方法会在类被 import 时调用一次,而这时往往是改变类的行为的最佳时 机,在这里可以使用例如 method swizlling 来修改原有的方法
+(void)initialize;
也是在第一次使用这个类的时候会调用这个方法,也就是说 initialize 也是懒加载。
总结:
在 Objective-C 中,runtime 会自动调用每个类的这两个方法
+load 会在类初始加载时调用
+initialize 会在第一次调用类的类方法或实例方法之前被调用
IBOutlet 连出来的视图属性为什么可以被设置成 weak?
因为父控件的 subViews 数组已经对它有一个强引用
1.首先要明确什么时候可以使用weak修饰?
一种情况是 : 在 ARC 中,在有可能出现循环引用的时候
另一种情况是 : 自身已经对它进行一次强引用,没有必要再强引用一次
2.然后看IBOutlet连出来的视图的引用关系
UIViewController →强引用→ UIView →强引用→ IBOutlet连出视图
3.综上所诉
所以问题场景满足第二种使用weak的条件, 既UIViewController不需要对IBOutlet连出来的视图再用strong修饰, 用weak即可。
什么情况使用 weak 关键字
在 ARC 中,在有可能出现循环引用的时候,往往要通过让其中一端使用 weak 来解决,比如: delegate 代理属性
自身已经对它进行一次强引用,没有必要再强引用一次,此时也会使用 weak,自定义 IBOutlet 控件属性一般也使用 weak;当然,也可以使用strong
weak修饰的属性所指的对象遭到摧毁时,属性值也会清空(nil)
请简述 UITableView 的复用机制
每次创建 cell 的时候通过 dequeueReusableCellWithIdentifier:方法创建 cell,
它先到 缓存池中找指定标识的 cell,
如果没有找到指定标识的 cell,就直接返回 nil,且会通过 initWithStyle:reuseIdentifier:创建一个 cell
当 cell 离开界面就会被放到缓存池中,以供下次复用
如何高性能的给 UIImageView 加个圆角?
不好的解决方案:
使用下面的方式会强制 Core Animation 提前渲染屏幕的离屏绘制, 而离 屏绘制就会给性能带来负面影响,会有卡顿的现象出现
self.view.layer.cornerRadius = 5;
self.view.layer.masksToBounds = YES;
正确的解决方案:使用绘图技术
- (UIImage *)circleImage {
// NO 代表透明
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
// 获得上下文
CGContextRef ctx = UIGraphicsGetCurrentContext();
// 添加一个圆
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
// 裁剪 CGContextClip(ctx);
// 将图片画上去
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭上下文 UIGraphicsEndImageContext();
return image;
}
还有一种方案:使用了贝塞尔曲线"切割"个这个图片, 给 UIImageView 添加了的圆 角,其实也是通过绘图技术来实现的
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
描述下 SDWebImage 里面给 UIImageView 加 载图片的逻辑
SDWebImage 中为 UIImageView 提供了一个分类 UIImageView+WebCache.h
, 这个分类中有一个最常用的接口 sd_setImageWithURL:placeholderImage:
,会在真实图片出现前会先显示占位图片,当真实图片被加载出来后在替换占位图片 加载图片的过程大致如下:
首先会在 SDWebImageCache 中寻找图片是否有对应的缓存, 它会以 url 作为数据的索引先在内存中寻找是否有对应的缓存
如果缓存未找到就会利用通过 MD5 处理过的 key 来继续在磁盘中查询对应的数据,
如果找到了, 就会把磁盘中的数据加载到内存中,并将图片显示出来
如果在内存和磁盘缓存中都没有找到,就会向远程服务器发送请求,开始下载图片下载后的图片会加入缓存中,并写入磁盘中 整个获取图片的过程都是在子线程中执行,获取到图片后回到主线程将图片显示出来。
你是怎么封装一个 view 的
可以通过纯代码或者 xib 的方式来封装子控件建立一个跟 view 相关的模型,然后将模型数据传给 view,通过模型上的数据给 view 的子控件赋值。
/**
* 纯代码初始化控件时一定会走这个方法
*/
- (instancetype)initWithFrame:(CGRect)frame {
if(self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
/**
* 通过 xib 初始化控件时一定会走这个方法
*/
- (id)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder]) {
[self setup];
}
return self;
}
- (void)setup {
// 初始化代码
}
触摸事件的传递
触摸事件的传递是从父控件传递到子控件 如果父控件不能接收触摸事件,那么子控件就不可能接收到触摸事件 不能接受触摸事件的四种情况 不接收用户交互,即:userInteractionEnabled = NO 隐藏,即:hidden = YES
透明,即:alpha <= 0.01
未启用,即:enabled = NO
提示:UIImageView 的 userInteractionEnabled 默认就是 NO,因此 UIImageView 以 及它的子控件默认是不能接收触摸事件的
如何找到最合适处理事件的控件:
首先,判断自己能否接收触摸事件 可以通过重写 hitTest:withEvent:方法验证 其次,判断触摸点是否在自己身上
对应方法 pointInside:withEvent: 从后往前(先遍历最后添加的子控件)遍历子控件,重复前面的两个步骤 如果没有符合条件的子控件,那么就自己处理
事件响应者链
如果当前 view 是控制器的 view,那么就传递给控制器;如果控制器不存在,则将其传递给它的父控件。
在视图层次结构的最顶层视图也不能处理接收到的事件或消息,则将事件或消息传 递给 UIWindow 对象进行处理。
如果 UIWindow 对象也不处理,则将事件或消息传递给 UIApplication 对象
如果 UIApplication 也不能处理该事件或消息,则将其丢弃
补充:如何判断上一个响应者
如果当前这个 view 是控制器的 view,那么控制器就是上一个响应者 如果当前这个 view 不是控制器的 view,那么父控件就是上一个响应者
一个 objc 对象的 isa 的指针指向什么?有什 么作用?
每一个对象内部都有一个 isa 指针,这个指针是指向它的真实类型 根据这个指针就能知道将来调用哪个类的方法
下面的代码输出什么?
@implementation Son : Father
- (id)init {
if (self = [super init]) {
NSLog(@"%@", NSStringFromClass([self class])); // Son
NSLog(@"%@", NSStringFromClass([super class])); //Son
}
return self;
}
@end
// 答案:都输出 Son
这个题目主要是考察关于 objc 中对 self 和 super 的理解:
self 是类的隐藏参数,指向当前调用方法的这个类的实例。而 super 本质是一个编译器标示符,和 self 是指向的同一个消息接受者
当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中 再找;
而当使用 super 时,则从父类的方法列表中开始找。然后调用父类的这个方法 调用[self class] 时,会转化成 objc_msgSend 函数 id objc_msgSend(id self, SEL op, ...)
调 用 [super class] 时 , 会 转 化 成 objc_msgSendSuper 函 数 id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一个参数是 objc_super 这样一个结构体,其定义如下 struct objc_super { __unsafe_unretained id receiver;
__unsafe_unretained Class super_class; };
第一个成员是 receiver, 类似于上面的 objc_msgSend 函数第一个参数 self 第二个成员是记录当前类的父类是什么,告诉程序从父类中开始找方法,找到方法 后,最后内部是使用 objc_msgSend(objc_super->receiver, @selector(class))去调用, 此时已经和[self class]调用相同了,故上述输出结果仍然返回 Son
objc Runtime 开源代码对- (Class)class 方法的实现
- (Class)class {
return object_getClass(self);
}
以下代码运行结果如何?
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
// 答案:主线程死锁
使用 block 时什么情况会发生引用循环,如 何解决?
系统的某些 block api 中,UIView 的 block 版本写动画时不需要考虑,但也有一 些 api 需要考虑
以下这些使用方式不会引起循环引用的问题
[UIView animateWithDuration:duration animations:^ {
[self.superview layoutIfNeeded];
}];
[[NSOperationQueue mainQueue] addOperationWithBlock:^ {
self.someProperty = xyz;
}];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification" object:nil
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * notification)
{
self.someProperty = xyz;
}];
但如果方法中的一些参数是成员变量,那么可以造成循环引用,如:GCD 、 NSNotificationCenter 调用就要小心一点,比如 GCD 内部如果引用了 self,而且 GCD 的参数是 成员变量,则要考虑到循环引用,举例如下:
// 【GCD】分析:self-->_operationsQueue-->block-->self 形成闭环,就造成了循环引用
__weak typeof(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^{
[weakSelf doSomething];
[weakSelf doSomethingElse];
});
// 【NSNotificationCenter】分析:self-->_observer-->block-->self 形成闭环,就造成了循环引用
__weak typeof(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey" object:nil queue:nil usingBlock:^(NSNotification *note) {
[weakSelf dismissModalViewControllerAnimated:YES];
}];