最近看简书上一些面试题,抽时间整理了一份答案。
1、为什么说Objective-C是动态语言?
- 什么是动态语言:</br> 所谓的动态类型语言,意思就是类型的检查是在运行时做的。 </br> 动态语言是在运行时改变其结构:新的函数可以被引进,已有的函数可以被删除或改变,如JavaScript。而C、C++ 等语言则不属于动态语言
- 静态类型语言:</br> 静态类型语言的类型判断是在运行前判断(如编译阶段),比如C#、Java就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,而动态类型语言却不需要,所以一般动态语言都会采用dynamic typing,常出现于脚本语言中.需要明确说明一点,那就是,是不是动态类型语言与这门语言是不是类型安全的完全不相干的,不要将它们联系在一起!
优缺点:</br>静态类型语言的主要优点在于其结构非常规范,便于调试,方便类型安全;缺点是为此需要写更多的类型相关代码,导致不便于阅读、不清晰明了。动态类型语言的优点在于方便阅读,不需要写非常多的类型相关的代码;缺点自然就是不方便调试,命名不规范时会造成读不懂,不利于理解等。顺便说一下,现在有这样一种趋势,那就是合并动态类型与静态类型在一种语言中,这样可以在必要的时候取长补短,Boo就是一个很好的试验性例子
- Objective-C的动态性:
</br> objective-c语言是C语言的一个子类,所以Objective-C是一个静态语言,但是Objective-C的三大特性之一的多态性让其拥有了动态性,oc的动态性让程序可以在运行时判断其该有的行为,而不是像c等静态语言一样在编译构建时就确定下来
- 动态类型:即id类型id可以用来表示任意一个对象,它是一个objc_object结构类型的指针,其第一个成员是一个objc_class结构类型的指针。
typedef struct objc_object {
Class isa;
}
/*根类NSObject同样也只有一个Class成员*/
@interface NSObject
{
Class isa;
}
// 一个对象的isa指向了这个对象的类Class,而这个对象的类Class的isa指向了metaclass或者class,找到对应的静态方法(+方法)或者实例方法(-方法)。
- 动态绑定。让代码在运行时判断需要调用什么方法,而不是在编译时。与其他面向对象语言一样,方法调用和代码并没有在编译时连接在一起,而是在消息发送时才进行连接。运行时决定调用哪个方法
类的实例对象的isa指向它的类;</br>类的isa指向该类的metaclass;</br>类的super_class指向其父类,</br>如果该类为根类则值为NULL;
metaclass的isa指向根metaclass,如果该metaclass是根metaclass则指向自身;
metaclass 的super_class指向父metaclass如果该metaclass是根metaclass则指向该metaclass对应的类;
</br>Object-C为每个类的定义生成两个objc_class,一个普通的 class,另一个即metaclass。我们可以在运行期创建这两个 objc_class数据结构,然后使用objc_addClass将class注册到运行时系统中,以此实现动态地创建一个新的类
- 动态加载。让程序在运行时添加代码模块以及其他资源。用户可以根据需要加载一些可执行代码和资源,而不是在启动时就加载所有组件。可执行代码中可以含有和程序运行时整合的新类
2、MVC/MVP/MVVM
引用文章:杂谈: MVC/MVP/MVVM
1. MVC:</br>概念:
MVC最早存在于桌面程序中的, M是指业务数据, V是指用户界面, C则是控制器. 在具体的业务场景中, C作为M和V之间的连接, 负责获取输入的业务数据, 然后将处理后的数据输出到界面上做相应展示, 另外, 在数据有所更新时, C还需要及时提交相应更新到界面展示. 在上述过程中, 因为M和V之间是完全隔离的, 所以在业务场景切换时, 通常只需要替换相应的C, 复用已有的M和V便可快速搭建新的业务场景. MVC因其复用性, 大大提高了开发效率, 现已被广泛应用在各端开发中.
- 不完全的MVC:</br>
用户将c的任务完全放在UIViewController里面去处理,造成了一个臃肿的ViewController</br>
用户信息页面作为业务场景Scene需要展示多种数据M(Blog/Draft/UserInfo), 所以对应的有多个View(blogTableView/draftTableView/image...), 但是, 每个MV之间并没有一个连接层C, 本来应该分散到各个C层处理的逻辑全部被打包丢到了Scene这一个地方处理, 也就是M-C-V变成了MM...-Scene-...VV, C层就这样莫名其妙的消失了
-
正确的MVC使用姿势
UserVC作为业务场景, 需要展示三种数据, 对应的就有三个MVC, 这三个MVC负责各自模块的数据获取, 数据处理和数据展示, 而UserVC需要做的就是配置好这三个MVC, 并在合适的时机通知各自的C层进行数据获取, 各个C层拿到数据后进行相应处理, 处理完成后渲染到各自的View上, UserVC最后将已经渲染好的各个View进行布局即可
- 缺点:</br>
1.过度的注重隔离:
2.业务逻辑和业务展示强耦合: 没有区分业务逻辑和业务展示, 这对单元测试很不友好,有些业务逻辑(页面跳转/点赞/分享...)是直接散落在V层.
2.MVP:
MVC的缺点在于并没有区分业务逻辑和业务展示, 这对单元测试很不友好. MVP针对以上缺点做了优化, 它将业务逻辑和业务展示也做了一层隔离, 对应的就变成了MVCP. M和V功能不变, 原来的C现在只负责布局, 而所有的逻辑全都转移到了P层.
mvp的全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是通过 Controller。
</br> 相对于MVC, 它其实只做了一件事情, 即分割业务展示和业务逻辑. 展示和逻辑分开后, 只要我们能保证V在收到P的数据更新通知后能正常刷新页面, 那么整个业务就没有问题. 因为V收到的通知其实都是来自于P层的数据获取/更新操作, 所以我们只要保证P层的这些操作都是正常的就可以了. 即我们只用测试P层的逻辑, 不必关心V层的情况.
3.MVVM:
MVVM是Model-View-ViewModel的简写。微软的WPF带来了新的技术体验,如Silverlight、音频、视频、3D、动画……,这导致了软件UI层更加细节化、可定制化。同时,在技术层面,WPF也带来了 诸如Binding、Dependency Property、Routed Events、Command、DataTemplate、ControlTemplate等新特性。MVVM(Model-View-ViewModel)框架的由来便是MVP(Model-View-Presenter)模式与WPF结合的应用方式时发展演变过来的一种新型架构框架。它立足于原有MVP框架并且把WPF的新特性糅合进去,以应对客户日益复杂的需求变化。
MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大优点:
- 低耦合。View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的”View”上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
- 可重用性。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑。
- 独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计,生成xml代码。
- 可测试。界面素来是比较难于测试的,而现在测试可以针对ViewModel来写。
3.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和delegate的区别
1.delegate为什么要用weak:</br>
为了避免循环引用,假如a使用b的代理</br>为什么代理要用weak
@interface Dog : NSObject
/* weak:指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制 */
@property (nonatomic, weak) id<DogDelegate>delegate;
/* strong:该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用(Retain Cycles) */
@property (nonatomic, strong)id<Dog_strongDelegate>delegate_strong;
2.delegate和dataSource的区别:</br>
dataSource是数据源,是用来连接数据的,delegate是代理函数,用来实现方法的。
dataSource我们需要关心的是我有什么数据,需要传递什么数据或者属性给dataSourse数据源,</br>delegate 我们需要关心的是这个函数我可以用来做什么
3.block和代理的区别</br>
无论是block还是delegate模式本质上都是回调,使用block,其优点是回调的block代码块直接就放在了block赋值的地方,使代码更为紧凑,缺点是block内使用到当前类的实例变量的时候,需要注意循环引用的问题,即需要使用__block(MRC下)或者__weak(ARC下)定义一个弱引用的self出来,block里面使用弱引用的self去操作属性或调用方法。delegate模式不用像block一样做特殊处理,但是如果多个对象设置的代理是同一个对象,就需要在delegate方法中判断当前执行代理的是哪个对象。
4.属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
--
1.属性的实质是什么:
- 属性</br>
-
属性作用和关键字</br>
@property = ivar + getter + setter;</br>实例变量+get方法+set方法,也就是说使用@property 系统会自动生成setter和getter方法;
2.属性默认的关键字
关键字 | 注释 |
---|---|
readwrite | 读写属性 默认属性 |
readonly | 只读属性,可以获取不能设置 |
assign | 基本数据类型、直接赋值、不会使引用计数+1 |
retain | 引用计数+1 |
copy | 建立一个索引计数为1的对象,在赋值时使用传入值的一份拷贝 |
nonatomic | 非原子性访问,多线程并发访问会提高性能 |
atomic | 线程安全 |
strong | 强引用 相当于retain |
weak | 弱引用、当引用计数为0时对应的指针变量变为nil |
3.@synthesize和@dynamic
- @property 有两个对应的词,一个是@synthesize,一个是@dynamic。
如果@synthesize 和@dynamic 都没写,那么默认的就是
@syntheszie var = _var; - @synthesize 的语义是如果你没有手动实现 setter 方法和 getter 方法,那么编译器会自动为你加上这两个方法
- @dynamic 告诉编译器:属性的 setter 与 getter 方法由用户自己实现,不自动生成。(当然对于 readonly 的属性只需提供 getter 即可)
- @dynamic var;然后你没有提供@setter 方法和@getter 方法,编译的时候没问题,但是当程序运行到 instance.var = someVar,由于缺 setter方法会导致程序崩溃;
或者当运行到 someVar = instance.var 时,由于缺 getter 方法同样会导致崩溃
4.属性的默认关键字是什么
- 基本数据: atomic,readwrite,assign
- 普通的 OC 对象: atomic,readwrite,strong
5.NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)
- NSString、NSArray、NSDictionary 等等经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,为确保对象中的属性值不会无意间变动,应该在设置新属性值时拷贝一份,保护其封装性block,也经常使用 copy,关键字block
- 因为父类指针可以指向子类对象,使用 copy 的目的是为了让本对象的属性不受外界影响,使用 copy 无论给我传入是一个可变对象还是不可对象,我本身持有的就是一个不可变的副本.</br>
(NSString使用strong)如果我们使用是 strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性
@property (nonatomic, strong) NSString *strong_String;
@property (nonatomic, copy) NSString *cop_String;
self.strong_String = @"strong__abcdefg";
self.cop_String =@"copy__abcdefg";
NSMutableString *mutableString =[NSMutableString stringWithString:@"123456789"];
self.strong_String = mutableString;
self.cop_String = mutableString;
NSLog(@"mutableString:%@--- strong_String:%@ \n cop_String:%@",mutableString,self.strong_String,self.cop_String);
[mutableString appendString:@"abcdefg"];
/* strong_String 的值跟随mutableString发生了改变 cop_String的值没有变*/
NSLog(@"mutableString:%@--- strong_String:%@ \n cop_String:%@",mutableString,self.strong_String,self.cop_String);
//mutableString123456789abcdefg--- strong_String123456789abcdefg
cop_String123456789
6.如何使自己所写的类居右copy功能
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopyiog 与NSMutableCopying 协议,不过一般没什么必要,实现 NSCopying 协议就够了
// 实现不可变版本拷贝
- (id)copyWithZone:(NSZone *)zone; // 实现可变版本拷贝
- (id)mutableCopyWithZone:(NSZone *)zone;
// 重写带 copy 关键字的 setter
- (void)setName:(NSString *)name {
_name = [name copy];
}
7.可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?
- 不可变对象</br>
[不可变对象 copy] // 浅复制
[不可变对象 mutableCopy] //深复制
[可变对象 copy] //深复制
[可变对象 mutableCopy] //深复制 - 可变对象</br>
[不可变对象 copy] // 浅复制
[不可变对象 mutableCopy] //单层深复制
[可变对象 copy] //单层深复制
[可变对象 mutableCopy] //单层深复
/* 不可变对象 */
NSString *string =@"abcdefg";
NSString *copy_string = [string copy]; /* 浅复制 只复制指针 */
NSMutableString *mutable_copy_string =[string mutableCopy]; /* 深复制 复制内容 即新开辟一块内存地址 */
/* 打印地址
string abcdefg----0x104e7e078
copy abcdefg----0x104e7e078
mutableCopy abcdefg----0x60800006a100 */
/* 可变对象 */
NSMutableString *mutableString =[NSMutableString stringWithString:@"mutable_abcdefg"];
NSMutableString *copy_mutableString =[mutableString copy];/* 深复制 复制内容 即新开辟一块内存地址 */
NSMutableString *mutable_copy_mutableString =[mutableString mutableCopy];/* 深复制 复制内容 即新开辟一块内存地址 */
/* 打印地址
string mutable_abcdefg----0x60000007e4c0
copy mutable_abcdefg----0x6000000459d0
mutableCopy mutable_abcdefg----0x60000007bac0 */
/* 系统的容器类对象:指NSArray,NSDictionary等 */
NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copy_array = [array copy]; /* 浅复制 只复制指针 */
NSMutableArray *mutable_copy_array =[array mutableCopy]; /* 容器本身是深复制 里边的元素是浅复制 */
[mutable_copy_array addObject:@"de"];
/* 所有数组的a 都变成了head 说明容器深复制只是复制了本身,里边的元素仍然是复制的指针 */
NSMutableString *testString =[array objectAtIndex:0];
[testString appendString:@"head"];
8.nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
- nonatomic 非原子型访问 多线程并发使用可以提高性能,atomic原子型访问,线程安全,一个线程读写时不允许别的线程读写,需要消耗大量内存性能。
- atomic 不是绝对的线程安全,此处的此处的线程安全是就getter,setter而言的。在多线程中,atomic只保证getter、setter方法安全,并不保证其它操作,例如字符串拼接,数组移除元素等,并没有执行getter和setter方法,顾不是绝对安全的。绝对安全应该加线程互斥锁
atomic加锁原理
@property (assign, atomic) int age;
- (void)setAge:(int)age
{
@synchronized(self) {
_age = age;
}
}
- iOS开发的建议:
- 所有属性都声明为nonatomic
- 尽量避免多线程抢夺同一块资源
- 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
5.进程和线程的区别?同步异步的区别?并行和并发的区别?
-
<strong>进程和线程的区别:</strong></br>
一个程序至少有一个进程,一个进程至少有一个线程,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率</br>
进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。进程是线程的容器,真正完成代码执行的线程,而进程则作为线程的执行环境。一个程序至少包含一个进程,一个进程至少包含一个线程,一个进程中的所有线程共享当前进程所拥有的资源。 -
<strong>同步和异步的区别:</strong></br>
同步: 进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件</br>
异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情 -
<strong>并发和并行的区别</strong></br>
并发:并发指的是一种现象,一种经常出现,无可避免的现象,它描述的是“多个任务同时发生,需要被处理” 这一现象,它的侧重点在与发生。</br>
并行:并行指的是一种技术,一个同时处理多个任务的技术,他描述了一种同时能够处理多个任务的能力,侧重点在于“运行”
6.线程间的通信
- <strong>线程间通信的体现:</strong></br>
- 一个线程传递数据给另一个线程
- 在一个线程中执行完特定任务后,转到另一个线程继续执行任务
- <strong>方法:</strong></br>
NSThread
可以先将自己的当前线程对象注册到某个全局的对象中去,这样相互之间就可以获取对方的线程对象,然后就可以使用下面的方法进行线程间的通信了,由于主线程比较特殊,所以框架直接提供了在主线程执行的方法- GCD
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
开启一个全局队列的子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 数据请求完毕
//我们知道UI的更新必须在主线程操作,所以我们要从子线程回调到主线程
dispatch_async(dispatch_get_main_queue(), ^{
//我已经回到主线程更新
});
});
7.NSCache优于NSDictionary的几点?
NSCache类和NSDictionary很相似,提供key,value的存储,不一样的是NSCache在内存吃紧的时候会做自动释放。
例如遇到一个问题是,在使用大量图片的app中,需要从存储里面读取数据,每次都从文件系统里面读取文件会造成卡顿现象。
解决办法就是把NSData对象缓存起来,先从NSCache里面读取数据,然后再从文件系统获取数据,提高效率
8.实现description方法能取到什么结果
description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)
9.objc使用什么机制管理对象内存?
ARC和MRC
通过 retainCount 的机制来决定对象是否需要释放。每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。
网友评论