iOS——总结
1、为什么说Objective-C是一门动态的语言?
答:什么叫动态静态->静态、动态是相对的,这里动态语言指的是不需要在编译时确定所有的东西,在运行时还可以动态的添加变量、方法和类;Objective-C 可以通过Runtime 这个运行时机制,在运行时动态的添加变量、方法、类等,所以说Objective-C 是一门动态的语言。
其他说法
Objective-C 是C 的超集,在C 语言的基础上添加了面向对象特性,并且利用Runtime 这个运行时机制,为Objective-C 增添了动态的特性。
Objective-C 使用的是 “消息结构” 并非 “函数调用”:使用消息结构的的语言,其运行时所应执行的代码由运行期决定;而使用函数调用的语言,则由编译器决定;
(1)动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python和Ruby就是一种典型的动态类型语言,其他的各种脚本语言如VBScript也多少属于动态类型语言。
(2)静态类型语言:静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、JAVA等。
因为在运行期可以继续向类中添加方法,所以编译器在编译时还无法确定类中是否有某个方法的实现。对于类无法处理一个消息就会触发消息转发机制
消息转发分为两大阶段:
“动态方法解析”:先征询接收者,所属的类,能否动态添加方法,来处理这个消息,若可以则结束,如不能则继续往下走
“完整的消息转发机制”:
请接收者看看有没其他对象能处理这条消息,若有,就把这个消息转发给那个对象然后结束
运行时系统会把与消息有关细节全部封装到NSInvocation 对象中,再给对象最后一次机会,令其设法解决当前还未处理的这条消息
Objective-c的动态性
Objective-C的动态性,让程序在运行时判断其该有的行为,而不是像c等静态语言在编译构建时就确定下来。它的动态性主要体现在3个方面:
1.动态类型:如id类型。实际上静态类型因为其固定性和可预知性而使用的特别广泛。静态类型是强类型,动态类型是弱类型,运行时决定接收者。
2.动态绑定:让代码在运行时判断需要调用什么方法,而不是在编译时。与其他面向对象语言一样,方法调用和代码并没有在编译时连接在一起,而是在消息发送时才进行连接。运行时决定调用哪个方法。
3.动态载入。让程序在运行时添加代码模块以及其他资源。用户可以根据需要执行一些可执行代码和资源,而不是在启动时就加载所有组件。可执行代码中可以含有和程序运行时整合的新类。
//------------------------
为什么说 Objective-C 是一种动态语言?这看起来似乎是一个简单而不简约而又老生常谈的问题,然而真正弄懂 Objective-C 的动态特性,三言两语还真是说不清楚。
Objective-C 是 C 的超集,在 C 语言的基础上添加了面向对象的特性,并且用 Runtime 这个运行时机制,为 Objective-C 增添了动态的特性。这篇文章不以博大精深的 Runtime 作为重点,而以开发过程中的一些具体实例来诠释 Objective-C 的动态特性。下面分别以 Objective-C 多态的三个特性动态类型、动态绑定和动态加载来加以说明。
*动态类型
即运行时再决定对象的类型。举个程序中的实例,即 id 类型,任何对象都可以被 id 指针所指,只有在运行时才能决定是什么类型。而静态类型在编译时就能确定是什么类型,如 int , NSString 等,若程序发生了类型不对应的问题,编译器就会发出警告。而动态类型在编译器编译的时候是不能被识别的,要等到运行时(run time)根据语境来识别确定。
总结为一点:动态类型和静态类型在时序的确定上是不同的,要分清运行时和编译时。
*动态绑定
在 Objective-C 中,一个对象能否调用指定的方法不是由编译器决定而是由运行时决定,这被称作是方法的动态绑定。基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,比如我们一般向一个 NSObject 对象发送 -respondsToSelector: 消息来确定对象是否可以对某个 SEL 作出响应,而在 OC 消息转发机制被触发之前,对应的类的 +resolveClassMethod: 将会被调用,在此时有机会动态地向类或实例中添加新的方法,也就是说,类的实现是可以动态绑定的。
*动态加载
让程序在运行时添加代码模块和资源,程序员可以根据需要执行一些可执行代码和资源,而不是在启东市就加载所有组件。举个非常通俗易懂的例子,开发的时候,需要为某种 icon 提供多个不同大小的图片,@2x,@3x,以保证设备更换的时候,图片也会自动地更换,这也体现了其动态加载的特性
2、讲一下MVC和MVVM,MVP?
MVVM分别指什么
Model-数据层
ViewController/View-展示层
ViewModel- 数据模型
MVVM与MVC的不同
首先我们简化一下MVC的架构模式图:
image在这里,Controller需要做太多得事情,表示逻辑、业务逻辑,所以代码量非常的大。而MVVM:
[图片上传失败...(image-379863-1540304692145)]
比如我们有一个需求:一个页面,需要判断用户是否手动设置了用户名。如果设置了,正常显示用户名;如果没有设置,则显示“简书0122”这种格式。(虽然这些本应是服务器端判断的)
我们看看MVC和MVVM两种架构都是怎么实现这个需求的
MVC:
Model类:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, assign) NSInteger userId;
@end
ViewController类:
#import "HomeViewController.h"
#import "User.h"
@interface HomeViewController ()
@property (nonatomic, strong) UILabel *lb_userName;
@property (nonatomic, strong) User *user;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (_user.userName.length > 0) {
_lb_userName.text = _user.userName;
} else {
_lb_userName.text = [NSString stringWithFormat:@"简书%ld", _user.userId];
}
}
这里我们需要将表示逻辑也放在ViewController中。
MVVM:
Model类:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, assign) NSInteger userId;
@end
ViewModel类:
声明:
#import <Foundation/Foundation.h>
#import "User.h"
@interface UserViewModel : NSObject
@property (nonatomic, strong) User *user;
@property (nonatomic, copy) NSString *userName;
- (instancetype)initWithUser:(User *)user;
@end
实现:
#import "UserViewModel.h"
@implementation UserViewModel
- (instancetype)initWithUser:(User *)user {
self = [super init];
if (!self) return nil;
_user = user;
if (user.userName.length > 0) {
_userName = user.userName;
} else {
_userName = [NSString stringWithFormat:@"简书%ld", _user.userId];
}
return self;
}
@end
Controller类:
#import "HomeViewController.h"
#import "UserViewModel.h"
@interface HomeViewController ()
@property (nonatomic, strong) UILabel *lb_userName;
@property (nonatomic, strong) UserViewModel *userViewModel;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
_lb_userName.text = _userViewModel.userName;
}
可见,Controller中我们不需要再做多余的判断,那些表示逻辑我们已经移植到了ViewModel中,ViewController明显轻量了很多。
总结:
- MVVM同MVC一样,目的都是分离Model与View,但是它更好的将表示逻辑分离出来,减轻了Controller的负担;
- ViewController中不要引入Model,引入了就难免会在Controller中对Model做处理;
疑问:把处理逻辑放到model里面写可以吗?
其实感觉你这样做,在该场景下达到了更好的效果。在model层里面进行了业务逻辑的处理。这样,同样减轻了massive viewcontroller。
但是这样有几个坏处,第一,破坏了model的原始性,model是原始数据,本不应该直接操作。第二,诸如其它需要在ViewModel里处理的逻辑,比如数据请求,数据缓存等不适合在Model里面处理的业务,就又得放到ViewController里面进行处理了。
ViewModel是新增加的一个层,有了这个概念,就可以想法来减轻ViewController了。
MVP
全称:Model-View-Presenter ;MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方 Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。
3、为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
1>.为什么代理要用weak?
防止循环引用。例如View有一个协议,需要一个代理实现回调。一个Controller添加这个View,并且遵守协议,成为View的代理。如果不用week,用strong,Controller ->View -> delegate -> Controller,就循环引用了。
2>.代理的delegate和dataSource有什么区别?
delegate偏重于与用户交互的回调,有那些方法可以供我使用,例如UITableviewDelegate;dataSource偏重于数据的回调,view里面有什么东西,属性都是什么,例如UITableviewDatasource;
3>.block和代理的区别?
block和代理是iOS开发中实现回调的两种方式,本文主要是对两者的应用场景做一下对比。
1.block简介
在 iOS中, block一共分三种。
(1)全局静态 block,不会访问任何外部变量,执行完就销毁。
^{
NSLog(@"Hello World!");
}();
(2)保存在栈中的 block,当函数返回时会被销毁,和第一种的区别就是调用了外部变量。
[UIView animateWithDuration:3 animations:^{
self.view.backgroundColor = [UIColor redColor];
}];
(3)保存在堆中的 block,当引用计数为 0 时会被销毁。例如按钮的点击事件,一直存在,即使执行过,也不销毁,因为按钮还可能被点击。直到持有按钮的View被销毁,它才会被销毁。
#import <UIKit/UIKit.h>
typedef void(^ButtonClickBlcok)();
@interface TestView : UIView
@property (nonatomic, copy) ButtonClickBlcok buttonClickBlcok;
@end
#import "TestView.h"
@implementation TestView
- (IBAction)buttonClick:(id)sender {
if (self.buttonClickBlcok) {
self.buttonClickBlcok();
}
}
@end
2.block优点
block的代码可读性更好。因为block只要实现就可以了,而代理需要遵守协议并且实现协议里的方法,而两者还不在一个地方。代理使用起来也更麻烦,因为要声明协议、声明代理属性、遵守协议、实现协议里的方法。block不需要声明,也不需要遵守,只需要声明属性和实现就可以了。
block是一种轻量级的回调,可以直接访问上下文,由于block的代码是内联的,运行效率更高。block就是一个对象,实现了匿名函数的功能。所以我们可以把block当做一个成员变量、属性、参数使用,使用起来非常灵活。像用AFNetworking请求数据和GCD实现多线程,都使用了block回调。
3.block缺点
blcok的运行成本高。block出栈需要将使用的数据从栈内存拷贝到堆内存,当然对象的话就是引用计数加1,使用完或者block置nil后才销毁。delegate只是保存了一个对象指针(一定要用week修饰delegate,不然也会循环引用),直接回调,没有额外消耗。就像C的函数指针,只多做了一个查表动作。
block容易造成循环引用,而且不易察觉。因为为了blcok不被系统回收,所以我们都用copy关键字修饰,实行强引用。block对捕获的变量也都是强引用,所以就会造成循环引用。
#import "ViewController.h"
typedef void(^TestBlock)(void);
@interface ViewController ()
{
void (^_testCycleBlock)(void);
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak ViewController *weakSelf = self;
_testCycleBlock = ^{
/**
//引发循环引用
NSLog(@"%@", self);
*/
//防止循环引用
NSLog(@"%@", weakSelf);
};
}
@end
4.如何使用
优先使用block。
如果回调的状态很多,多于三个使用代理。
如果回调的很频繁,次数很多,像UITableview,每次初始化、滑动、点击都会回调,使用代理。
block和代理都各有优缺点,所以我们一定要理解区分使用场景,应用适合的回调方式。优化APP的性能,提高流畅性,从点滴做起.
4、属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
1>.属性的实质是什么?包括哪几个部分
@property = ivar + getter + setter;
利用class_copyPropertyList 查看类的所有属性
利用class_copyIvarList查看类的所有成员变量
利用class_copyMethodList查看类的所有方法
2>.属性默认的关键字都有哪些?
3>.@dynamic关键字
@dynamic告诉编译器,属性的setter与getter方法由用户自己实现。
4>.@synthesize关键字
@synthesize的语义是如果你没有手动实现setter方法和getter方法,那么编译器会自动为你加上这两个方法。
链接:https://www.jianshu.com/p/0ce46e09dd1e
9、为什么IBOutlet修饰的UIView也适用weak关键字?
1.为什么代理要用weak?代理的delegate和dataSource有什么区别?block和代理的区别?
A:为了避免循环引用。weak指明该对象并不负责保持delegate这个对象,delegate这个对象的销毁由外部控制。strong该对象强引用delegate,外界不能销毁delegate对象,会导致循环引用。DataSource是关于View的内容的东西包括属性,数据等等,而Delegate则是一些我们可以调用的方法,全是操作。block和代理都能解决对象间交互的问题,block更轻型,更简单,能够直接访问上下文,代码通常在同一个地方,这样读代码也连贯。缺点是容易引起循环引用。delegate更重一些,需要实现接口,它的方法分开来,很多时候需要存储一些临时数据,另外相关的代码需要分离到各处没有block好读,其优点就是它是用weak关键字修饰的,不会引起循环引用。
2.属性的实质是什么?包括哪几个部分?属性默认的关键字都有哪些?@dynamic关键字和@synthesize关键字是用来做什么的?
A:属性的本质是@property = ivar+getter+setter,也就是说@property系统会自动生成getter和setter方法。属性默认的关键字包括atomic,nonatomic,@synthesize,@dynamic,getter=getterName,setter=setterName,readwrite,readonly,assign,retain,copy。
@dynamic:表示变量对应的属性访问器方法,是动态实现的,你需要在 NSObject 中继承而来的 +(BOOL) resolveInstanceMethod:(SEL) sel 方法中指定 动态实现的方法或者函数。
@synthesize:如果没有实现setter和getter,编译器能够自动实现getter和setter方法。
3.NSString为什么要用copy关键字,如果用strong会有什么问题?(注意:这里没有说用strong就一定不行。使用copy和strong是看情况而定的)
A:针对于当把NSMutableString赋值给NSString的时候,才会有不同,用copy的话NSString的值不会发生变化,用strong则会发生变化,随着NSMutableString的值变化。如果是赋值是NSString对象,那么使用copy还是strong,结果都是一样的,因为NSString对象根本就不能改变自身的值,他是不可变的。
4.如何令自己所写的对象具有拷贝功能?
A:若想让自己写的对象具有拷贝功能,则需要实现NSCopying协议。如果自定义的对象分为可变版本和非可变版本,那么就要同时实现NSCopying和NSMutableCopying协议,不过一般没什么必要,实现NSCopying协议就够了。
5.可变集合类 和 不可变集合类的 copy 和 mutablecopy有什么区别?如果是集合是内容复制的话,集合里面的元素也是内容复制么?
A:对于不可变对象,copy操作是浅复制,mutableCopy是深复制。对于不可变对象,mutableCopy不仅仅是深复制,返回的对象类型还是不可变对象类型相应的可变对象的类型。内容复制也就是深拷贝,集合的深复制有两个方法,可以用initWithArray:copyItems:将第二个参数设置为YES即可进行深复制,如:NSDictionary shallowCopyDict = [NSDictionary alloc]initWithDictionary:someDictionary copyItems:YES];如果用这个方法深复制,集合里的每个元素都会收到copyWithZone:消息。如果集合里的对象遵循NSCopying协议,那么对象就会深复制到新的集合。如果对象没有遵循NSCopying协议,而尝试用这种方法进行深复制则会出错。copyWithZone:这种拷贝方式只能提供一层内存拷贝,而非真正的深拷贝。第二种方法是将集合进行归档解档,如:NSArray trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
6.为什么IBOutlet修饰的UIView也适用weak关键字?
A:因为既然有外链那么视图在xib或者storyboard中肯定存在,视图已经对它有一个强引用了。
- nonatomic和atomic的区别?atomic是绝对的线程安全么?为什么?如果不是,那应该如何实现?
A:nonatomic和atomic的区别在于两者自动生成getter和setter的方法不一样,如果你自己写getter和setter方法,那么(getter,setter,retain,copy,assign)只起提示作用,写不写都一样。
对于atomic的属性,系统生成的getter和setter会保证get,set的操作完整性,不受其他线程影响。比如线程A的getter方法运行到一半,线程B调用了setter,那么线程A的getter还是能得到一个完整的对象。
而nonatomic就没有这个保证了,所以速度要比atomic快。
不过atomic可不能保证线程安全,如果线程A调用了getter,与此同时线程B和线程C都调了setter,那最后线程Aget到的值,三种都有可能:可能是B,C set之前原始的值,也可能是B set的值,也可能是C set的值。同时这个最终的值,也可能是B set的值,也可能是C set的值。要保证安全,可以使用线程锁。
8.UICollectionView自定义layout如何实现?
A:UICollectionViewLayoutAttributes,UICollectionViewFlowLayout。
9.用StoryBoard开发界面有什么弊端?如何避免?
A:难以维护,如果需要改动全局的一个字体,如果是代码的话就很好办,pch或头文件中改动就好了。如果是storyboard中就需要一个一个改动很麻烦。
如果storyboard中scene太多,打开storyboard会比较慢。
错误定位比较困难,好多错误提示模棱两可。
10.进程和线程的区别?同步异步的区别?并行和并发的区别?
A:进程是一个内存中运行的应用程序,比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程。
同步是顺序执行,执行完一个再执行下一个。需要等待,协调运行。
异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这些事件完成后再工作。
并行和并发 是前者相当于三个人同时吃一个馒头,后者相当于一个人同时吃三个馒头。
并发性(Concurrence):指两个或两个以上的事件或活动在同一时间间隔内发生。并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
并行性(parallelism)指两个或两个以上事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。
区别:(并发)一个处理器同时处理多个任务和(并行)多个处理器或者是多核的处理器同时处理多个不同的任务。
11.线程间通信?
A:NSThread、GCD、NSOperation。
12.GCD的一些常用的函数?(group,barrier,信号量,线程同步)
A:1.延迟执行任务函数:dispatch_after(.....)。
2.一次性执行dispatch_once(...)。
3.栅栏函数dispatch_barrier_async/dispatch_barrier_sync。
4.队列组的使用dispatch_group_t。
5.GCD定时器。
13.如何使用队列来避免资源抢夺?
A:dispatch_barrior_async 作用是在并行队列中,等待前面两个操作并行操作完成。
14.数据持久化的几个方案(fmdb用没用过)
A:Coredata,realm,fmdb。
15.说一下AppDelegate的几个方法?从后台到前台调用了哪些方法?第一次启动调用了哪些方法?从前台到后台调用了哪些方法?
A:
1.当程序第一次运行并且将要显示窗口的时候执行,在该方法中我们完成的操作
-
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
2.程序进入后台的时候首先执行程序将要取消活跃该方法 -
(void)applicationWillResignActive:(UIApplication *)application
3.该方法当应用程序进入后台的时候调用 -
(void)applicationDidEnterBackground:(UIApplication *)application
4.当程序进入将要前台的时候调用 -
(void)applicationWillEnterForeground:(UIApplication *)application
5.应用程序已经变得活跃(应用程序的运行状态) -
(void)applicationDidBecomeActive:(UIApplication *)application
6.当程序将要退出的时候调用,如果应用程序支持后台运行,该方法被applicationDidEnterBackground:替换 -
(void)applicationWillTerminate:(UIApplication *)application
16.NSCache优于NSDictionary的几点?
A:NSCache 是一个容器类,类似于NSDIctionary,通过key-value 形式存储和查询值,用于临时存储对象。
注意一点它和NSDictionary区别就是,NSCache 中的key不必实现copy,NSDictionary中的key必须实现copy。
NSCache中存储的对象也不必实现NSCoding协议,因为毕竟是临时存储,类似于内存缓存,程序退出后就被释放了。
17.知不知道Designated Initializer(指定初始化函数)?使用它的时候有什么需要注意的问题?
A:比如:
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
18.实现description方法能取到什么效果?
A:1.NSLog(@"%@", objectA);这会自动调用objectA的description方法来输出ObjectA的描述信息.
2.description方法默认返回对象的描述信息(默认实现是返回类名和对象的内存地址)
3.description方法是基类NSObject 所带的方法,因为其默认实现是返回类名和对象的内存地址, 这样的话,使用NSLog输出OC对象,意义就不是很大,因为我们并不关心对象的内存地址,比较关心的是对象内部的一些成变量的值。因此,会经常重写description方法,覆盖description方法的默认实现。
19.objc使用什么机制管理对象内存?
A:通过 retainCount 的机制来决定对象是否需要释放。 每次 runloop 的时候,都会检查对象的 retainCount,如果retainCount 为 0,说明该对象没有地方需要继续使用了,可以释放掉了。
20.block的实质是什么?一共有几种block?都是什么情况下生成的?
A:block对象就是一个结构体,里面有isa指针指向自己的类(global malloc stack),有desc结构体描述block的信息,forwarding指向自己或堆上自己的地址,如果block对象截获变量,这些变量也会出现在block结构体中。最重要的block结构体有一个函数指针,指向block代码块。block结构体的构造函数的参数,包括函数指针,描述block的结构体,自动截获的变量(全局变量不用截获),引用到的block变量。(block对象也会转变成结构体)
block代码块在编译的时候会生成一个函数,函数第一个参数是前面说到的block对象结构体指针。执行block,相当于执行block里面forwarding里面的函数指针。