2018/3/29 完善(2)消息机制的转发流程<增加转发前的方法验签过程>
2018/3/29 验证(3)weak、assign的区别
2018/3/29 验证(4)__block修饰外部基本变量与未修饰的区别
2018/6/29 改进 (8) 加入深拷贝、浅拷贝的理解
2018/6/29 修正 (7) 原问题错误。造成误导
2018/7/2 增加 (11)内存管理答案
1.为什么说Objective-C是一门动态语言
1.Objective-C是一门运行时语言。在编译阶段不确定所有的东西,在运行时,根据运行环境确定对象的类型,对象对应类的方法实现等。
2.它的动态主要体现在3个方面,动态类型、动态绑定、动态加载
3.动态类型如id(弱类型);
动态绑定(将方法调用的时机推迟到运行时,在编译阶段,方法的调用不和代码绑定。只有消息发送出来之后,才会确定调用的代码);
动态加载(如动态资源加载,@2x、@3x)
2.什么是消息机制
在Objective-C中,方法调用是一个消息发送的过程(在java,C++等静态语言中是函数调用)。
OC对象发送一个消息的过程如下:
1.向对象发送消息
2.在缓存中查找是否有匹配方法。如果有则响应,否则继续
3.在对象的类的方法列表中(method list)查找是否有匹配方法,如果有则响应,否则继续
4.在对象的父类的缓存中查找是否有匹配方法。如果有则响应,否则继续
5.在对象的父类的方法列表中(method list)查找是否有匹配方法。如果有则响应,否则继续
6.进入动态解析 -(BOOL)resolveInstanceMethod:(SEL)sel;- (BOOL)resolveClassMethod:(SEL)sel;查看是否有动态添加方法实现。如果有则响应,否则继续
7.进入消息重定向 -(id)forwardingTargetForSelector:(SEL)sel;如果有指定消息接收对象则响应,否则(指:返回nil或者self)继续
8.进入方法签名- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector返回方法签名并进入下一步,否则调用doesNotRecongnizeSelector:方法并抛出异常。
9.进入消息转发 - (void)forwardInvocation:(NSInvocation *)aInvocation;如果有指定消息转发对象则响应,否则调用doesNotRecongnizeSelector:方法并抛出异常。
3.说下属性修饰词 copy、strong、weak、assign
copy: copy关键字的作用是产生一份副本,断开与源对象的关联。即源对象的修改不影响副本对象。从而避免在无意情况下修改了源对象,而造成副本对象的改变。
copy只能对遵守NSCopying、NSMutableCopying协议的对象进行操作。
copy不可变对象,是对源对象的引用计数+1,进行一次强引用。即浅拷贝;拷贝后的对象也是不可变对象。
copy可变对象,会重新开辟一份内存空间,存放源对象存储的值。即深拷贝;拷贝后的对象是不可变对象。
copy产生的对象都是不可变对象;mutableCopy产生的对象都是可变对象。
strong和weak修饰的对象都指向源对象值的地址。源对象值修改,也随之修改。区别是前者会对原对象的引用计数+1,防止原对象被释放;后者不会,当原对象引用计数为0时被释放时,weak的值也为nil了。
weak和assign都是弱引用一个对象,不会对源对象的引用计数产生影响(+1)。但是当源对象的引用计数为0时,assign修饰的对象会产生野指针,原因是assign虽然不会对源对象的引用计数+1,但指针仍然指向源对象。
而weak修饰的对象的指针会随源对象的销毁而销毁。
retain与strong相同。retain用于MRC,strong用于ARC。
weak,assign修饰的属性验证
图片.png
图片.png
4.block的使用及它的存储域问题
block是OC对象,继承自NSBlock类。
block可能会存储在全局数据区(_NSConcreteGlobalBlock)、堆区(_NSConcreteMallocBlock)、栈区(_NSConcreteStackBlock)。
在ARC环境下,block声明既可以用copy也可以用strong。使用copy是在MRC时代遗留的习惯问题。因为在ARC时,编译器会自动将block从栈区拷贝到堆区。避免在使用时,已被释放。
weak属性经常配合block使用,避免循环引用问题。<注:weak修饰符在iOS 5引入,只能在ARC环境下使用>
__block修饰基本数据类型。可以使其在block内部进行修改其值。原因从下图源码中可以看出,正常定义一个变量 a 时, block的实现中 是与外部变量 a 的定义结构相同;当使用__block修饰时,a会变为一个指针,使用指针引用外部变量 a 的地址。
图片.png
这是MyBlock的源码实现
图片.png
最后附一篇介绍block及__block的文章
5.MVC设计模式
MVC是一种架构模式,M表示MOdel,V表示视图View,C表示控制器Controller:
Model负责存储、定义、操作数据;
View用来展示数据给用户,和用户进行操作交互;
Controller是Model和View的协调者,Controller把Model中的数据拿过来给View用。
Controller可以直接与Model和View进行通信,而View不能和Controller直接通信。
View与Controller通信需要利用代理协议的方式,当有数据更新时,MOdel也要与Controller进行通信,这个时候就要用Notification和KVO,这个方式就像一个广播一样,MOdel发信号,
Controller设置监听接受信号,当有数据更新时就发信号给Controller,Model和View不能直接进行通信,这样会违背MVC设计模式。
6.在定义 property 的时候,atomic 和 nonatomic 有何区别?
atomic 和 nonatomic 的区别在于,系统自动生成的getter/setter 方法不一样。如果你自己写
getter/setter,那 atomic/nonatomic/retain/assign/copy 这些关键字只起提示作用,
写不写都一样。
对于atomic的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。
比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能
得到一个完好无损的对象。
而nonatomic就没有这个保证了。所以,nonatomic的速度要比atomic快。
不过atomic可并不能保证线程安全。如果线程 A 调了 getter,与此同时线程 B 、线程 C 都调了
setter——那最后线程 A get 到的值,3种都有可能:可能是 B、C set 之前原始的值,也可能是 B set
的值,也可能是 C set 的值。同时,最终这个属性的值,可能是 B set 的值,也有可能是 C set 的
值。
转自链接:https://www.jianshu.com/p/7288eacbb1a2
简单验证如下:
- (void)gcdGroup
{
/// init property name
self.name = @"origin";
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.bonlion.queue", DISPATCH_QUEUE_CONCURRENT);
/// thread A
dispatch_group_async(group, queue, ^{
self.name = @"A";
NSLog(@"thread A->%@", [NSThread currentThread]);
});
/// thread B
dispatch_group_async(group, queue, ^{
self.name = @"B";
NSLog(@"thread B->%@", [NSThread currentThread]);
});
/// thread C
dispatch_group_async(group, queue, ^{
NSLog(@"name value is ->%@", self.name);
NSLog(@"thread C->%@", [NSThread currentThread]);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"notify thread is used");
});
}
image.png
上图结果证明:在多线程情况下确实可以保证数据存取的完整性,但是并不能保证可以得到你想要的结果。此处可以使用GCD的信号量dispatch_semaphore来上锁,确保得到想要的结果
7.用copy修饰的NSMutableString、NSMutableArray、NSMutableDictionary类型的属性,如果改用strong修饰,可能会造成什么结果?
错误问题:(用@property声明的 NSString / NSArray / NSDictionary 经常使用 copy 关键字,为什么?如果改用strong关键字,可能造成什么问题?)
答:用copy修饰可变对象。会开辟一份新的内存空间,存放不可变类型,存储(源对象所存储)的值。从而避免源对象值的改变影响该对象的值(即产生一个副本对象)。
若使用strong修饰可变对象。即对源对象的引用计数+1。该对象的指针指向源对象的内存地址。当源对象值改变时,该对象的值也会随之改变。从而造成取值错误。
8.系统对象的 copy 与 mutableCopy 方法
不管是集合类对象(NSArray、NSDictionary、NSSet ... 之类的对象),还是非集合类对象
(NSString, NSNumber ... 之类的对象),接收到copy和mutableCopy消息时,都遵循以下准则:
1. copy 返回的是不可变对象(immutableObject);如果用copy返回值调用mutable对象的方法就会crash。
2. mutableCopy 返回的是可变对象(mutableObject)。
3. 只有对不可变对象进行copy操作是指针复制(浅复制),其它情况都是内容复制(深复制)!
何谓深拷贝、浅拷贝:区别在于深拷贝是重新开辟一块内存空间,指针指向新这块新的内存地址;
浅拷贝不开辟新的内存空间,指针还是指向源对象的内存地址
9.KVC底层实现
当一个对象调用setValue:forKey: or valueForKey:方法时,方法内部会做以下操作:
1). 检查是否存在相应的 key 的setter or getter方法,如果存在,就调用setter or getter方法。
2). 如果setter or getter 方法不存在,就会查找与 key 相同名称并且带下划线的成员变量,
如果有,则直接给成员变量属性赋值 or 取值。
3). 如果没有找到_key,就会查找相同名称的属性key,如果有就直接赋值 or 取值。
4). 如果还没有找到,则调用 setValue:forUndefinedKey: 和valueForUndefinedKey: 方法。
以上两个方法的默认实现都是抛出异常,我们可以根据需要进行重写。
10.KVO的原理
附一份KVO简单实现——有详细注释
KVO深层次的讲解
facebook的自释放KVO
基本概念:
KVO又称键(key)值(value)监听(observing)。
KVO的底层实现依赖于强大的runtime库。
KVO通过addObserver:forKeyPath:options:context:添加观察者对象(observer),观察目标对象
的某个属性(key)的新值或者旧值(options)的改变,关键字(context)可以作为区分使用 的方式实现值改变的回调机制。
使用场景:
1.一般使用于对scrollView的偏移量(contentOffset)值改变的监听。
2.商城购物车中改变商品数量,更新总价
底层原理:
在注册观察者之后,编译器会通过runtime的API动态创建注册一个中间类(派生类),继承自原本类(父类),然后将父类的 isa 指针指向这个子类(中间类)。同时,为子类动态添加了设置(setter)方法。
随后在 setter 方法中实现成对出现的 willChangeValueForKey: 与 didChangeValueForKey: 方法及父类的 setter 方法。然后再在didChangeValueForKey:方法中实现监听者的 observeValueForKeyPath: 方法,实现监听属性改变的过程。
大致过程如下:
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
// 表明观察策略--> 观察新值 与 旧值的改变
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
// 注册观察者,监听者为当前控制器,监听对象为 p。
[p addObserver:self forKeyPath:@"age" options:options context:@"as you like"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"\nkeyPath->%@\nchange->%@\ncontext->%@", keyPath, change, context);
}
图片.png
断点之后,发现使用runtime获取到的p对象所属的类为
NSKVONotifying_Person
。中间类的setter方法实现大致如下
图片.png
PS:附->可以通过runtime得知,中间类的类名及其对应的方法列表的方法名。中间类只有setter方法,同时还有一个 class 方法,说明它重写了NSObject类的class方法。为什么这么做呢?因为它内部的实现大致这样
- (Class)class{return object_getClass(class_getSuperclass(self));}
就可以做到过程欺骗。因为你使用[p class]方法得到的类还是Person类,除非使用runtime API 才能获得到真实类。
图片.png
11.iOS是如何管理内存的
在iOS中,使用引用计数来管理OC对象的内存。
一个新创建的对象引用计数是1,当引用计数为0时,OC对象就会销毁,释放其占用的内存。
调用retain会使引用计数+1,调用release会使引用计数-1。
内存管理经验总结:
当调用alloc、new、copy、mutableCopy方法时都会返回一个对象,在不需要这个对象时,要调用release或autorelease使其引用计数-1。
想要拥有某个对象就使其引用计数+1,不需要某个对象就使其引用计数-1。
网友评论