沃梦达 / IT编程 / 移动开发 / 正文

2021年iOS高级工程师最新面试题大全_基础知识

1. unicode一个汉字占多少字节?字母呢?一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间.2. NSObject结构体里面有什么,大小为什么是16字节?一个OC对象的内存布局?struct objc_objec...

1. unicode一个汉字占多少字节?字母呢?

一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间.

2. NSObject结构体里面有什么,大小为什么是16字节?一个OC对象的内存布局?

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
objc_class是从objc_object中继承而来,所以objc_class中也有isa结构体
在objc_class结构体中关于class_data_bits_t的注释:class_rw_t * plus custom rr/alloc flags,意思是class_data_bits_t相当于class_rw_t * 加上rr/alloc标志。它提供了便捷的方式data()方法返回class_rw_t *指针

3. 所有OC对象都继承与NSObject吗,id是什么含义?

不是,NSProxy是一个虚类,它可以被继承,并重写下面两个方法来实现消息转发到另一个实例
-(void)forwardInvocation:(NSInvocation*)invocation;
-(NSMethodSignature*)methodSignatureForSelector:(SEL)sel;
NSProxy就是:负责将消息转发到真正的target的代理类

id:是一种数据类型;
id类型被定义为指向对象的指针

4. oc是单继承吗?如果要实现多个继承怎么实现?

是单继承 可以通过消息转发、delegate和protocol和类别来实现类似多继承。

5. 问类是否是一个对象,如果是它继承什么?

有类对象  objc_class 继承于 objc_object

6. iOS中的各控件中的继承关系图?

7. nsdictionary字典的原理?追问重复的key是怎么排列的?取的时候是怎么取的?

字典通过使用- (void)setObject:(id)anObject forKey:(id)aKey;方法,用Hash表来实现key和value之间映射和存储的。
哈希概念:哈希表本质是一个数组,每一个元素称为一个箱子,而箱子里面的存放的是键值对。
哈希表(Hash Table)也叫做散列表,这是根据关键码(key vlaue)而直接进行访问的数据结构。换句话说,通过把关键码映
射到表里面的一个位置来访问记录,用来加快查找的速度。映射的函数就叫做散列函数,存放记录的数组也叫做散列表。

哈希存储过程
3.1 首先根据key计算出它的哈希值h
3.2 如果箱子的个数为n,那么key值是应该放在第(h%n)个箱子中
3.3 如果该箱子已经有了值,就使用开放寻址法或者拉链法进行解决冲突。
如果我们在使用拉链法解决哈希冲突时候,每个箱子都是一个链表,属于同一个箱子的所有键值都会排列在链表中。

8. NSArray的存储形式、有哪几种存储形式?

实际的数组元素被存储在堆(heap)内存中;数组引用变量是一个引用类型的变量,被存储在栈(stack)内存中

数组就是在内存中开辟一块连续的、大小相同的空间,用来存储数据。 连续:内存地址是连续的

9. 可变数组的实现原理?

不管创建的事可变还是不可变的数组,在alloc之后得到的类都是 __NSPlaceholderArray。而当我们init一个不可变的空数组之后,得到的是**__NSArray0**;如果有且只有一个元素,那就是 __NSSingleObjectArrayI;有多个元素的,叫做 __NSArrayI;init出来一个可变数组的话,都是 __NSArrayM。

我们看到__NSPlaceholderArray的名字就知道它是用来占位的。

循环缓冲区(也称为环形缓冲区)是固定大小的缓冲区,工作原理就像内存是连续的且可循环的一样。在生成和使用内存时,不需将原来的数据全部重新清理掉,只要调整head/tail 指针即可。当添加数据时,head 指针前进。当使用数据时,tail 指针向前移动。当到达缓冲区的尾部时,指针又回到缓冲区的起始位置。

10. 问 从字典中取数据,为什么比在数组中取数据快,底层原理是什么?

Hash

11. NSCache和NSMutableDictionary的相同点与区别?

NSCache和NSMutableDictionary功能用法基本是相同的
NSCache是线程安全的,NSMutableDictionary线程不安全,Mutable开发的类一般都是线程不安全的
当内存不足时NSCache会自动释放内存(所以从缓存中取数据的时候总要判断是否为空)
NSCache可以指定缓存的限额,当缓存超出限额自动释放内存,NSCache 是一个容器类,类似于NSDIctionary,通过key-value 形式存储和查询值,用于临时存储对象。
NSCache的Key只是对对象进行了Strong引用,而非拷贝,所以不需要实现NSCopying协议
NSCache胜过NSDictionary之处在于,当系统资源将要耗尽时,它可以自动删减缓存。如果采用普通的字典,那么就要自己编写挂钩,在系统发出“低内存”通知时手工删减缓存。
NSCache并不会“拷贝”键,而是会“保留”它。此行为用NSDictionary也可以实现,然而需要编写相当复杂的代码。NSCache对象不拷贝键的原因在于:很多时候,键都是不支持拷贝操作的对象来充当的。因此,NSCache不会自动拷贝键,所以说,在键不支持拷贝操作的情况下,该类用起来比字典更方便。另外,NSCache是线程安全的,而NSDictionary则绝对不具备此优势

12. NSArray和NSSet有什么区别?

NSSet  , NSMutableSet类声明编程接口对象,无序的集合,不重复的,  在内存中存储方式是不连续的,不像NSArray(是有序的集合)类声明编程接口对象是有序集合,可以有重复对象,  在内存中存储位置是连续的;
NSSet和我们常用NSArray区别是:在搜索一个一个元素时NSSet比NSArray效率高,主要是它用到了一个算法hash(散列,也可直译为哈希)
NSSet通过anyObject访问元素,NSArray通过下标访问

13. 数组的深拷贝和浅拷贝?

14. const extern static 用法区别?

const用来修饰右边的基本变量或指针变量  被修饰的变量只读,不能被修改

修饰基本数据类型 不能进行修改

修饰对象 因为修饰的是对象本身,所以不能修改对象的地址,但是可以修改对象中的属性,因为属性是没有被const修改的。

static修饰局部变量 保证局部变量永远只初始化一次,在程序的运行过程中永远只有一份内存,  生命周期类似全局变量了,但是作用域不变。

static修饰全局变量 使全局变量的作用域仅限于当前文件内部,即当前文件内部才能访问该全局变量。

iOS中在一个文件声明的全局变量,工程的其他文件也是能访问的,但是我又不想让其他文件访问,这时就可以用static修饰它了

static修饰函数时,被修饰的函数被称为静态函数,使得外部文件无法访问这个函数,仅本文件可以访问。

extern 这个单词翻译过来是“外面的、外部的”。顾名思义,它的作用是声明外部全局变量。这里需要特别注意extern只能声明,不能用于实现,而且定义和分配内存都在原来类中。

15. 用extern的情况下什么时候程序会报错?

1,MAIN.C(4): error C279: 'XXX': multiple initialization   (重复初始化)
出现这个错误的原因是定义全局变量的时候,对该变量多次初始化(赋值或者其他操作)。

2,*** WARNING L1: UNRESOLVED EXTERNAL SYMBOL    (变量只申明而没有定义)
在文件中定义该变量即可。

3,*** WARNING L2: REFERENCE MADE TO UNRESOLVED EXTERNAL    (使用了一个未定义的外部变量.(经常是在当前文件中声明了 extern xxx , 但其实在其它的文件中没有定义这个变量))
在文件中定义该外部变量即可。

16. #define和const定义的变量,有什么区别?

17. inline知道吗?说说它的作用。追问:inline在什么情况下都会展开吗?inline在什么时候展开、编译还是运行?

inline.内联函数.在iOS中的一些框架中,static inline是经常出现的关键字组合.

static inline CGFloat CGFloatFromPixel(CGFloat value) {

    return value / YYScreenScale();
}
但它在这里就是宏的作用,即你可以将CGFloatFromPixel当作一个宏.当然inline函数与宏有区别,inline可以:
解决函数调用效率的问题:
函数之间调用,是内存地址之间的调用,当函数调用完毕之后还会返回原来函数执行的地址。函数调用有时间开销,内联函数就是为了解决这一问题。
不用inline修饰的函数, 汇编时会出现 call 指令.调用call指令就是就需要:
(1)将下一条指令的所在地址入栈
(2)并将子程序的起始地址送入PC(于是CPU的下一条指令就会转去执行子程序).
优点相比于函数:
inline函数避免了普通函数的,在汇编时必须调用call的缺点:取消了函数的参数压栈,减少了调用的开销,提高效率.所以执行速度确比一般函数的执行速度要快.
2)集成了宏的优点,使用时直接用代码替换(像宏一样);
避免了宏的缺点:需要预编译.因为inline内联函数也是函数,不需要预编译.

18. iOS launchwith options 返回no有什么作用?

当return YES时执行,return NO时不执行

19. iOS反射机制?

通过类名的字符串形式实例化对象
Classclass=NSClassFromString(@"student");
Student*stu=[[classalloc]init];

将类名变为字符串
Class class=[Student class];
NSString*className=NSStringFromClass(class);

通过方法的字符串形式实例化方法
SELselector=NSSelectorFromString(@"setName");
[stu performSelector:selector withObject:@"Mike"];

20. iOS 中内省的几个方法?class方法和object_getClass、objc_getClass方法有什么区别?

对象在运行时获取其类型的能力称为内省。内省可以有多种方法实现
OC运行时内省的6个方法:

- (BOOL)isKindOfClass:(Class)aClass; //判断是否是这个类或者这个类的子类的实例

- (BOOL)isMemberOfClass:(Class)aClass; //判断是否是这个类的实例

- (BOOL)conformsToProtocol:(Protocol *)aProtocol;  //判断是否遵守某个协议

+ (BOOL)conformsToProtocol:(Protocol *)protocol; //判断某个类是否遵守某个协议

- (BOOL)respondsToSelector:(SEL)aSelector;  //判读实例是否有这样方法

+ (BOOL)instancesRespondToSelector:(SEL)aSelector; //判断类是否有这个方法

21. 面向对象语言的特点是啥?设计原则?

封装 继承 多态

单一职责原则,开闭原则,依赖倒置原则(面向接口编程),迪米特原则,里氏替换原则,接口隔离原则。

22. 什么是多态?

多态就是父类指针指向子类指针。父类的指针调用子类对象的方法,就会首先在子类里面查找,找到之后就打印出来,如果没有找到就会在父类里面查找,然后打印出来。

23. Objective-C 的动态性是怎么实现的?

Objective-C具有相当多的动态特性,经常被提到和用到的有动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading)

即运行时在决定对象的类型。例如:id类型,可以指向任意对象类型

基于动态类型,一个对象实例化之后,其类型就被确定了。该对象的属性和响应的函数也被确定下来,这就是动态绑定。

动态加载:根据需求动态地加载资源。对于iOS来说,基本上就是根据不同的机型做适配。

24. 刚刚说到循环强引用,可以具体说一下吗?

循环引用的实质:多个对象相互之间有强引用,不能施放让系统回收。

25. 实现description方法能取到什么效果?

description是nsobject的一个实例的方法,返回的是一个nsstring。当我们使用nslog打印的时候,打印出来的一般都是对象的内存地址,如果我们实现description方法时,我们就可以使用nslog打印对象的时候,我们可以把它里面的属性值和内存地址一起打印出来.打印什么,就是看你写什么了。

-(NSString *)description{ 
      NSString * string = [NSString stringWithFormat:@"<Person:内存地址:%p name = %@ age = %ld>",self,self.name,self.age]; 
      return string;     
}

26. 类簇?

先构建一个类的结构体系,然后用这个类指定一个对象来存储不同数据类型的变量(如:char,int,float,double) 
因为不同数据类型的变量在使用的时候可以互相转换类型或用字符串标识,所以我们可以用一个简单的类来管理它们。

Number是一个抽象的父类,它实现的方法主要是操作它的子类。Number类不会直接声明一个变量去存储不同类型的数据,而是由它的子类们去创建一个对象去存储然后将实现方法隐藏,将调用接口共享给抽象父类Number。

注:创建一个类的时候,我们应该遵守最基本的原则,即:通过所有的子类可以很简便的管理数据对象以及后期扩展方便。

使用了类簇以后,我们只能看到公开的类Number

使用上面的类方法创建对象,可以不用releas对象,因为上面的类方法本质是便利构造器,在创建对象的时候已经加入了autoreleas机制。当然许多类仍然保留了最基本的alloc和init来创建一个对象,以防止你需要管理他们的dealloc实现。(注:autoreleas在将来的某个时刻自动释放池清理的时候才释放。dealloc在RC=0的时候马上调用进行清理)。

27. strlen()和sizeof()作用于一个字符串时的区别?

 sizeof() 是运算符,在编译时期计算参数占用多少内存空间大小;

strlen() 是一个函数,在运行时计算字符串的长度;

strlen() 遇到“ \0 ” 计算结束,并且“ \0 ”不会包含在计算的大小内。

`sizeof`的结果等于对象或类型所占的内存字节数

28. << 和 >>的含义?(移位符号)

要是C语言里的就是移位符号:在c语言中所有的位运算都是指二进制数的位运算。即使输入的是十进制的数,在内存中也是存储为二进制形式。 “<<”用法:格式是:a<=0。功能:将整型数a按二进制位向左移动m位,高位移出后,低位补0。 “>>”用法:格式是:a>>m,a和m必须是整型表达式,要求m>=0。功能:将整型数a按二进制位向右移动m位,低位移出后,高位补0。

29. 如何创建一个字符串常量?在.h声明并且实现会有什么问题?

30. 介绍scheme原理;微信如何实现呼起第三方app?

31. 什么是延迟加载?

懒汉模式,只在用到的时候才去初始化。

也可以理解成延时加载。

我觉得最好也最简单的一个列子就是tableView中图片的加载显示了。

一个延时载,避免内存过高,一个异步加载,避免线程堵塞。

32. OC实例变量的修饰符? 及作用范围?

@puplic 作用范围最大,可以在任何地方被访问(外界即可访问,又可以继承)
1.可以在其他类中访问被@public修饰的成员变量
2.也可以在本类中访问被@public修饰的成员变量
3.可以在子类中访问父类中被@public修饰的成员变量

@private  作用范围只能在自身类(外界既不可访问,又不能继承)
1.不可以在其他类中访问被@private修饰的成员变量
2.也可以在本类中访问被@private修饰的成员变量
3.不可以在子类中访问父类中被@private修饰的成员变量

@protected 作用范围在自身类和子类,如果什么都不加修饰
1.不可以在其他类中访问被@protected修饰的成员变量
2.也可以在本类中访问被@protected修饰的成员变量
3.可以在子类中访问父类中被@protected修饰的成员变量

@package
介于public和private之间的,如果是在其他包中访问就是private,在当前代码中访问就是public.

33. 宏了解吗,讲一讲?写一个宏定义函数,实现返回三个数中最大的

#define MAX(a,b,c) (a>b?(a>c?a:c):(b>c?b:c))

34. iOS传值的几种方式?

属性传值 block传值 delegate传值  通知传值   单例传值 数据库传值 NSFileManage存储传值  

35. 在OC中如何判断俩个对象完全相同?==、 isEqualToString、isEqual区别?

判断两个对象是否完全相同 用==
==操作符比较的是指针值,也就是内存地址是否相同。
然而有的时候我们只是想比较指针所指向的内容,判断两个对象在类型和值上是否都一样,就需要通过isEqual:方法来比较。
而且,如果已知两个对象是字符串,最好通过isEqualToString:方法来比较。 对于数组和字典,也有isEqualToArray:方法和isEqualToDictionary:方法。

36. isEquel和hash的关系?

37. #include、#import、@class的区别?

在C 语言中, 我们使用 #include 来引入头文件,如果需要防止重复导入需要使用

在OC语言中, 我们使用#import来引入头文件,可以防止重复引入头文件,可以避免出现头文件递归引入的现象。

#import比起#include的好处就是不会引起交叉编译 #import 确定一个文件只能被导入一次,这使你在递归包含中不会出现问题 

使用@class仅仅是告诉编译器这是一个类,并不会因入该类的其他信息,而我们所关心的也仅此一点,而不需要知道该类的内部有哪些属性和方法,因此使用@class可以提升编译性能

38. nil、Nil、NULL和NSNULL的区别?

nil:OC中的对象的空指针, 把对象置空,置空后是一个空对象且完全从内存中释放
Nil: 用nil的地方均可用Nil替换,Nil表示置空一个类
NULL: 表示把一个指针置空。(空指针)
NSNULL: 把一个OC对象置空,但想保留其容器(大小)
此处说一下NSNull,在集合中不能nil值,因为NSArray和NSDictionary中nil有特殊的含义。但是有些时候,需要在集合中存放空值,比如个人信息中,只知道姓名,不知道电话号码,此时,有必要将电话号码设置为空,这时,就用到了NSNull。
NSNull中只有一个null方法 :[NSNull null]
可以给空指针发送消息,不会造成crash

39. 手写一个枚举?

typedef NS_ENUM(NSInteger, CYLSex) {
  CYLSexMan,
  CYLSexWoman
};

40. 谓词的认识?

NSPredicate类是用来定义逻辑条件约束的获取或内存中的过滤搜索

例子1:
NSArray*array=@[@1,@2,@3,@4,@5,@6,@7];

NSPredicate*predicate=[NSPredicate predicateWithFormat:@"self > 2 && self < 5"];

NSArray*filterArray=[array filteredArrayUsingPredicate:predicate];

//filterArray is @[@3,@4];
例子2:
NSNumber *number = @123;

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self = 123"];

BOOL result = [predicate evaluateWithObject:number];

例子三:
NSNumber *number = @123;

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"self between {123, 200}"];

BOOL result = [predicate evaluateWithObject:number];

41. -id、instancetype、NSObject *和id *的区别?

首先需要知道,在cocoa的开发环境里,NSObject是所有类的根类
id:

从定义来看,id就是一个isa指针,可以指向任何一个继承了Object(或者NSObject)类的对象

id可以简单理解为一个万能指针

id是动态数据类型,编译时编译器不会检查id对象的类型,只有在运行时动态检查后会报错。

id 可用于定义变量 定义方法返回值   定义方法参数。

instancetype:

instancetype意思为实例化,instancetype与和id一样,都可以指向一个继承了Object(或者NSObject)类的对象

区别在于:instancetype只能作为方法返回值,需要返回该方法所在的类的实例化对象,所以instancetype也被称为关联返回类型。

使用instancetype会在编译时进行类型检查,有利于开发者在编译阶段发现错误。

关联返回类型的方法:

以 alloc 或 new 开头的类方法

以 autorelease、init、retain 或 self 开头的实例方法。

NSObject*:

NSObject* 就是指向NSObject类型的指针了,可以指向任何一个继承了NSObject的对象,感觉和上面的id、instancetype也没有区别。

区别在于NSObject* 是静态数据类型,编译时会进行类型检查。

NSObject*的作用与id一致。

id<NSObject> *

id<NSObject> *就是指该对象的类型可以是任何一个NSObject或继承了NSObject的子类,但该对象必须要遵循<NSObject>协议(奇葩的命名:协议名与类名一样)。简单来说就是它不关心对象是什么类型,只要遵循<NSObject>协议即可。

在定义delegate时常用。

42. iOS应用导航模式有哪些?

导航模式:将信息以最优的方式组织起来展现给用户。

客户端常见模式:tab、抽屉、列表、平铺/轮播、宫格和悬浮icon等。

平铺模式,一般由scrollView和pageControl组合而成的展示方式。手机自带的天气比较典型。

标签模式,tabBar的展示方式,这个比较常见。

树状模式,tableView的多态展示方式,常见的9宫格、系统自带的邮箱等展现方式。

43. performSelector传三个参数?

[obj performSelector:@selector(play)withObject:@"李周"afterDelay:4.f];

performSelectorOnMainThread

44. 对泛型的理解?

45. NSHashTable与NSMapTable?

NSHashTable是NSSet的通用版本,对元素弱引用,可变类型;可以在访问成员时copy

NSMapTable是NSDictionary的通用版本,对元素弱引用,可变类型;可以在访问成员时copy

(注:NSHashTable与NSSet的区别:NSHashTable可以通过option设置元素弱引用/copyin,只有可变类型。但是添加对象的时候NSHashTable耗费时间是NSSet的两倍。

NSMapTable与NSDictionary的区别:同上)

46. 类方法和实例方法的区别?

47. 0到99这100个数,怎么随机取出不同的数?

48. oc一个空函数里面有参数吗? 他们存在栈还是寄存器?

49. For in循环内增加删除元素,引起的问题?

崩溃错误:<__NSArrayM: 0xb550c30> was mutated while being enumerated.’
当程序出现这个提示的时候,是因为用快速遍历forin遍历数组,又同时增或删这个数组里面的内容,导致数组count发生变化,猜测在forin快速遍历的内部有个内置的不会动态改变个数的计数器, 当你的数组做出增删后, 计数器并没有相应的增减, 这样就会导致继续通过计数器获取数组, 造成数组越界。

50. self和super区别?

self调用自己方法,super调用父类方法

self是类,super是预编译指令

【self class】和【super class】输出是一样的

1.当使用 self 调用方法时,会从当前类的方法列表中开始找,如果没有,就从父类中再找;而当使用 super 时,则从父类的方法列表中开始找,然后调用父类的这个方法。

2.当使用 self 调用时,会使用 objc_msgSend 函数: id objc_msgSend(id theReceiver, SEL theSelector, …)。第 一个参数是消息接收者,第二个参数是调用的具体类方法的 selector,后面是 selector 方法的可变参数。以 [self setName:] 为例,编译器会替换成调用 objc_msgSend 的函数调用,其中 theReceiver 是 self,theSelector 是 @selector(setName:),这个 selector 是从当前 self 的 class 的方法列表开始找的 setName,当找到后把对应的 selector 传递过去。

3.当使用 super 调用时,会使用 objc_msgSendSuper 函数:id objc_msgSendSuper(struct objc_super *super, SEL op, …)第一个参数是个objc_super的结构体,第二个参数还是类似上面的类方法的selector,

当编译器遇到 [super setName:] 时,开始做这几个事:
1)构 建 objc_super 的结构体,此时这个结构体的第一个成员变量 receiver 就是 子类,和 self 相同。而第二个成员变量 superClass 就是指父类
调用 objc_msgSendSuper 的方法,将这个结构体和 setName 的 sel 传递过去。
2)函数里面在做的事情类似这样:从 objc_super 结构体指向的 superClass 的方法列表开始找 setName 的 selector,找到后再以 objc_super->receiver 去调用这个 selector

51. 说说消息转发机制的优劣?

优点:
利用消息转发机制可以无代码侵入的实现多重代理,让不同对象可以同时代理同个回调,然后在各自负责的区域进行相应的处理,降低了代码的耦合程度。
使用 @synthesize 可以为 @property 自动生成 getter 和 setter 方法(现 Xcode 版本中,会自动生成),而 @dynamic 则是告诉编译器,不用生成 getter 和 setter 方法。当使用 @dynamic 时,我们可以使用消息转发机制,来动态添加 getter 和 setter 方法。当然你也用其他的方法来实现。

缺点:
Objective-C本身不支持多继承,这是因为消息机制名称查找发生在运行时而非编译时,很难解决多个基类可能导致的二义性问题,但是可以通过消息转发机制在内部创建多个功能的对象,把不能实现的功能给转发到其他对象上去,这样就做出来一种多继承的假象。转发和继承相似,可用于为OC编程添加一些多继承的效果,一个对象把消息转发出去,就好像他把另一个对象中放法接过来或者“继承”一样。消息转发弥补了objc不支持多继承的性质,也避免了因为多继承导致单个类变得臃肿复杂。

本文标题为:2021年iOS高级工程师最新面试题大全_基础知识