Swift初体验

iOS面试题汇总

2018-02-05  本文已影响105人  coderjon

1.weak和assign区别

修饰变量类型的区别:

weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错-“Property with ‘weak’ attribute must be of object type”。

assign 可修饰对象和基本数据类型。当需要修饰对象类型时,MRC时代使用unsafe_unretained。当然,unsafe_unretained也可能产生野指针,所以它名字是"unsafe_”。

是否产生野指针的区别:

weak 不会产生野指针问题。因为weak修饰的对象释放后(引用计数器值为0),指针会自动被置nil,之后再向该对象发消息也不会崩溃。 weak是安全的。

assign 如果修饰对象,会产生野指针问题;如果修饰基本数据类型则是安全的。修饰的对象释放后,指针不会自动被置空,此时向对象发消息会崩溃。

总结:

assign 适用于基本数据类型如int,float,struct等值类型,不适用于引用类型。因为值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理。

weak 适用于delegate和block等引用类型,不会导致野指针问题,也不会循环引用,非常安全。

值类型会被放入栈中,遵循先进后出原则,由系统负责管理栈内存。而引用类型会被放入堆中,需要我们自己手动管理内存或通过ARC管理。

2.对象的生命周期

在对象的创建和初始化之后,只要对象的retainCount的值比0大,那么它就会一直存在在内存中。通过向一个对象发送retain消息,或者进行copy操作。其他的对象可以引用并持有该对象的所有权。同时,移除引用的时候要发送release消息。


object_lifecycle@2x.png

3.对象是如何初始化的

整个对象的初始化过程其实只是为一个分配内存空间,并且初始化 isa_t 结构体的过程

alloc的实现:

直接调用了另一个私有方法 id _objc_rootAlloc(Class cls)。id _objc_rootAlloc(Class cls)调用了callAlloc。

init方法:

init 方法只是调用了 _objc_rootInit 并返回了当前对象。

4.一个objc对象的isa指针指向什么?有什么作用

isa_@2x.png

对象的isa指向类,类的isa指向元类(meta class),元类isa指向元类的根类。

isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,他指向对象的类,而Class里也有个isa的指针, 指向meteClass(元类)。元类保存了类方法的列表。当类方法被调用时,先会从本身查找类方法的实现,如果没有,元类会向他父类查找该方法。同时注意的是:元类(meteClass)也是类,它也是对象。元类也有isa指针,它的isa指针最终指向的是一个根元类(root meteClass).根元类的isa指针指向本身,这样形成了一个封闭的内循环。

5.runtime怎么添加属性、方法等

6.runtime 如何实现 weak 属性

weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似;然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

那么runtime如何实现weak变量的自动置nil

runtime对注册的类会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。

weak属性需要在dealloc中置nil么

在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理
即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil
在属性所指的对象遭到摧毁时,属性值也会清空。

7.runtime如何通过selector找到对应的IMP地址?(分别考虑类方法和实例方法)

8.使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?

无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被 NSObject -dealloc 调用的object_dispose()方法中释放。

补充:对象内存销毁时间表,分四个步骤

9._objc_msgForward函数是做什么的?直接调用它将会发生什么?

_objc_msgForward是IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。直接调用_objc_msgForward是非常危险的事,这是把双刃刀,如果用不好会直接导致程序Crash,但是如果用得好,能做很多非常酷的事。

JSPatch就是直接调用_objc_msgForward来实现其核心功能的。

10.能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?为什么?

分析:

11.简述下Objective-C中调用方法的过程(runtime)

12.什么是method swizzling(俗称黑魔法)

13.对象如何找到对应的方法去调用

14.内存的分区

memory_2@2x.png

15.runtime消息转发机制

对象在收到无法解读的消息后, 首先调用其所属类的这个类方法 :
+ (BOOL)resolveInstanceMethod:(SEL)selector
selector : 那个未知的选择子

返回YES则结束消息转发

返回NO则进入备胎

假如尚未实现的方法不是实例方法而是类方法, 则会调用另一个方法resolveClassMethod:

动态方法解析失败, 则调用这个方法
- (id)forwardingTargetForSelector:(SEL)selector
selector : 那个未知的消息
返回一个能响应该未知选择子的备胎对象

备胎搞不定, 这个方法就准备要被包装成一个NSInvocation对象, 在这里要先返回一个方法签名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

NSMethodSignature : 该selector对应的方法签名

给接收者最后一次机会把这个方法处理了, 搞不定就直接程序崩溃

- (void)forwardInvocation:(NSInvocation *)invocation
invocation : 封装了与那条尚未处理的消息相关的所有细节的对象

在这里能做的比较现实的事就是 : 在触发消息前, 先以某种方式改变消息内容, 比如追加另外一个参数, 或是改变消息等等. 实现此方法时, 如果发现某调用操作不应该由本类处理, 可以调用超类的同名方法. 则继承体系中的每个类都有机会处理该请求, 直到NSObject. 如果NSObject搞不定, 则还会调用doesNotRecognizeSelector:来抛出异常, 此时你就会在控制台看到那熟悉的unrecognized selector sent to instance..

16.iOS中__block 关键字的底层实现原理

Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值。

把三个16进制的内存地址转成10进制就是:

定义后前:6171559672

block内部:5732708296

定义后后:5732708296

中间相差438851376个字节,也就是 418.5M 的空间,因为堆地址要小于栈地址,又因为iOS中一个进程的栈区内存只有1M,Mac也只有8M,显然a已经是在堆区了。

这也证实了:a 在定义前是栈区,但只要进入了 block 区域,就变成了堆区。这才是 __block 关键字的真正作用。

17.HTTP协议的8种请求类型

18.列举几种容易造成tableview卡顿的原因,并简单描述解决办法

19.时间复杂度

20.iOS开发中数据持久化有哪几种

21.继承之后的类,子类里面[self class]、[self superClass]、[super class]

22.通知是否可以在子线程中执行

苹果采取通知中心在同一线程中post和转发同一消息策略。我们可以再多线程环境下使用同一个NSNOtificationCenter对象而不需要加锁。但是delloc方法和-postNotificationName:方法不在同一线程中运行时,会出现BAD_ACCESS。例如:sleep(1).

23.串行、并发和同步、异步

串行:每次只有一个任务被执行
并发:同一时间可以有多个任务被执行
同步:只有在完成了塔预定的任务之后才返回
异步:立即返回

24.线程间通信

NSThread:performselector

GCD:dispatch_async(dispatch_get_main_queue(),^{
})

NSOperation:[NSOperaionQueue mainqueue]

25.实现线程同步的方法

串行队列,分组,信号量

26.如何使用队列来避免资源抢夺

线程锁,也可以使用串行队列

27.NSCache优于NSDictionary的几点

28.block底层实现

block其实是一个指针结构体
几个重要的结构体:

__block_impl:这是一个结构体,可以理解为block的基类
__main_block_impl_0:可以理解为block变量
__main_block_func_0:可以理解为匿名函数
__main_block_desc_0:block的描述

29.objc在向一个对象发送消息时,发生了什么

根据对象的isa指针找到对象的method_lists列表,如果没有找到,再沿着父类找method_lists,最终找到,确认IMP,发送消息

30.runtime如何实现weak变量的自动置nil

runtime对注册的类会进行布局,对于weak对象会放到一个hash表中,用weak指向的对象内存地址作为key,当此对象的引用计数为0的还是会调用dealloc,从而置nil

31.给类添加一个属性后,在类结构体里哪些元素会发生变化

intance_size:实例内存大小
objc_ivar_list *ivars:属性列表

32.runloop是来做什么的?runloop和线程有什么关系?主线程默认开启了runloop么?子线程呢?

runloop:循环跑圈,用来处理线程里面的事情和消息

runloop和线程的关系:每个线程如果想继续运行,不被释放,就必须有一个runloop来不停的跑圈,来处理线程里面的各个事件和消息

主线程默认开启了一个runloop,子线程默认没有开始runloop

33.苹果是如何实现Autuorelease Pool的

Autorelease Pool:缓存池。可以延迟release,将创建的对象,添加到最近的autoreleasepool中,等到autoreleasePool作用域结束的时候,会将里面所有的对象引用计数-1

34.isa指针

对象的isa指针指向所属类
类的isa指针指向元类
元类额isa指针指向了根类

35.类方法和实例方法的区别

调用方式:类方法必须使用类调用,不可以使用属性,方法存储在元类结构体的method_lists。实例方法使用实例对象调用,可以在实例方法里面使用属性,存储在类的method_lists

36.objc向一个nil对象发送消息会发生什么

37.frame和bounds有什么不同

frame指的是:该view在父view坐标系统中的位置和大小
bounds指的是:该view在本坐标系中的位置和大小

38.oc可以多继承么?可以实现多个接口么?category是什么?重写一个类的继承方式好还是分类好。为什么

oc不可以多继承;可以实现多个接口,category是类别;用分类号,仅对本category有效,不会影响到其他类和原有的类的关系;

39.@property的本质是什么?ivar、getter、setter是如何生成并添加到这个类中的

@property:ivar + setter + getter

属性的两大概念:ivar(实例变量) + getter、setter(存取方法)

属性作为oc的一项特性,主要的作用就是在于封装对象中的数据,oc对象通常会把其所需要的数据保存为各种实例变量,实例变量一般通过存取方法来访问,getter读取变量值,setter写入变量值

40.weak和assign区别

assign:可以用非oc对象,基本数据类型。而weak必须用于oc对象
weak:表明该属性定义了一种”非拥有关系“,当属性所指的对象销毁时,属性值会自动清空(nil)

41.@property(nonatomic,retain)NSString *name和@property(nonatomic,copy)NSString *name的setter

``

``- (void)setName:(NSString *)name{

if(\_name != name){
    [\_name release];
    \_name = [name copy];
}

}``

42.@synthesie和@dynamic分别有什么作用

43.id声明的对象有什么特性

id声明的对象具有运行时特征,可以指向任意类型的oc对象。

44.category(类别)、extension(扩展)和继承的区别

45.我们说的oc是动态运行时语言是什么意思

主要是将数据类型的确定由编译时,推迟到了运行时。简单来说,运行时机制使我们直到运行时才去决定一个对象的类别以及调用该类别对象指定方法。

46.为什么我们常见的delegate属性都用的是weak而不是retain/strong

为了避免delegate两端产生不必要的循环引用

47.什么时候用delgate,什么时候用notification

delegate:1对1反向传值;
notification:只想要把消息发送出去,告知状态变化,并不关心谁想要知道

48.kvc底层实现

当一个对象调用setvalue方法时,方法内部会做一下操作:

49.kvo底层实现

50.ViewController生命周期

51.方法和选择器有何不同

selector是一个方法的名字,方法是名字和实现的组合体

52.你是否接触过OC中的反射机制

53.类变量的@public,@protected,@private,@package声明各有什么含义

@public:任何地方都能访问
@protected:该类和子类中访问,是默认的
@private:只能在本类中访问
@package:本包内使用,跨包不可以

54.什么是谓词

谓词是通过NSPredicate给定的逻辑条件作为约束条件,完成对数据的筛选
定义谓词对象,谓词对象中包含了了过滤条件

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<%d",30];

使用谓词条件过滤数组中的元素,过滤之后返回查询结果

NSArray *array = [persons filteredArrayUsingPredicate:predicate];

55.如何访问并修改一个类的私有属性

56.isKindofClass、isMemberClass、selector作用分别是什么

57.常用的instruments

58.runtime实现的机制是什么,怎么用,一般用于干嘛

59.cocoapods原理

执行pod命令之后,项目中会生成:ProjectName.xcworkspace、Podfile.lock、Pods等文件;
CocoaPods的工作主要是通过ProjectName.xcworkspace来组织的,在打开ProjectName.xcworkspace文件后,发现Xcode会多出一个Pods工程。

库文件的引入主要由Pods工程中的Pods-ProjectName-frameworks.sh脚本负责,在每次编译的时候,该脚本会帮你把预引入的所有三方库文件打包的成ProjectName.a静态库文件,放在我们原Xcode工程中Framework文件夹下,供工程使用。
如果Podfile使用了use_frameworks!,这是生成的是.framework的动态库文件。引入方式也略有不同。

Resource资源文件主要由Pods工程中的Pods-ProjectName-resources.sh脚本负责,在每次编译的时候,该脚本会帮你将所有三方库的Resource文件copy到目标目录中。

在Pods工程中的的每个库文件都有一个相应的SDKName.xcconfig,在编译时,CocoaPods就是通过这些文件来设置所有的依赖参数的,编译后,在主工程的Pods文件夹下会生成两个配置文件,Pods-ProjectName.debug.xcconfig、Pods-ProjectName.release.xcconfig。

Target -> Build Settings ,User Header Search Paths条目中,添加${SRCROOT}或者$(PODS_ROOT),并且选择Recursive,递归搜索,然后就可以自动补齐了。

60.framework制作

将公共的拖到Public下

61.objc_msgForward函数是做什么的,直接调用它将会发生什么

objc_msgForward是IMP类型,用于消息转发。当对一个对象发送一条消息,但是它并没有实现的时候,就会调用这个方法

62.什么是TCP/UDP

TCP:传输控制协议
UDP:用户数据协议

TCP是面向连接的,建立连接需要经历三次握手,是可靠的传输层协议
UDP是面向无连接的,数据传输是不可靠的,它只管发,不管是否能收到

首先Client端发送连接请求报文,Server段接受连接后回复ACK报文,并为这次连接分配资源。Client端接收到ACK报文后也向Server段发生ACK报文,并分配资源,这样TCP连接就建立了。

tcp_connect@2x.png

假设Client端发起中断连接请求,也就是发送FIN报文。Server端接到FIN报文后,意思是说我Client端没有数据要发给你了,但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息。这个时候Client端就进入FIN_WAIT状态,继续等待Server端的FIN报文。当Server端确定数据已发送完成,则向Client端发送FIN报文,告诉Client端,好了,我这边数据发完了,准备好关闭连接了。Client端收到FIN报文后,就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。,Server端收到ACK后,就知道可以断开连接了。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。Ok,TCP连接就这样关闭了!

tcp_break_client@2x.png
tcp_break_server@2x.png

63.通信底层实现原理(OSI七层模型)

物理层、数据链路层、网络层、传输层、回话层、表示层、应用层

64.XMPP

xmpp是一种以XML为基础的开放式实时通信协议

65.OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么

// 创建线程的方法

- [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]
- [self performSelectorInBackground:nil withObject:nil];
- [[NSThread alloc] initWithTarget:nil selector:nil object:nil];
- dispatch_async(dispatch_get_global_queue(0, 0), ^{});
- [[NSOperationQueue new] addOperation:nil];

// 主线程中执行代码的方法
- [self performSelectorOnMainThread:nil withObject:nil waitUntilDone:YES];
- dispatch_async(dispatch_get_main_queue(), ^{});
- [[NSOperationQueue mainQueue] addOperation:nil];

66.tableview的重用机制

UITableView通过重用单元格来达到节省内存的目的:通过为每个单元格指定一个重用标识符,即指定了单元格的种类,当屏幕上的单元格滑出屏幕时,系统会把这个单元格添加到重用队列,等待被重用。当有新单元格从屏幕外滑入屏幕内时,从重用队列中找看有没有可以重用的单元格,如果有,就拿过来用,如果没有就创建一个来使用

67.用伪代码写一个线程安全的单例模式

static id _instance

+ (instancetype)shareData{

return [[self alloc] init];

}

+ (instancetype)alloceWithZone:(struct _NSZone *)zone{

static dispatch_once_t oncetoken;
dispatch_once(&oncetoken,^{
    \_instace = [super allocWithZone: zone];
});
return \_instance;

}

- (id)copyWithZone:(NSZone *)zone{

return _instance;

}

68.HTTP协议中POST和GET方法有哪些区别

69.请简单介绍下APNS发送系统消息的机制

70.不用中间变量,用两种方法交换A和B的值

中间变量

void swap(int a, int b) {

int temp = a;
a = b;
b = temp;

}

加法

void swap(int a, int b) {

a = a + b;
b = a - b;
a = a - b;

}

异或

void swap(int a, int b) {

a = a ^ b;
b = a ^ b;
a = a ^ b;

}

71.实现响应链传递的两种方式?在那些地方使用过

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event;
``

使用:

responder_1@2x.png responder_2@2x.png

72.block底层实现

能够捕获它所在函数内部的变量的函数指针、匿名函数或者闭包
void (*test)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
那么这整句就是说
定义一个函数指针指向一个新创建的__main_block_impl_0实例的地址。注意创建这个实例时构选函数传的两个参数, 正是编译器帮我们生成的静态函数__main_block_func_0及__main_block_desc_0的变量__main_block_desc_0_DATA

73.armv7,armv7s,arm64,i386,x86_64

iOS测试分为模拟器测试和真机测试,处理器分为32位处理器,和64位处理器。

模拟器32位处理器测试需要i386架构。(iphone5及其以下)
模拟器64位处理器测试需要x86_63架构。(iphone5s及其以上)

真机32位处理器需要armv7或者armv7s构架。(iphone4用armv7。iphone5,iphone5s用armv7s)
真机64位处理器需要arm64架构。(iphone6以上)

74.Xcode中将图片放入images.xcassets和直接拖入的区别

75.iOS获取设备的唯一标识的方法总结

76.tcp/ip协议层次结构

五层:应用层、传输层、网络层、数据链路层、物理层
五层:应用层、传输层、网络层、网络接口层

77.响应链

78.算法

快速排序

上一篇 下一篇

猜你喜欢

热点阅读