《基础知识整理温习一》
1、UIView 和CAlayer的区别和联系?
view是layer的代理,view主要处理与用户的交互,layer负责视图的绘制和动画的渲染,UIView继承自UIResponder,CALayer 继承NSObject,不可以响应事件,不可以处理事件。UIView主要是对显示内容的管理而 CALayer 主要侧重显示内容的绘制。
2、nil Nil null [NSNull null]的区别?
nil为一个对象的空指针。
Nil为一个类的空指针。
null为一个指向基本数据类型,C类型的空指针。
[NSNull null]是一个值为空的对象。
nil == Nil == Null 所以它们在object-c中是可以通用的,都表示空对象的意思。
[NSNull null]是值为空的对象。
“空对象”是已经释放了内存地址的对象,即不存在的对象。
“值为空的对象”是分配了地址,但是没有值得对象,是实际存在的对象。
3、block ?
3.1什么是block?
带有自动变量(局部变量)的匿名函数。
Block实质就是Objective-C对象。
int (^myBlock) (int num);
int 返回值类型
^语法标记,标记它是一个block类型
int num 函参类型和函参的变量名
//定义
int (^myBlock) (int num) = ^(int num ) {
return num + 10;
};
//循环引用的例子:
Person *person = [[Person alloc] init];
person.age = 20;
__weak typeof (person) weakPerson = person;
person.block = ^{
NSLog(@"age is %d",weakPerson.age);
};
person.block();
总结:循环引用是因为,对象强引用了blcok,block内部也强引用了捕获进去的对象,相互引用无法释放。
解决循环引用:
1、用__weak、__unsafe_unretained解决
2、用__block解决
__weak 、 __block、__unsafe_unretained的区别?
_block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型,__block对象可以在block中被重新赋值,__weak不可以,__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用。
__weak只能修饰对象,不能修饰基本数据类型,只在ARC下使用,可以避免循环引用,。
__weak当对象销毁时,会自动指向nil.
__unsafe_unretained: 当对象销毁时,会依然指向之前的内存空间,会造成野指针。
//并不是所有的block都会循环引用 ,比如下边这种:
[UIView animateWithDuration:(NSTimeInterval) animations:^{
self.name = @"";
}];
3.2 block为什么用copy?
block分为全局block,栈区block,堆区block。
block内部没有调用外部变量时存放在全局区(ARC和MRC下均是)
block使用了外部变量,这种情况也正式我们平时所常用的方式,Block的内存地址显示在栈区,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃,在对block进行copy后,block存放在堆区,这样让其拥有保存调用的外部变量的内存的能力,copy的原因是为了让Block在初始化作用域外进行正常访问外部变量。
3.3项目中怎么检查内存泄漏?
1、通过写一个基类控制器,BaseViewController,所有的页面都继承于它,然后重写dealloc方法,在页面返回的时候看有没有走该方法。或者通过写UI ViewController的分类,里面通过runtime的方法交换dealloc方法实现页面打印,这样不用所有的页面都继承于基类。
2、使用xcode的Instruments -> Leaks进行检测
3、使用三方库,MLeaksFinder进行检测。
4、属性修饰符?
Strong:除NSString\block以外的OC对象
Weak:修饰已经被添加到父控件上的控件,比如xib拖出来的控件, 以及 delegate
Assign: 基本数据类型、枚举、结构体(非OC对象)
Copy : NSString,block
4.1 NSString为什么要用copy修饰,用strong可以吗?有什么区别?
//用strong修饰的时候
@property (nonatomic ,strong) NSString *name;
- (void)setName:(NSString *)name {
[_name release];
[name retain];
_name = name;
}
//用copy修饰的时候
@property (nonatomic ,copy) NSString *name;
- (void)setName:(NSString *)name {
[_name release];
_name = [name copy ];
}
copy修饰的时候是先释放旧值,然后把新值copy一份赋值给它,strong修饰的时候是先释放旧值,然后把新值赋值给它,为了防止可变字符串对其进行赋值时候内容被篡改,所以用copy修饰。
@property (nonatomic ,strong) NSString *name;
NSMutableString *newName = @"ZhangSan";
self.name = newName;
newName = @"LiSi";
self.name 会随着newName的改变而改变。
@property (nonatomic ,copy) NSString *name;
使用copy修饰的时候,是将新值copy一份然后赋值,当新值改变的时候,copy没变,所以不会影响属性的值。
4.2 delegate为什么用weak修饰?
避免循环引用
For Example: tableView
Controller - > tableView - >delegate ->Controller这形成了一个圈,如果delegate是strong的时候,就会造成循环引用。
4.3 Assign 和 Weak的区别?
Assign 修饰基础数据类型,非OC对象,如NSInter,CGFloat,BOOL等基础数据类型。不牵涉到内存管理。如果用assign修饰对象的时候,当修饰的对象销毁之后,指针不会置为nil,就会造成野指针的出现,当再向该对象发消息就会出现崩溃。
Weak 修饰OC对象,当修饰的对象销毁之后,指针会自动置为nil,之后再向该对象发消息也不会崩溃.
4.3.1weak指针自动置为nil的底层实现?
Runtime 维护了一个 Weak 表,用于存储所有 Weak 指针。Weak 表是一个哈希表,Key 是对象的地址,Value 是一个数组,数组里面放的是 Weak 指针的地址,然后遍历这个数组,把每个地址存储的数据设为 nil ,最后把这个 key-value entry 从 Weak 表中删除。
5、内存管理?
什么是内存管理?是指软件运行时对计算机内存资源的分配和使用的技术。其最主要的目的是如何高效,快速的分配,并且在适当的时候释放和回收内存资源。
想要知道内存怎么管理首先我们要知道内存是怎么分配的?
下面是iOS一个app的内存分布,分为代码区,常量区,全局/静态区,堆区,栈区。
app程序一般存放于ROM(储存行内存)中。启动app时,系统会把开启的app程序从ROM中转移到RAM(运行内存)中。不能再说了,再说就讲计算机组成原理了。
代码区 :存放程序的二进制代码。
常量区:存放常量字符串,程序结束后由系统释放程序结束释放。
全局/静态区:全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量存放在一块区域,未初始化的全局变量和静态变量在相邻的另一块区域,程序结束后由系统释放。
栈区:由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点时有限制,数据不灵活。[先进后出]由编译器自动分配释放。
堆区:由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。一般由程序员分配释放,若程序员不释放,程序结束时 可能由OS回收 。
MRC:Manual Reference Counting 手动引用计数器,需要我们手动管理对象引用计数器的值。
ARC:automatic reference counting自动引用计数,在程序编译时自动加入retain/release。在对象被创建时retain count+1,在对象被release时count-1,当count=0时,销毁对象。
Autoreleasepool:加入autoreleasepool对象会由系统自动加上autorelease方法,如果该对象引用计数为0,则销毁。
5.1 ARC 在编译时做了哪些工作?
自动调用保留(retain)与释放(release)的方法
相对于垃圾回收这类内存管理方案,ARC 不会带来运行时的额外开销,所以对于应用的运行效率不会有影响。ARC会把能够互相抵消retain、release、autorelease,操作简化,如果发现在同一个对象上执行了多次保留与释放操作,那么ARC有时可以成对的移除这两个操作。
5.2 ARC 在运行时做了哪些工作?
主要是指weak关键字。weak修饰的变量能够在引用计数为0时被自动设置成nil,显然是有运行时逻辑在工作的。关于原因会单独开一个问题
为了保证向后兼容性,ARC在运行时检测到类函数中的autorelease后紧跟其后retain,此时不直接调用对象的autorelease方法,而是改为调用objc_autoreleaseReturnValue。
objc_autoreleaseReturnValue会检视当前方法返回之后即将要执行的那段代码,若那段代码要在返回对象上执行retain操作,则设置全局数据结构中的一个标志位,而不执行autorelease操作,与之相似,如果方法返回了一个自动释放的对象,而调用方法的代码要保留此对象,那么此时不直接执行retain,而是改为执行objc_retainAoutoreleasedReturnValue函数。此函数要检测刚才提到的标志位,若已经置位,则不执行retain操作,设置并检测标志位,要比调用autorelease和retain更快。
6、KVC
解释:键 – 值编码(Key-ValueCoding),它可以直接通过key值对对象的属性进行存取操作,而不需通过调用明确的存取方法。这样就可以在运行时动态在访问和修改对象的属性,而不是在编译时确定。
访问方法:
valueForKey:
valueForKeyPath:
设置方法:
setValue:forKey:
setValue: forKeyPath:
当根据KVC搜索规则,没有搜索到对应的key或者keyPath,则会调用对应的异常方法。异常方法的默认实现,在异常发生时会抛出一个NSUndefinedKeyException的异常,并且应用程序Crash。
我们可以重写下面两个方法,根据业务需求合理的处理KVC导致的异常:
在取值时,未有对应的key:
- (nullable id)valueForUndefinedKey:(NSString*)key;
在赋值时,未有对应的key:
- (void)setValue:(nullable id)value forUndefinedKey:(NSString*)key;
KVC底层实现原理:runtime。
KVC 的Getter搜索模式:
先找相应的 getter 方法(get<Key>, <key>, is<Key>, 或者 _<key>),找到了则返回(对象类型直接返回,其它类型进行转换);
没有找到,则寻找 NSArray 相应的方法;
没有找到,则寻找 NSSet 相应的方法;
如果还没有,且 accessInstanceVariablesDirectly 类属性返回的是 YES,则去搜索实例变量(_<key>、_is<Key>、<key>、is<Key>)。如果发现了,则返回;
还没有,则转到 valueForUndefinedKey: 方法并抛出异常。
KVC的setter搜索模式:
先找 setter 方法(set<Key>:或_set<Key>);
没找到,如果则 accessInstanceVariablesDirectly 类属性返回的是 YES,则去查找实例变量(_<key>、_is<Key>、<key>、is<Key>),若找到,则赋值;
没有找到 setter 方法和实例变量,则转到 setValue:forUndefinedKey: 方法,并抛出异常。
应用:
1、字典转模型
2、对象归档,解档
3、动态地取值和设值
4、用KVC来访问和修改私有变量
5、修改一些控件的内部属性
比如UITabBarController的UITabbar不能满足我们的情况的时候,我们可以创建一个继承于UITabbar的类A,重写layoutSubviews对其进行样式调整,最后,[self setValue:myTabBar forKey:@"tabBar"];对其进行修改。
6. xib/storyboard设置圆角的地方
7、KVO
解释:键值观察Key-Value-Observe,提供了一个对象监听另外一个对象属性的变化的机制。
底层实现原理:
1.KVO是基于runtime机制实现的
2.当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
3.如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
4.每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
5.键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
应用:
主要用于UI和数据的绑定方面。