iOS面试题总结(一)

1.为什么不能给类别category 添加成员变量?extension呢?

2.isKindOfClass: 和 -isMemberOfClas区别?

3.weak的实现原理

4.理解 [self class] 与 [super class] ?

5.ios中的内存管理机制

6.Block如何访问外部变量? 下划线__block的作用? 如何防止循环引用?

7.Block循环引用问题
(1).为什么Msonry不会循环引用?
(2).weakSelf、strongSelf结合使用

8.深拷贝与浅拷贝

9.KVO实现原理

10.Runtime的原理及实际使用场景

讲解

1.为什么不能给类别category 添加成员变量?extension呢?
分类并不会改变原有类的内存分布的情况,它是在运行期间决定的,此时内存的分布已经确定,若此时再添加实例会改变内存的分布情况,这对编译性语言是灾难,是不允许的。
category 是基于运行时的:

typedef struct category_t {
const char *name; //类的名字
classref_t cls; //类
struct method_list_t *instanceMethods; //category中所有给类添加的实例方法的列表
struct method_list_t *classMethods; //category中所有添加的类方法的列表
struct protocol_list_t *protocols; //category实现的所有协议的列表
struct property_list_t *instanceProperties; //category中添加的所有属性
} category_t;

结构体中并没有成员变量这个list,所以无法在category添加成员变量,添加属性是可以的,但是不会生成添加属性的getter和setter方法,所以,尽管添加了属性,也无法使用点语法调用getter和setter方法,但你可以使用运行时实现关联对象并可以引用。

反观扩展(extension),作用是为一个已知的类添加一些私有的信息,必须有这个类的源码,才能扩展,它是在编译器生效的,所以能直接为类添加属性或者实例变量。

category跟extension最大的区别在于生效时间不一样,category在运行时生效,而extension在编译时生效

2.isKindOfClass: 和 -isMemberOfClas区别?
isKindOfClass来确定一个对象是否是一个类的成员,或者是派生自该类的成员。一个实例对象的类对象或类对象的元类是否与当前类相等,或派生自当前类)
isMemberOfClass只能确定一个对象是否是当前类的成员。(一个实例对象的类对象或类对象的元类是否与当前类相等
详解
类方法+ (BOOL)isKindOfClass:(Class)cls
元类 --> 根元类 --> 根类 --> nil 与 传入类的对比
这里会先取类对象的isa (self->ISA()),类对象的isa指向的是元类,判断元类是否与cls相等,而且这里是一个for循环,不相等的话会往元类的父类(tcls = tcls->superclass)循环去查找对比。
实例方法- (BOOL)isKindOfClass:(Class)cls
对象的类 --> 父类 --> 根类 --> nil 与 传入类的对比
这里是先取实例对象的isa ([self class]),也就是类对象,判断类对象与cls是否相等,不相等的话会往类对象的父类(tcls = tcls->superclass)循环去查找对比。

类方法+ (BOOL)isMemberOfClass:(Class)cls
类的元类 与 传入类 对比
判断的是类对象的isa(即元类)是否等于cls。
实例方法- (BOOL)isMemberOfClass:(Class)cls
对象的父类 与 传入类 对比
判断的是实例对象调用class方法的返回值(也就是类对象)是否与cls相等。
iOS 捋清楚 isKindOfClass 与 isMemberOfClass
iOS 底层原理探索之 isKindOfClass & isMemberOfClass

3.weak的实现原理

weak是Runtime维护了一个hash(哈希)表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。

对象准备释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。

        A.x = B;
 __weak B.y = A;

当A释放时,会根据A的地址获取所有弱引用它的指针的地址(如B.y),把它置为nil。
4.理解 [self class] 与 [super class] ?

我们知道实际上在iOS中,对方法的调用是通过发送消息来完成的。也就是说使用 [self class] 时,会使用obj_msgSend(id theReceiver, SEL selector, ...)函数向Receiver来发送消息。而使用 [super class] 时,会使用obj_msgsendSuper(...)函数向Receiver来发送消息。
————————————————

@implementation Son : Father 
- (id)init 
{ 
self = [super init]; 
if (self) { 
NSLog(@”%@”, NSStringFromClass([self class])); 
NSLog(@”%@”, NSStringFromClass([super class])); 
} 
return self; 
} 
@end

上边代码会输出什么,为什么
简单来说,self和super都是指向当前实例的,不同的是,[self class]会在当前类的方法列表中去找class这个方法,[super class]会直接开始在当前类的父类中去找calss这个方法,两者在找不到的时候,都会继续向祖先类查询class方法,最终到NSObject类。那么问题来了,由于我们在Father和Son中都没有去重写class这个方法,最终自然都会去执行NSObject中的class方法,结果也自然应该是一样的。
至于为什么是Son,我们可以看看NSObject中class的实现:

-(Class)class { 
return object_getClass(self); 
}

5.ios中的内存管理机制
为了管理所有对象的引用计数和weak指针,苹果创建了一个全局的SideTables,虽然名字后面有个"s"不过他其实是一个全局的Hash表,里面的内容装的都是SideTable结构体而已。它使用对象的内存地址当它的key。管理引用计数和weak指针就靠它了。

图片.png

6.Block如何访问外部变量? 下划线__block修饰auto变量的作用? 如何防止循环引用?
auto变量含义:离开作用域(大括号),会自动释放的变量

  • block在访问auto变量(局部变量)时,block内部会捕获到外部变量的值,后面修改外部auto变量的值,block内部的值不会随着改变而改变

  • block在访问static变量(局部变量)时,block内部会捕获到外部变量的地址值,所以后面修改外部static变量值的时候,通过地址访问到的是最新修改后的值。

  • 当auto变量加了__block修饰,编译器会将__block变量包装成一个对象,会捕获到block内部,并进行指针传递,所以能够修改其值(类似于访问static变量)。

另外,静态变量、全局变量和全局静态变量,传入的就是地址值,可以直接被block修改

  • 为了避免循环引用,最好的是对block内部引用的self对象进行弱引用。一般使用一个弱指针来指向该对象,然后在block内使用该弱引用指针来进行操作,这样就避免了block对该对象的强引用。

7.Block循环引用问题
(1).为什么Msonry不会循环引用?
查看masonry源码可以看到究竟:msonry中设置布局的方法中的block对象并没有被View所引用,而是直接在方法内部同步执行,执行完以后block将释放,其中捕捉的外部变量的引用计数也将还原到之前。
(2).weakSelf、strongSelf结合使用
使用weakSelf结合strongSelf的情况下,能够避免循环引用,也不会造成提前释放导致block内部代码无效(野指针问题)

_person1 = [[Person alloc] init];
_person2 = [[Person alloc] init];
_person2.name = @"张三";
__weak __typeof(self) weakSelf = self;
_person1.block = ^{
    __typeof(&*weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf.person2.name);
    });
};

8.深拷贝与浅拷贝
浅拷贝就是拷贝之后,并没有真正的复制,而是复制对象和原对象都指向同一个地址
深拷贝是真正的复制了一份,复制的对象只想新的地址

  • copy:对于可变对象为深拷贝,对于不可变对象为浅拷贝
  • mutablecopy:始终为深拷贝
    注:
    oc中只有遵循才支持copy,只有遵循才支持mutablecopy,如果没有遵循,拷贝时会直接crash。
    这两篇文章我觉得总结的不错
    深刻理解iOS中的“深拷贝”和“浅拷贝”
    iOS中NSString的strong、copy的使用

9.KVO实现原理
当某个类的对象属性第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法。派生类在被重写的setter方法内实现真正的通知机制。
当观察对象A时,KVO机制动态创建一个新的名为NSKVONotifying_A的新类,该类集成字对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter方法,setter方法会负责在调用元setter方法之前和之后,通知所有观察对象属性值的更改情况。
被观察属性发生改变之前,willChangeValueForkey:被调用,通知系统该keyPath的属性值即将变更;当改变发生后,didChangeValueForkey:被调用,通知系统该keyPath的属性值已经变更;之后,observeValueForKey:ofObject:context:也会被调用。且重写观察属性的setter方法这种继承方式的注入是在运行时而不是编译时实现的。

10.Runtime的原理及实际使用场景
主动使用
1.字典转模型
2.给分类属性添加get,set方法
3.方法交换swizzling
4.设置UITextField占位文字的颜色
···

隐式调用
1.KVO与KVC的实现。
2.内存管理,weak表的维护。
···

你可能感兴趣的:(iOS面试题总结(一))