一 、KVO与KVC
KVC
https://blog.csdn.net/yuwuchaio/article/details/80701681
KVC 键值编码 通过字符串Key值去直接访问对象属性,通过KVC机制可以间接的访问对象的属性,KVC之所以能访问对象属性是因为遵从了NSKeyValueCoding协议,所有的NSObject类都遵从了这个协议,通过KVC访问对象的属性
KVC内部查询key值的顺序
当调用setVlaue:属性值 forKey :@"name"的代码的时候,底层的执行机制如下:
程序优先调用set方法,代码通过setter方法完成设置。
如果程序没有找到set方法,KVC机制会检查 +(BOOL)accessInstanceVariablesDirectly方法有没有返回YES,该方法默认返回YES,如果你重写了该方法返回NO,那么这一步KVC会执行setValue:forUndefinedKey:方法.因此,如果你不想让你的类被别人使用KVC的话,重写 +(BOOL)accessInstanceVariablesDirectly方法,让其返回NO即可,这样的话,如果KVC没有找到set属性名时,会直接调用setValue:forUndefinedKey:方法 一般开发者并不会重写+(BOOL)accessInstanceVariablesDirectly方法,接下来KVC会搜索该类里面有没有名为_的成员变量,无论是在类.h部分还是在类.m部分定义,也无论用了什么样的访问修饰符,只要存在_命名的变量,KVC都可以对该成员变量赋值.
如果该类中既没有set方法,也没有_成员变量,KVC机制会搜索_is的成员变量
如果该类还是没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量(即没有下划线的成员变量),再给他们赋值.
如果上面列出的方法或成员变量都不存在,系统将会执行该对象的setValue: forUndefineKey:方法,默认是抛出异常.
KVC如何处理异常
KVC中最常见的异常时使用了错误的key,或者传递了nil,KVC中有专门的方法来处理这些异常 通常在KVC操作Model时,抛出异常的那两个方法是需要重写的,如果直接抛出异常导致app崩溃是不合理的 错误的<key> 重写setValue: forUndefinedKey: 方法 一般是直接打印出来即可,或自己做特殊处理 传了<nil> 重写setNilValueForKey:就可以了
-(void)setNilValueForKey:(NSString *)key{ NSLog(@"不能将%@设成nil",key);}
KVC的使用
1.动态的取值和设值
2.用KVC来访问和修改私有变量
3.Model和字典转换
4.修改一些控件的内部属性
KVO
手动触发KVO
1.需要重写关闭属性的自动触发
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"name"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
2.[self.redView addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
添加这两个方法
[self.redView willChangeValueForKey:@"name"];
[self.redView didChangeValueForKey:@"name"];
此时就会自动调用
底层实现原理:
动态的创建了一个redView的类,重写了子类的set方法,在set方法中添加了willChangeValueForKey和didChangeValueForKey。
二、字典初始化传入nil崩溃
因为系统检测到传入的key的count和value的count不一致,进而导致崩溃,相当于越界了
三、UIImage imageNamed 与 imageWithContentsOfFile的差别
[UIImage imageNamed:]只适合与UI界面中小的贴图的读取,而一些比较大的资源文件应该尽量避免使用这个接口。
imageName的方式会在使用的时候系统会cache,程序员是无法处理cache的,这是由系统自动处理的,对于重复加载的图像,速度会提升很多,这样反而用户体验好。所以如果某张图片需要在应用中使用多次,或者重复引用,使用imageName的方式会更好
imageWithContentsOfFile的方式,在使用完成之后系统会释放,不会缓存下来,所以也就没有这样的问题。一般也不会把所有的图片都会缓存。有些图片在应用中只使用一两次的,就可以用这样的方式,比如新手引导界面的图片等等,就适合这样的方式。没有明显的界限。
四、iOS内存管理
栈(stack):栈是编译器自动分配并释放,用来存放函数的参数,局部变量。
堆(heap):堆一般是程序员自己分配和释放,如果我们在使用的过程中,没有释放,那么等到程序完全结束,系统将会对堆中的内容进行回收。一般开发中的alloc就是存放在堆中的
全局变量(静态变量)(static):全局变量和静态变量是单独存放的,因为他们的声明周期和整个程序的生命周期一致。释放的时间是由整个程序结束后系统负责回收。
文字常量区:一般用来存放常量字符串,比如说String *str = @"hello world",他的释放也和全局变量一致,在程序结束后进行释放。
程序代码区:用来存放函数的二进制内容的区域。
内存中存放的对象都是存放在堆上的,系统并不会释放堆上的内存,所以需要我们手动去管理。
内存管理分为ARC(自动内存管理)、MRC(手动内存管理)、自动释放池(autorelease pool)。
1.MRC时代,我们需要手动的添加retain和relase,
2ARC时代,不需要手动添加,系统会自动帮我们插入retain和relase,
3自动释放池(autorelease pool)实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。实际上对于 [NSString stringWithFormat:1.0] 这类构造函数返回的对象都是autorelease的。
手动添加的autorelease pool会在大括号执行完之后释放池子中的对象。
每次初始化好的对象都是autorelease对象,ARC下不需要手动加autorelease,MRC下需要手动加autorelease,ARC系统会自动帮我们插入retain和relase,MRC下需要手动加。
每个runloop里面都会默认有autorelease pool,当runloop休眠或停止的时候,会释放autorelease pool,释放autorelease pool的时候会调用池子中的autorelease对象的relase方法,释放对象。主线程中的autorelease pool是由若干个autorelease page组成,系统自动把不同的对象放到不同的page中,当runloop休眠的时候释放对应的page。
在实际中的使用场景其实很明确了, 在程序中中有大量临时变量的时候最好手动创建.
最常出现大量变量的时候显然是循环/遍历, 我们常用的for循环, 以及enumerate其实跟autoreleasepool也有关, for循环是不自动创建autoreleasepool的, 而enumerate中已经自动创建了autoreleasepool, 值得注意的是高并发enumerate常常会出一些意外的问题, 例如对象被提前释放, 所以建议高并发情况下使用for循环(性能高于enumerate), 再手动添加autoreleasepool.
autorelease pool来避免频繁申请/释放内存(就是pool的作用了)。这个应该是相对比较好理解的。
main.m里面的自动释放池只有app退出时才会释放。
autorelase与ralase的区别:realse后的对象会马上释放,autorelase相当于是延迟对象的释放,调用该方法后,对象不会被马上释放,只有当前runloop运行结束的时候才会释放。
五 iOS深拷贝与浅拷贝
浅拷贝:浅拷贝并不拷贝对象本身,只是对指向对象的指针进行拷贝
深拷贝:直接拷贝对象到内存中一块区域,然后把新对象的指针指向这块内存
在iOS中并不是所有对象都支持Copy和MutableCopy,遵循NSCopying协议的类可以发送Copy协议,遵循NSMutableCopying协议的类可以发送MutableCopy消息。如果一个对象没有遵循这两个协议而发送Copy或者MutableCopy消息那么会发生异常。如果要遵循NSCopying协议,那么必须实现copyWithZone方法。如果要遵循NSMutableCopying协议那么必须实现mutableCopyWithZone方法。
系统非容器类(NSString)不可变对象调用Copy方法其实只是把当前对象的指针指向了原对象的地址,而调用mutableCopy方法则是新分配了一块内存区域并把新对象的指针指向了这块区域。对于可变对象来说调用Copy和MutableCopy方法都会重新分配一块内存。但是copy和mutableCopy的区别在于copy在复制对象的时候其实是返回了一个不可变对象,因此当调用方法改变对象的时候会崩溃
对于容器类只有不可变的容器执行copy操作才是浅拷贝,所以所有的容器类使用mutableCopy是深拷贝
六、首页嵌套的手势冲突
解决方式通过计算tableview的偏移量,来固定他的偏移量。
拖动图片放大的功能实现 可以在拖动列表的时候,在代理方法中设置图片的frame来实现
七 GCD 的使用
执行方式分为同步异步 队列分为串行队列和并行队列
同步执行方法 任务按照顺序执行、不会开启新的线程
异步执行任务 任务可以并行处理,会开启新的线程
串行队列 队列是放任务的,队列里面的任务按照顺序执行 主队列是串行队列,主队列对应着管理者主线程的那条队列
并行队列 多个任务同时执行 全局队列是并行队列
同步串行队列 只有当串行队列为主线程时会卡死当前线程
异步串行队列 如果是主队列,不会开启新线程 如果是自己创建的队列,会开启新线程,按照顺序执行任务
https://www.jianshu.com/p/b89915a331ca
同步并行队列 不会开启子线程 ,串行执行任务
异步并行队列 会开启新的线程,并发的执行任务
串行队列 同步执行与异步执行的区别 ? 同步队列 sync的任务需要立即执行.任务按照顺序执行。异步队列,只能保证async里面的任务是顺序执行,asyncblock外面的代码与asyncblock里面的代码不顺序执行
同步并行队列 与 异步串行队列的区别 ? 同步并行队列不会开启子线程,异步串行队列会开启子线程。
调度组控制网络请求 需要配合dispatch_group_enter(group);dispatch_group_leave(group)
八 、iOS 分类(category)、类扩展(extension)、协议(protocol)
分类 category
使用场景分析
1.扩展已有的类
有大量的子类,需要添加公用方法,但又无法修改它们的父类的情形(如系统类)。
一般是大量的功能代码已经形成,使用子类需要添加新类的头文件等。分类只能添加方法,不能添加属性。(下文会提到如何添加属性)
2.使用父类私有方法
已经存在了大量的子类方法,但是又无法修改他们的父类,比如系统自带的类添加类扩展方法。在子类中声明父类类别后,即可通过编译。
category、extension异同点分析
1、分类(category)
① category只能添加“方法”,不能添加成员变量。
② 分类中可以访问原来类中的成员变量,但是只能访问@protect和@public属性。
③ 添加方法加上前缀,添加方法会覆盖父类的同名方法,可以防止意外覆盖,也防止被别人覆盖。
④ 分类中添加的成员变量,要通过getter、setter方法进行添加。
类扩展(extension)
① 类扩展的属性和方法都是私有的,也可以定义在.h中,这样就是共有的,类扩展中的方法是一定要实现的方法。Category没有这个限制。
分类有.h和.m两个文件 扩展只有.h 方法的实现是放在原类中的
九、消息传递
1.void objc_msgSend(void /id self,SEL op/)
当调用方法[self class] 相当于调用objc_msgSend(Self,@sel(class))
void objc_msgSuperSend(void /struct objc_super *super,SEL op/),objc_super结构体中接受者receiver就是当前对象
struct objc_super{
id receiver;
}
2.当调用方法的时候,会先在缓存中查找是否有该方法,没有的话通过isa指针去当前类方法列表中查找,没有的话再去父类查找,直到找到该方法,如果查找到根部(父类为nil比如NSObject)还没有,就会去调用消息转发机制
消息转发后调用的方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel
- (id)forwardingTargetForSelector:(SEL)aSelector{
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
- (void)forwardInvocation:(NSInvocation *)anInvocation
3.当一个类的方法没有实现的时候,可以通过runtime为方法添加实现