iOS面试总结

2017年iOS面试总结

1、为什么说Objective-C是一门动态语言?

答:Objective-C类的类型和数据变量的类型都是在运行时确定的,而不是在编译时确定。例如多态性,我们可以使用父类对象来指向子类对象,并且可以用来调用子类的方法。运行时Runtime特性,可以动态的添加方法或者替换方法。

2、讲一下MVC和MVVM,MVP?

答:MVC:简单说逻辑、视图、数据进行分层,实现解耦。

MVVM:由模型、视图、视图模型三部分组成,比MVC更加释放控制器的臃肿,将一部分的逻辑(耗时,公共方法,网络请求)和数据的处理操作从控制器搬到了ViewModel中了。

MVVM的特点:

1️⃣低耦合:View可以独立于Model的变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变化。

2️⃣可重用性:可以把一些视图的逻辑放在ViewModel里面,让很多View重用这段视图逻辑。

3️⃣独立开发:开发人员可以专注与业务逻辑和数据的开发ViewModel,设计人员专注于界面View的设计。

4️⃣可测试性:可以针对ViewModel来对界面View进行测试。

3、为什么代理的关键字要用weak?代理的delegatedataSource有什么区别?block和代理的区别?

答:代理是使用weak来修饰,使用weak是为了避免循环使用,当使用weak修饰的属性,当对象释放的时候,系统会对属性赋值nilObjective-C有个特性就是对nil对象发送消息也就是调用方法不会发生Crash

delegate:表示代理,代理可以让A对象通知B对象,A发生了改变,B遵循了A的代理并且实现了A的代理方法。

dataSource:表示数据源,如果A对象声明了数据源,那么创建A对象的时候,就该实现数据源来告诉A。

代理和Block的区别:

相同点:代理和Block大多是我们都可以用来倒序传值的,都需要注意避免循环使用。

不同点:使用copy关键字修饰,block保存的是一段代码,其实也就是一个函数,当我们调用block的时候要判断是否已经实现。

4、属性的本质是什么?包括哪几个部分?属性默认的关键字有哪些?@dynamic和@synthesize关键字是用来做什么的?

答:属性是描述类的特征,也就是具备什么特性。属性包括3个部分,带下划线的成员变量,gettersetter方法。

默认关键字:readwrite,assign,atomic,是针对基本类型(NSInteger,BOOL,NSUInteger,int等)
针对引用类型,默认有readwrite,strong,atomic(NSString,NSArray,NSDictonary)

@synthesize:是一种编译器功能,会让编译器自动生成gettersetter方法,可以指定与属性相对应的成员变量。

@dynamic:告诉编译器自动生成的是无效的,必须是自己生成的gettersetter方法。

5、属性的默认属性关键字是什么?

答:默认关键字是readwrite,assign,atomic

6、NSString为什么要用copy关键字,如果用strong会有什么问题?

答:可变类型(NSMutableArray,NSMutableString)是不可变类型(NSString,NSArray)的子类,因为多态的原因,我们可以使用赋值指向子类对象,也就是可以使用不可变类型去接受可变类型。

1️⃣当我们使用strong修饰A不可变类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值也会发生变化。strong只是让创建的对象引用计数+1,并返回当前对象的内容地址。当我们修改B指向的内容的时候,A指向的内容也同样发生了改变。因为它们指向的内存地址是相同的,是一份内容。

2️⃣当我们使用copy修饰A不可变类型的时候,并且使用B可变类型给A赋值,再去修改可变类型B值的时候,A所指向的值不会发生变化。因为当时用copy修饰A的时候就会拷贝一份内容出来,并且返回指针给A,当我们修改B指向的内容时,A指向的内容是没有发生改变的。因为A指向的内存地址和B指向的内存地址是不同的,是两份内容。

3️⃣copy修饰不可变类型(NSString,NSArray)的时候,使用不可变类型进行赋值,表示浅拷贝,只拷贝一份指针和strong修饰一样,当修饰的是可变类型(NSMutableArray,NSMutableString)的时候,使用可变类型进行赋值,表示深拷贝,直接拷贝新一份内容到内存中,表示两份内容。

7、如何令自己所写的对象具有拷贝功能?

答:必须遵循NSCopying协议,如果想实现可变和不可变拷贝,必须同时遵循NSCopyingNSMutableCoping协议。并且实现方法-(id)copyWithZone:(NSZone *)zone;

8、可变集合类和不可变集合类的copymutableCopy有什么区别?如果是集合内容复制,集合里面的元素也是内容复制吗?

答:可变集合类使用copy表示深拷贝,不可变集合类使用copy的时候是浅拷贝。
可变集合类使用mutableCopy表示深拷贝,不可变集合类使用copy的时候是浅拷贝。
关于容器实现copymutableCopy,容器内元素默认都是指针拷贝即浅拷贝,不是内容复制。

9、为什么IBOutlet修饰的UIView也适用weak关键字?

答:在xibSB拖控件的时候,其实控件就加载到了父控件的Subviews数组里面去了,进行了强引用,即使使用了weak也不造成对象的释放。

10、nonatomic 和 atomic 的区别?atomic是绝对的线程安全吗?为什么?如果不是,那应该如何实现?

答:nonatomic:表示非原子性,不安全,但是效率高。
atomic:表示原子性,安全,但是效率低。
atomic不能绝对保证线程的安全,当多线程同时访问的时候,会造成线程不安全。可以使用线程锁来保证线程的安全。

11、UICollectionView自定义layout如何实现?

-(CGSize)collectionViewContentSize
返回collectionView的内容的尺寸

-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
返回rect中的所有的元素的布局属性
返回的是包含UICollectionViewLayoutAttributes的NSArray

UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视    图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:layoutAttributesForCellWithIndexPath:layoutAttributesForSupplementaryViewOfKind:withIndexPath:layoutAttributesForDecorationViewOfKind:withIndexPath:


-(UICollectionViewLayoutAttributes )layoutAttributesForItemAtIndexPath:(NSIndexPath )indexPath
返回对应于indexPath的位置的cell的布局属性

-(UICollectionViewLayoutAttributes )layoutAttributesForSupplementaryViewOfKind:(NSString )kind atIndexPath:(NSIndexPath *)indexPath
返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载

-(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString)decorationViewKind atIndexPath:(NSIndexPath )indexPath
返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载

-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。

12、用StoryBoard开发界面有什么弊端?如何避免?

答:使用简单逻辑页面的跳转是可以使用SB的,开发起来比较快。但是SB对于逻辑复杂的项目,开发起来比较慢。不适合多人协同开发,也不利于版本的后期维护。使用SB在项目编译的时候,也都会加载到内存中,造成内存的浪费。
可以使用xib来代替,编辑复杂的逻辑界面的时候可以使用纯代码编写。

13、进程和线程的区?同步和异步的区别?并行和并发的区别?

答:进程: 是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

线程: 是进程的一个实体,是CPU调度和分配的基本单位它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个进程的其他进程共享进程所拥有的全部资源。

同步: 阻塞当前线程操作不能开辟线程。

异步: 不阻碍线程继续操作,可以开辟线程来执行任务。

并发: 当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其他线程处于挂起状态。

并行: 当系统有一个以上的CPU时,线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行。

区别:并行是指两个或者多个事件在同一时刻发生,而并发是指两个或者多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却只能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可以被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行了。

14、线程间怎么通信?

答:当使用dispatch_async函数开辟线程执行任务完成时,需要使用dispatch_async(dispatch_get_main_queue(),^{});函数回到主线程

15、GCD中一些常用的函数?(group,barrier,信号量,线程同步)

我们使用队列组来开辟线程时,队列组中的队列任务是并发,当所有的队列组中的任务完成的时候,才可以调用队列组完成任务。

/** 创建自己的队列 */
dispatch_queue_t queue = dispatch_queue_create("wy", DISPATCH_QUEUE_CONCURRENT);
/** 创建一个队列组*/
dispatch_group_t group = dispatch_group_create();
/** 将队列任务添加到队列组中*/
dispatch_group_async(group, queue, ^(){
    NSLog(@"dispatch-1");
});
dispatch_group_async(group, queue, ^(){
    NSLog(@"dispatch-2");
});
dispatch_group_async(group, queue, ^(){
    NSLog(@"dispatch-3");
});
/** 队列组完成调用函数*/
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
})

barrier:表示栅栏,当在并发队列里面使用栅栏时候,栅栏之前的并发任务开始并发执行,执行完毕后,执行栅栏内的任务,等栅栏任务执行完毕后,再并发执行栅栏后的任务。

    dispatch_queue_t queue = dispatch_queue_create("wy", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^(){
    NSLog(@"dispatch-1");
    });
    dispatch_group_async(group, queue, ^(){
    NSLog(@"dispatch-2");
});
dispatch_barrier_async(queue, ^(){
    NSLog(@"dispatch-barrier");
})
    dispatch_group_async(group, queue, ^(){
    NSLog(@"dispatch-3");
});
dispatch_group_async(group, queue, ^(){
    NSLog(@"dispatch-4");
});

信号量:Semaphore 是通过“计数”的方式来标识线程是否等待还是继续执行的。

dispatch_semaphore_create(int);//创建一个信号,并初始化信号的计数大小
/* 等待信号,并且判断信号量,如果信号量计数大于等于你创建时候的信号量的计数,就可以通过,继续执行,并且将你传入的信号计数减1,
  • 如果传入的信号计数小于你创建的计数,就表示等待,等待信号计数的变化

  • 如果等待的时间超过你传入的时间,也会继续下面操作

  • 第一个参数:semaphore 表示信号量

  • 第二个参数:表示等待的时间

  • 返回int 如果传入的信号计数大于等于你创建信号的计数时候,返回0. 反之,返回的不等于0
    */

    int result = dispatch_semaphore_wait(dispatch_semaphore_t semaphore,time outTime);//表示等待也是阻碍线程
    //表示将信号计数+1
    dispatch_semaphore_signal(dispatch_semaphore_t semaphore);

实现线程的同步的方法:串行队列、分组、信号量,也可以使用并发队列。

//加入队列
dispatch_async(queue, ^{
    //1.先去下载图片
    dispatch_sync(queue, ^{
    
    });
    //2.在主线程中展示UI
    dispatch_sync(dispatch_get_main_queue(), ^{
    
    });
    })

16、如何使用队列来避免资源抢夺?

答:当我们使用多线程来访问同一个数据的时候,就有可能造成数据的不准确性。这个时候我们可以使用线程锁来绑定,也是可以使用串行队列来完成。

17、数据持久化的几个方案

属性列表 plist
文件归档 NSKeyedArchiver
数据库 SPLite
CoreData

18、说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?

答:

19、NSCache优于NSDictionary的几点?

答:

NSCache 是可以自动释放内存的

NSCache 是线程安全的,我们可以在不同的线程中添加,删除和查询缓存中的对象。

一个缓存对象不会拷贝key对象。

20、什么是Designated Initializer?使用它的时候有什么需要注意的问题?

答:初始化函数,如果想自定义初始化函数时,也是必须要初始化父类,以来保证可以继承父类的一些方法或者属性。

21、实现description方法能取得什么效果?

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

22、objc使用什么机制管理对象内存?

答:使用内存管理计数器来管理内存,当内存管理计数器为0的时候,对象就会被释放。

中级

1.block的实质是什么?一共有几种block?都是什么情况下生成的?

答:
block:本质就是一个Objective-C对象

blcok:存储位置可能分为3个地方:代码区、堆区和栈区,在ARC下会自动拷贝到堆区,因此在ARC下只能有两个可能的地方:代码区和堆区。

代码区:不访问栈区的变量(如局部变量)并且不访问堆区的变量(alloc创建的对象),此时block存放在代码区

堆区:访问了处于栈区的变量或者堆区的变量,此时block存放在堆区。需要注意实际是放在栈区,在ARC情况下会自动拷贝到堆区,如果不是ARC则存放在栈区,所在函数执行完毕就会释放,在外面调用需要用copy指向它,这样就拷贝到堆区,strong不会拷贝会造成野指针错区。

2、为什么在默认情况下无法修改被block捕获的变量?__block都做了什么?

答:默认情况下,block里面的变量,拷贝进去的是变量的值,
而不是指向变量的内存的指针。
当使用__block修饰后的变量,拷贝到block里面就是指向变量的指针,所以我们可以修改变量的值。

Runtime

1、objc在向一个对象发送消息时,发生了什么?

答:根据对象的isa指针找到类对象id,在查询类对象里面methodLists方法函数列表,如果没有找到,再沿着superClass寻找父类,再在父类methodLists方法列表里面查询,最终找到SEL,根据idSEL确认IMP(指针函数),在发送消息。

2、什么时候会报unrecognized selector错误?iOS有哪些机制来避免走到这一步?

答:在发送消息的时候,我们会根据类里面的methodLists列表去查询我们要动用的SEL,当查询不到的时候,我们会一直沿着父类查询,当最终查询不到的时候就会报unrecognized selector错误。

当系统查询不到方法的时候,会调用+(BOOL)resolveInstanceMethod:(SEL)sel 动态解释的方法来给我们一次机会来添加,调用不到的方法。或者我们可以再次使用 -(id)forwardingTargetForSelector:(SEL)aSelector 重定向的方法来告诉系统该调用什么方法来保证不会崩溃。

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

答:不能向编译后得到的类中增加实例变量,能够向运行时创建的类中添加实例变量。

解释:

1️⃣ 编译后的类已经注册在runtime中,类结构体中objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,runtime会调用class_setvarlayout或者class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量。

2️⃣ 运行时创建的类是可以添加实例变量,调用class_addIvar函数,但是要在调用objc_allocateClassPair之后。

4、runtime如何实现weak变量的自动置为nil?

答:runtime对注册的类会进行布局,对于weak对象会放入一个hash表中。用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc,假如weak指向的对象内存地址是a,那么就会以akey,在这个weak表中搜索,找到所有以akeyweak对象然后设置为nil.

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

答:instancece_size:实例的内存大小

objc_ivar_list:属性列表

RunLoop

1、runloop 是来做什么?runloop 和线程有什么关系?主线程默认开启了runloop 吗?子线程呢?

答:runloop:字面意思就是跑圈,其实就是一个循环跑圈,用来处理线程里面的事件和消息。

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

主线程默认是开启一个runloop,也就是这个runloop才能保证我们程序正常的运行,子线程是默认没有开始runloop的。

2、runloopmode 是用来做什么的?有几种 mode ?

答:model:是runloop里面的模式,不同的模式下runloop处理的事件和消息是有一定的差别的。

系统默认注册了5个mode:

kCFRunLoopDefaultMode:App的默认mode,通常主线程是在这个mode下运行的

UITrackingRunLoopMode:界面跟踪mode,用于scrollview追踪触摸滑动,保证界面滑动时不受其他mode影响。

UIInitializationRunLoopMode:在刚启动APP时进入的第一个mode,启动完成后就不再使用了。

GSEventReceiveRunLoopMode:接受系统事件的内部mode,通常用不到。

kCFRunLoopCommonModes: 这是一个占位的mode,没有实际作用。

注意:iOS对以上5种mode进行了封装,NSDefaualtRunLoop、NSRunLoopCommonModes

3、为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?

答:NSTimer对象是在 NSDefaultRunLoopMode 下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode 模式下面,却不可以继续响应NSTimer发送的消息了。所以想在scrollview的情况下还调用NSTimer的消息,我们可以把NSRunLoop的模式改为NSRunLoopCommonModes.

4、苹果是如何实现Autorelease Pool?

答:Autorelease Pool 作用:缓存池,可以避免我们经常写 release 的一种方式,其实是 延迟release ,将创建的对象添加到最近的autoreleasePool 中,等到autoreleasePool作用域结束的时候会将里面所有的对象的引用计数-1.

类结构

1、isa指针?(对象的isa,类对象的isa,元类的isa)

答:

在OC中类也是对象,所属元类,所以经常说 万事万物皆对象。

对象的isa指针指向所属的类。

类的isa指针指向所属的元类。

元类的isa指针指向了根元类,根元类指向了自己。

2、类方法和实例方法有什么区别?

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

3、介绍一下分类,能用分类做什么?内部是如何实现的?它为什么会覆盖掉原来的方法?

答:category:我们可以给类或者系统类添加实例方法,我们添加的实例方法会被动态的添加到类结构里面的 methodList 列表里面。category的方法并没有“完全覆盖掉”原来类已有的方法,而是category方法放到了新方法列表的前面,原来方法放到了category的后面去了。这也就是我们平常所说的category的方法会“覆盖”掉原来类的同名方法,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应名字的方法,就会罢休,殊不知后面可能还有一样名字的方法。

4、运行时能增加成员变量吗?能增加的如何增加?如果不能,为什么?

答:可以增加成员变量,但是必须实现它的gettersetter 方法,但是没有带下划线同名的成员变量。但是我们使用runtime我们就可以增加成员变量。

- (void)setName:(NSString *)name {
/**
*  为某个类关联某个对象
*
*  @param object#> 要关联的对象 description#>
*  @param key#>    要关联的属性key description#>
*  @param value#>  你要关联的属性 description#>
*  @param policy#> 添加的成员变量的修饰符 description#>
*/

objc_setAssociatedObject(self, @selector(name), name,   OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
/**
*  获取到某个类的某个关联对象
*
*  @param object#> 关联的对象 description#>
*  @param key#>    属性的key值 description#>
*/
return objc_getAssociatedObject(self, @selector(name));

}

5、objc 中向一个nil对象发送消息将会发生什么?(返回值是对象,是标量,结构体)

答:如果一个方法返回值是一个对象,那么发送给nil的消息将返回0 或者nil.

如果方法返回值为指针类型,其指针大小为小于或者等于sizeof(void*),float,double,long double或者long long的整形标量,发送给nil的消息将返回0。

如果方法返回值为结构体,发送给nil的消息将返回0.结构体中各个字段的值将都是0,其它的结构体数据类型将不是用0填充。

高级

1、UITableView的优化方法(缓存方法,异步绘制,减少层级,hide,避免离屏渲染)

答:缓存高度:当我们创建frame模型的时候,计算出来cell的高度的时候,我们可以将cell的高度缓存到字典里面,以cell的indexPath 和 Identifier作为key.

2、有没有用过运行时,用它都能做什么?(交换方法,创建类,给新建的类增加方法改变isa指针)

答:

交换方式:一般卸载类的 +(void)load 方法里面

// 获取原始setBackgroundColor方法 
Method originalM = class_getInstanceMethod([self class], @selector(setBackgroundColor:));
// 获取自定义的pb_setBackgroundColor方法 
Method exchangeM = class_getInstanceMethod([self class], @selector(pb_setBackgroundColor:));
// 交换方法 
method_exchangeImplementations(originalM, exchangeM);

创建类:

Class MyClass = objc_allocateClassPair([NSObject class], "Person", 0);

添加方法:

参数一:类名参数

参数二:SEL 添加的方法名字参数

参数三:IMP指针 (IMP就是Implementation的缩写,它是指向一个方法实现的指针,每一个方法都有一个对应的IMP)

参数四:其中types参数为"i@:@“,按顺序分别表示:i 返回值类型int,若是v则表示void@ 参数id(self): SEL(_cmd)@ id(str)
V@:表示返回值是void 带有SEL参数

class_addMethod(Person, @selector(addMethodForMyClass:), (IMP)addMethodForMyClass, "V@:");

添加实例变量

参数一:类名参数

参数二:属性名称参数

参数三:开辟字节长度参数

参数四:对其方式参数

参数五:参数类型 "@" return: BOOL 是否添加成功

BOOL isSuccess = class_addIvar(Person, "name", sizeof(NSString *), 0, "@");

3.看过哪些第三方框架的源码?都是如何实现的?

4.SDWebImage的缓存策略?

答:SDWebImage加载一张图片的时候,会先在内存里面查找是否有这张图片,如果没有会根据图片的md5后的名称去沙盒里面寻找是否有这张图片,如果没有会开辟线程去下载,下载完成后加载到imageview上面,并将md为名称缓存到沙盒里面。

5.AFN为什么添加一条常驻线程?

答:AFN的目的就是开辟线程请求网络数据。如果没有常驻线程的话就会每次请求网络就去开辟线程,完成之后销毁开辟线程,这样就造成了资源的浪费。开辟一条常驻线程,就可以避免这种浪费,我们可以在每次的网络请求都添加到这条线程。

6、KVO的使用?实现原理?为什要创建子类来实现?

答:
KVO:键值观察,根据键对应的值的变化来调用方法。

注册观察者:addObserver:forKeyPath:options:context:

实现观察者:observerValueForKeyPath:ofObject:change:context:

移除观察者:removeObserver:forKeyPath:(对象销毁必须销毁观察者)

注意:使用KVO监听A对象的时候,监听的本质不是这个A对象,而是系统中一个中间对象 NSKVONotifying_A 并继承A对象,并且A对象的isa指针指向的也不是A的类而是这个NSKVONotifying_A 对象。

7.KVC的使用?实现原理?KVC拿到key以后是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar?

答:KVC:键值赋值,使用最多的是字典转模型。利用runtime获取对象的所有成员变量,再根据KVC键值赋值进行字典转模型。

setValue:forKey:只查找本类里面的属性

setValue:forKeyPath:会查找本类里面属性,没有会继续查找父类里面的属性。

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