iOS面试题总结 (2019年6月)(3)
1、属性默认关键词是那些,NSString常用什么修饰,为什么?
对应基本数据类型默认关键字是:atomic, readwrite, assign。
对于普通的 Objective-C 对象默认关键字是:atomic, readwrite, strong。
NSString用copy修饰。为了安全,一般情况下,我们都不希望字串的值跟着字符串变化,所以我们一般用copy来设置string的属性。
@property (strong,nonatomic) NSString *rStr;
@property (copy, nonatomic) NSString *cStr;
- (void)test{
NSMutableString *mStr = [NSMutableString stringWithFormat:@"abc"];
self.rStr = mStr;
self.cStr = mStr;
NSLog(@"mStr:%p,%p", mStr,&mStr);
NSLog(@"strongStr:%p,%p", _rStr, &_rStr);
NSLog(@"copyStr:%p,%p", _cStr, &_cStr);
}
假如,mStr对象的地址为0x11,也就是0x11是@“abc”的首地址,mStr变量自身在内存中的地址为0x123;
当把mStr赋值给strong的rStr时,rStr对象的地址为0x11,rStr变量自身在内存中的地址为0x124;rStr与mStr指向同样的地址,他们指向的是同一个对象@“abc”,这个对象的地址为0x11,所以他们的值是一样的。
当把mStr赋值给copy的cStr时,cStr对象的地址为0x22,cStr变量自身在内存中的地址0x125;cStr与mStr指向的地址是不一样的,他们指向的是不同的对象,所以copy是深复制,一个新的对象,这个对象的地址为0x22,值为@“abc”。
如果现在改变mStr的值:
//注意mStr如果这里是不可变字符串,那么这里无法改变,是浅拷贝。会崩溃
[mStr appendString:@"de"];
NSLog(@"strongStr:%@", _rStr);
NSLog(@"copyStr:%@", _cStr);
结果:
使用strong的字串rStr的值:@"abcde",
而使用copy的字串cStr的值:@"abc",
如果是不可变字符串
NSString * str = @"abc";
self.rStr = str;
self.cStr = str;
str = @"66666666";
NSLog(@"%@===%@",self.rStr,self.cStr);//打印结果都是abc
2、nullable、nonnull 、NS_ASSUME_NONNULL_BEGIN 和 NS_ASSUME_NONNULL_END 的含义和用途?
__nullable指代对象可以为NULL或者为NIL
__nonnull指代对象不能为null
当我们不遵循这一规则时,编译器就会给出警告。
事实上,在任何可以使用const关键字的地方都可以使用__nullable和__nonnull,不过这两个关键字仅限于使用在指针类型上。而在方法的声明中,我们还可以使用不带下划线的nullable和nonnull,如下所示:
- (nullable id)itemWithName:(NSString * nonnull)name
在属性声明中,也增加了两个相应的特性,因此上例中的items属性可以如下声明:
@property (nonatomic, copy, nonnull) NSArray * items;
当然也可以用以下这种方式:
@property (nonatomic, copy) NSArray * __nonnull items;
推荐使用nonnull这种方式,这样可以让属性声明看起来更清晰。
如果需要每个属性或每个方法都去指定nonnull和nullable,是一件非常繁琐的事。苹果为了减轻我们的工作量,专门提供了两个宏:NS_ASSUME_NONNULL_BEGIN和NS_ASSUME_NONNULL_END。在这两个宏之间的代码,所有简单指针对象都被假定为nonnull,因此我们只需要去指定那些nullable的指针。如下代码所示:
NS_ASSUME_NONNULL_BEGIN
@interface TestNullabilityClass ()
@property (nonatomic, copy) NSArray * items;
- (id)itemWithName:(nullable NSString *)name;
@end
NS_ASSUME_NONNULL_END
3、BAD_ACCESS在什么情况下出现?
访问了悬垂指针,比如对一个已经释放的对象执行了release、访问已经释放对象的成员变量或者发消息。 死循环。
4、以下代码输出什么?
- (void)deadLockCase1 {
NSLog(@"1"); // 任务1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任务2
});
NSLog(@"3"); // 任务3
}
控制台输出:1 ,后面就崩溃了。
5、isKindOfClass和isMemberOfClass的区别?selector的作用?
相同点:
都是NSObject的比较Class的方法.
不同点:
isKindOfClass:确定一个对象是否是一个类的成员,或者是派生自该类的成员。或者是继承自某类。
isMemberOfClass:确定一个对象是否是当前类的成员.
selector:通过方法名,获取在内存中的函数的入口地址。
6、使用NSOperationQueue的addOperationWithBlock要考虑循环引用吗,为什么?
7、为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern"C"{
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
答案:
#ifndef __INCvxWorksh
#define __INCvxWorksh
#endif
这一段是用来防止头文件重复引用,vs可以使用#pragma once。但是推荐使用宏定义来防止重复包含,其跨平台,兼容性好。
如果是C++文件,以C的方式编译,并执行{}内的指令。其目的是为了兼容C代码,常出现在动态链接库的代码中。
#ifdef _cplusplus //这句表示如果是c++文件
extern"C"{ //用extern "C"把一段代码包起来
#endif
#ifdef _cplusplus
}
#endif
8、HTTP七层协议
TCP协议对应于传输层
网络七层协议由下往上分别为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。其中物理层、数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象;传输层、会话层、表示层和应用层则被称作主机层,是用户所面向和关心的内容。
HTTP协议对应于应用层,TCP协议对应于传输层,IP协议对应于网络层,HTTP协议是基于TCP连接的,三者本质上没有可比性。 TCP/IP是传输层协议,主要解决数据如何在网络中传输;而HTTP是应用层协议,主要解决如何包装数据。Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是它的一组接口。
9、Objective-C 消息发送与转发机制原理
消息发送和转发流程可以概括为:
消息发送(Messaging)是 Runtime 通过 selector 快速查找 IMP 的过程,有了函数指针就可以执行对应的方法实现;
另外:(Objective-C动态性的根源在方法的调用是通过message来实现的,一次发送message的过程就是一次方法的调用过程。发送message只需要指定对象和SEL,Runtime的objc_msgSend会根据在信息在对象isa指针指向的Class中寻找该SEL对应的IMP,从而完成方法的调用。)
消息转发(Message Forwarding)是在查找 IMP 失败后执行一系列转发流程的慢速通道,如果不作转发处理,则会打日志和抛出异常。
消息查找过程IMP
(1)缓存查找
(2)继续在类的继承体系中查找
10、底层解析weak的实现原理?
weak 实现原理的概括
Runtime维护了一个weak表,用于存储指向某个对象的所有weak指针。weak表其实是一个hash(哈希)表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象指针的地址)数组。
weak 的实现原理可以概括一下三步:
1、初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
2、添加引用时:objc_initWeak函数会调用 objc_storeWeak() 函数, objc_storeWeak() 的作用是更新指针指向,创建对应的弱引用表。
3、释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
11、Block 底层原理总结,有几种类型的Block,Block的循环引用原理?
(1)Block可以简单总结:
block本质上也是一个OC对象,它内部也有个isa指针;
block是封装了函数调用以及函数调用环境的OC对象.
(2)Block 有三种类型:
NSGlobalBlock 全局区的Block
NSStackBlock 栈区的Block
NSMallocBlock 堆区的Block
(3)Block的循环引用原理?
对象、变量、Block的相互持有
那么如何解决这个问题呢?
通常我们ARC环境下面的解决办法是通过__weak指针来解决这个问题,通过上面讲的Block里面的变量是通过访问的外部变量是否是strong或weak指针来进行内部对象进行相应修饰的,所以如果访问的外部对象是weak指针时,他们的引用关系就会如下图:
12、YTCar *car 实现对象car的手动内存管理模式下的set方法
//是一个不断去掉旧值赋新值的过程
- (void)setCar:(YTCar *)car{
if (_car != car) { //判断新旧值是否相等
//release掉旧值
[_car release];
//retain新值
_car = [car retain];
}
}
13、NSThread、 GCD、NSOperation的区别?
1)NSThread
优点:NSThread 比其他两个轻量级。
缺点:需要自己管理线程的生命周期,线程同步。
线程同步对数据的加锁会有一定的系统开销。
3)GCD
替代NSThread等线程,自动管理生命周期。
2)NSOperation
优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。
基于GCD底层,使用起来更加面向对象。比GCD多了一些简单实用的功能。
14、autorelease 自动释放池的释放时机,autoreleasepool的实现原理?
(1)runloop就是iOS中的消息循环机制,当一个runloop结束时系统才会一次性清理掉被autorelease处理过的对象,其实本质上说是在本次runloop迭代结束时清理掉被本次迭代期间被放到autorelease pool中的对象的。至于何时runloop结束并没有固定的duration!
(2)当一个autorelease pool被drain 的时候,会对pool里的每一个对象发送一个release消息;
(3)每一个线程(包括主线程)都有一个AutoreleasePool栈。当一个新的池子被创建的时候,push进栈,当池子被释放内存时,pop出栈。对象调用autorelease方法进入栈顶池子中。当线程结束的时候,会自动地销毁所有跟它有关联的池子。
使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
在普通for循环和for in 循环中没有,当for循环中便利产生大量autorelease变量时,就需要手动加局部AutoreleasePool。
autoreleasepool的实现原理?
AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。
AutoreleasePool底层实现原理
autoreleasepool的实现原理
15、OC中如何判断两个对象完全相同?
(1)isEqual和hash
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![self class] == [object class]) {
return NO;
}
//此处还需判断对象中的各个属性是否相同
...
//若所有属性都相同则返回
return YES;
}
首先判断两个指针是否相等,若相等,则均指向同一对象,所以受测的对象也必定相等。接下来判断两对象所属的类,若属于同一类。
hash:比较得时候最好是先计算其哈希码,再进行比较。
(2)==
比较的是两个对象的指针本身,有时候返回的结果并不是我们想要的结果。
(3)isEqualToString
用于判断两个字符串是否相等的方法,当然还有isEqualToArray: isEqualToDictionary:
(4)NSSet中可变类的等同性比较【数组去重】
OC 判断两个对象是否相等
16、retain一个NSTime类型成员变量会有什么问题?
使用NSTimer可能会碰到循环引用的问题。特别是当类具有NSTimer类型的成员变量,并且需要反复执行计时任务时。
_timer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(startCounting) userInfo:nil
repeats:YES];
类有一个成员变量_timer,给_timer设置的target为这个类本身。这样类保留_timer,_timer又保留了这个类,就会出现循环引用的问题,最后导致类无法正确释放。
解决这个问题的方式也很简单,当类的使用者能够确定不需要使用这个计时器时,就调用
[_timer invalidate];
_timer = nil;
17、Category的使用及原理?与Runtime有关吗?