iOS程序运行流程
系统调用app的main函数
main函数调用UIApplicationMain.
UIApplicationMain创建shared application instance, UIApplication默认的instance.
UIApplicationMain读取Info.plist找到主nib文件, 加载nib,把shared application instance 设为nib的owner.
通过nib文件,创建app的独立UIWindows object.
通过nib,实例化了程序的AppDelegate object.
app内部启动结束,application:didFinishLaunchingWith- Options: 被设定成 wAppDelegate instance.
AppDelegate向UIWindow instance发makeKeyAndVisible消息, app界面展示给用户. app准备好接收用户的操作指令.
简述应用程序按Home键进入后台时的生命周期,以及从后台回到前台时的生命周期?
应用程序的状态:
Not running 未运行,程序没启动
Inactive 未激活,程序在前台运行,不过没接受到事件,没有事件处理的状态下通常处于这个状态。
Active 激活 程序在前台并且接收到了事件
Backgound 后台 程序在后台而且能执行代码,大多数程序进入这个状态后会在在这个状态上停留一会。
Suspended 挂起 程序在后台不能执行代码。
- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
//告诉代理进程启动但还没进入状态保存 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
// 告诉代理启动基本完成程序准备开始运行 - (void)applicationWillResignActive:(UIApplication *)application
// 当应用程序将要入非活动状态执行,在此期间,应用程序不接收消息或事件,比如来电话了 - (void)applicationDidBecomeActive:(UIApplication *)application
// 当应用程序入活动状态执行,这个刚好跟上面那个方法相反 - (void)applicationDidEnterBackground:(UIApplication *)application
// 当程序被推送到后台的时候调用。所以要设置后台继续运行,则在这个函数里面设置即可 - (void)applicationWillEnterForeground:(UIApplication *)application
//当程序从后台将要重新回到前台时候调用,这个刚好跟上面的那个方法相反。 - (void)applicationWillTerminate:(UIApplication *)application
//当程序将要退出是被调用,通常是用来保存数据和一些退出前的清理工作。这个需要要设置UIApplicationExitsOnSuspend的键值。 - (void)applicationDidFinishLaunching:(UIApplication*)application
//当程序载入后执行
描述应用程序的启动顺序?
1、程序入口main函数创建UIApplication实例和UIApplication代理实例
2、在UIApplication代理实例中重写启动方法,设置第一ViewController
3、在第一ViewController中添加控件,实现对应的程序界面。
参考链接:https://www.jianshu.com/p/bdf3f20848b9
iOS本地数据存储都有哪几种方式?iOS如何实现复杂对象的存储?
NSKeyedArchiver(归档)采用归档的形式来保存数据,该数据对象需要遵守NSCoding协议,并且该对象对应的类必须提供encodeWithCoder:和initWithCoder:方法;
NSUserDefaults:用来保存应用程序设置和属性、用户保存的数据。用户再次打开程序或开机后这些数据仍然存在。NSUserDefaults可以存储的数据类型包括:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary;
Write写入方式:永久保存在磁盘中;
SQLite(FMDB、CoreData) ;
NSCoding + NSKeyedArchiver实现复杂对象的存储;
堆和栈的区别?
栈区(stack)--由编译器自动分配释放,存放函数的参数值、局部变量的值。
堆区(heap)--一般由程序员分配释放。
全局区(静态区)(static)--全局变量和静态变量。程序结束后由系统释放。
文字常量区--常量字符串存放在这里。程序结束后由系统释放。
程序代码区—存放函数体的二进制文件。
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢 出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
iOS类是否可以多继承?如果没有,那可以用其他方法实现吗?简述实现过程
不可以,可以通过消息转发、delegate和protocol和类别来实现类似多继承
KVO (Key-value observing)
KVO是观察者模式的另一实现。
使用了isa混写(isa-swizzling)来实现KVO
使用setter方法改变值KVO会生效,使用setValue:forKey即KVC改变值KVO也会生效,因为KVC会去调用setter方法
-
(void)setValue:(id)value
{
[self willChangeValueForKey:@"key"];[super setValue:value];
[self didChangeValueForKey:@"key"];
}
那么通过直接赋值成员变量会触发KVO吗?
不会,因为不会调用setter方法,需要加上
willChangeValueForKey和didChangeValueForKey方法来手动触发才行
KVC(Key-value coding)**
-(id)valueForKey:(NSString *)key;
-(void)setValue:(id)value forKey:(NSString *)key;
复制代码
KVC就是指iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态地访问和修改对象的属性。而不是在编译时确定,这也是iOS开发中的黑魔法之一。很多高级的iOS开发技巧都是基于KVC实现的
当调用setValue:属性值 forKey:@”name“的代码时,,底层的执行机制如下:
- 程序优先调用set<Key>:属性值方法,代码通过setter方法完成设置。注意,这里的<key>是指成员变量名,首字母大小写要符合KVC的命名规则,下同
- 如果没有找到setName:方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUndefinedKey:方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为<key>的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以<key>命名的变量,KVC都可以对该成员变量赋值。
- 如果该类即没有set<key>:方法,也没有_<key>成员变量,KVC机制会搜索_is<Key>的成员变量。
- 和上面一样,如果该类即没有set<Key>:方法,也没有_<key>和_is<Key>成员变量,KVC机制再会继续搜索<key>和is<Key>的成员变量。再给它们赋值。
- 如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUndefinedKey:方法,默认是抛出异常。
即如果没有找到Set<Key>方法的话,会按照_key,_iskey,key,iskey的顺序搜索成员并进行赋值操作。
如果开发者想让这个类禁用KVC,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set<Key>:属性名时,会直接用setValue:forUndefinedKey:方法。
当调用valueForKey:@”name“的代码时,KVC对key的搜索方式不同于setValue:属性值 forKey:@”name“,其搜索方式如下:
- 首先按get<Key>,<key>,is<Key>的顺序方法查找getter方法,找到的话会直接调用。如果是BOOL或者Int等值类型, 会将其包装成一个NSNumber对象。
- 如果上面的getter没有找到,KVC则会查找countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes格式的方法。如果countOf<Key>方法和另外两个方法中的一个被找到,那么就会返回一个可以响应NSArray所有方法的代理集合(它是NSKeyValueArray,是NSArray的子类),调用这个代理集合的方法,或者说给这个代理集合发送属于NSArray的方法,就会以countOf<Key>,objectIn<Key>AtIndex或<Key>AtIndexes这几个方法组合的形式调用。还有一个可选的get<Key>:range:方法。所以你想重新定义KVC的一些功能,你可以添加这些方法,需要注意的是你的方法名要符合KVC的标准命名方法,包括方法签名。
- 如果上面的方法没有找到,那么会同时查找countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所的方法的代理集合,和上面一样,给这个代理集合发NSSet的消息,就会以countOf<Key>,enumeratorOf<Key>,memberOf<Key>组合的形式调用。
- 如果还没有找到,再检查类方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(默认行为),那么和先前的设值一样,会按_<key>,_is<Key>,<key>,is<Key>的顺序搜索成员变量名,这里不推荐这么做,因为这样直接访问实例变量破坏了封装性,使代码更脆弱。如果重写了类方法+ (BOOL)accessInstanceVariablesDirectly返回NO的话,那么会直接调用valueForUndefinedKey:方法,默认是抛出异常。
iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:)
属性关键字**
1.读写权限:readonly,readwrite(默认)
2.原子性: atomic(默认),nonatomic。atomic读写线程安全,但效率低,而且不是绝对的安全,比如如果修饰的是数组,那么对数组的读写是安全的,但如果是操作数组进行添加移除其中对象的还,就不保证安全了。
3.引用计数:
- retain/strong
- assign:修饰基本数据类型,修饰对象类型时,不改变其引用计数,会产生悬垂指针,修饰的对象在被释放后,assign指针仍然指向原对象内存地址,如果使用assign指针继续访问原对象的话,就可能会导致内存泄漏或程序异常
- weak:不改变被修饰对象的引用计数,所指对象在被释放后,weak指针会自动置为nil
- copy:分为深拷贝和浅拷贝
浅拷贝:对内存地址的复制,让目标对象指针和原对象指向同一片内存空间会增加引用计数
深拷贝:对对象内容的复制,开辟新的内存空间
image.png
可变对象的copy和mutableCopy都是深拷贝
不可变对象的copy是浅拷贝,mutableCopy是深拷贝
copy方法返回的都是不可变对象
分类、扩展、代理、通知、KVO、KVC、属性
参考链接:https://juejin.im/post/6844904039008698381
@property (nonatomic, copy) NSMutableArray * array;这样写有什么影响?
因为copy方法返回的都是不可变对象,所以array对象实际上是不可变的,如果对其进行可变操作如添加移除对象,则会造成程序crash
frame 和 bounds 的区别是什么?
frame相对于父视图,是父视图坐标系下的位置和大小。bounds相对于自身,是自身坐标系下的位置和大小。
frame以父控件的左上角为坐标原点,bounds以自身的左上角为坐标原点
什么时候会发生「隐式动画」?
当改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来.相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作,这就是隐式动画
UIView 和 CALayer 之间的关系?
UIView显示在屏幕上归功于CALayer,通过调用drawRect方法来渲染自身的内容,调节CALayer属性可以调整UIView的外观,UIView继承自UIResponder,CALayer不可以响应用户事件
UIView是iOS系统中界面元素的基础,所有的界面元素都继承自它。它内部是由Core Animation来实现的,它真正的绘图部分,是由一个叫CALayer(Core Animation Layer)的类来管理。UIView本身,更像是一个CALayer的管理器,访问它的根绘图和坐标有关的属性,如frame,bounds等,实际上内部都是访问它所在CALayer的相关属性
UIView有个layer属性,可以返回它的主CALayer实例,UIView有一个layerClass方法,返回主layer所使用的类,UIView的子类,可以通过重载这个方法,来让UIView使用不同的CALayer来显示
+[UIView animateWithDuration:animations:completion:] 内部大概是如何实现的?
animateWithDuration:这就等于创建一个定时器
animations:这是创建定时器需要实现的SEL
completion:是定时器结束以后的一个回调block
iOS 的沙盒目录结构是怎样的?
Application:存放程序源文件,上架前经过数字签名,上架后不可修改
Documents:常用目录,iCloud备份目录,存放数据,这里不能存缓存文件,否则上架不被通过
Library
Caches:存放体积大又不需要备份的数据,SDWebImage缓存路径就是这个
Preference:设置目录,iCloud会备份设置信息
tmp:存放临时文件,不会被备份,而且这个文件下的数据有可能随时被清除的可能
Push Notification推送 是如何工作的?
推送通知分为两种,一个是本地推送,一个是远程推送
本地推送:不需要联网也可以推送,是开发人员在APP内设定特定的时间来提醒用户干什么
从图中可以很清楚的看出来推送的原理主要分为以下几步:
1.由App向iOS设备发送一个注册通知,用户需要同意系统发送推送。
2.iOS向APNs远程推送服务器发送App的Bundle Id和设备的UDID。
3.APNs根据设备的UDID和App的Bundle Id生成deviceToken再发回给App。
4.App再将deviceToken发送给远程推送服务器(自己的服务器), 由服务器保存在数据库中。
5.当自己的服务器想发送推送时, 在远程推送服务器中输入要发送的消息并选择发给哪些用户的deviceToken,由远程推送服务器发送给APNs。
6.APNs根据deviceToken发送给对应的用户。
· APNs 服务器就是苹果专门做远程推送的服务器。
·deviceToken是由APNs生成的一个专门找到你某个手机上的App的一个标识码。
· deviceToken 可能会变,如果你更改了你项目的bundle Identifier或者APNs服务器更新了可能会变。
是一个与线程相关的机制。
一个RunLoop就是一个时间处理的循环,用来不停的调度工作以及处理输入时间。使用runloop的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。runloop的设计是为了减少cpu无谓的空转;
进程与线程的特点与区别?
1)进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。
2)进程和线程的主要差别在于它们是不同的操作系统资源管理方式。 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。 共享变量可以实现线程调用另一个线程。
25匹马,现有5条跑道,没有计时器,要找出最快3匹马,至少要跑几场?
至少跑7场,
对25匹马随机分成5个组(A,B,C,D,E,F),每组跑一场,记录每一匹马在当前组中名次(第1名,第2名,第3名)(跑了五场)
从各个组中选取名次为第一名的马组成一组,跑一场,记录名次(第六场),本组第1名则确定了25匹马中最快的一匹马
选取第六场中名次为第1名的所在原来组名次为第2、3名马,选取第六场中名次为第2名的所在原来组名次第1、2名马(它自己+第2名),选取第六场中名次为第3名所在原来组名次第1名的马(它自己),组成一组,跑一场,记录名次(第七场),本场的第1、2名就是25匹马中最快的第2、3名
8瓶液体,其中1瓶有毒药,毒药1小时后至死,请问最快找出毒药,需要几只老鼠?
1只老鼠可以断定2瓶液体,2^3=8,所以需要3只老鼠即可,
对液体进行编号,001,010,011,100,101,110,111
给1号老鼠喂编码个位数上是1的液体(001,011,101,111),
给2号老鼠喂编码十位数上是1的液体(010,011,110,111),
给3号老鼠喂编码百位数上是1的液体(100,101,110,111),
1小时后,
如果老鼠全活, 8号液体有毒,
如果全都死,7号液体有毒,
如果1号死,2,3活, 1号液体有毒
如果2号死, 1,3活,2号液体有毒
如果3号死,1,2活, 4号液体有毒
如果1,2号死,3活, 3号液体有毒
如果1,3号死,2活, 5号液体有毒
如果2,3号死,1活, 6号液体有毒
UIButton不响应事件的原因
按钮添加到了一个没有开启用户交互的父View上,例如UIImageView,这时候开启父试图的交互 view.userInteractionEnabled = YES
按钮自身被遮挡,点击的时候根本就没有点击到button,而是他上面一层View,自然就不会响应
按钮的frame超出了父视图的frame,这个是最容易出现的,按钮的frame必须在父视图的frame内部点击才有效
注意:超出父视图不响应,但在父视图里的会响应。
webview的优化,webview为什么会加载白屏
webview加载H5页面过程
初始化 webview -> 请求页面 -> 下载数据 -> 解析HTML -> 请求 js/css 资源 -> dom 渲染 -> 解析 JS 执行 -> JS 请求数据 -> 解析渲染 -> 下载渲染图片
优化
前端优化
合并资源,减少http请求数
加快请求速度:预解析DNS,减少域名数,并行加载,CDN 分发
客户端优化
离线包方案:把一个个功能模块的所有相关页面和资源打包下发
提前初始化 webview,提供一个全局的webview。添加webview 池重用池,可以用两个或多个 webview 重复使用
预加载数据
总结:缓存/预加载/并行,缓存一切网络请求,尽量在用户打开之前就加载好所有内容,能并行做的事不串行做。
app启动优化
App总启动时间 = pre-main耗时 + main耗时,可以通过添加环境变量:DYLD_PRINT_STATISTICS
启动流程
pre-main:系统dylib(动态链接库)和自身App可执行文件的加载
main: main方法执行之后到AppDelegate类中的didFinishLaunchingWithOptions方法执行结束前这段时间,主要是构建第一个界面,并完成渲染展示
pre-main优化
减少类的数量,删除无用代码(未被调用的静态变量、类和方法,抽象重复代码
+load方法中做的事情延迟到+initialize中,或者在+load中做的事情不宜花费过多时间
减少不必要的framework,或者优化已有的framework
main阶段优化
didFinishLaunchingWithOptions,日志、统计,某个功能的预加载,第三方SDK初始化,可以采用懒加载,或者分阶段启动
首页启动渲染的页面优化,对于viewDidLoad以及viewWillAppear方法中尽量去尝试少做,晚做,不做,或者采用异步的方式去做。不使用xib或者storyboard,直接使用代码
谈谈你对KVC的理解?
KVC|可以通过(key)直接访问对象的属性,或者给对象的属性赋值,这样可以在运行时动态的访问或修改对象的属性当调用setValue:属性值forKey: @"name"的代码时,,底层的执行机制如下∶
1、程序优先调用set key>:属性值方法,代码通过(setter方法|完成设置。注意,这里的<key是指成员变量名,首字母大小写要符合(KVC)的命名规则,下同
2、如果没有找到setName:|方法,KVC机制会检查(+(BOOL)accessInstancelariablesDirectly方法有没有返回VYES ,默认该方法会返回(VEs ,如果你重写了该方法让其返回NO的话,那么在这一步(KC 会执行(setValue: forUndefinedKey:)方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为<kep)的成员变量,无论该变量是在类接口处定义,还是在类实现处定义,也无论用了什么样的访问修饰符,只在存在以<key命名的变量,KVC都可以对该成员变量赋值。
3、如果该类即没有set<key>:方法,也没有(_<key>成员变量,KVC机制会搜索(_is<Key>)的成员变量。
4、和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值。
谈谈对性能优化的看法,如何做?
从用户体验出发:
1、程序logging不要太长、
2、相同数据不做重复获取、
3、昂贵资源要重用(cell、sqlite、date),
4、良好的编程习惯和程序设计:选择正确的集合对象和算法来进行编程、选择适合的数据存储格式(plist、SQLite)、优化SQLite查询语句
5、数据资源方面的优化(缓存和异步加载)
解决方案:
• 能够发现问题
• 利用log或工具分析问题原因
• 假设问题原因
• 改进代码和设计
oc中可修改和不可以修改类型?
答:可修改不可修改的集合类,这个我个人简单理解就是可动态添加修改和不可动态添加修改
一样。比如NSArray和NSMutableArray,前者在初始化后的内存控件就是固定不可变的,后者可
以添加等,可以动态申请新的内存空间
说说响应链
当事件发生的时候,响应链首先被发送给第一个响应者(往往是事件发生的视图,也就是用户触摸屏幕的地方)。事件将沿着响应者链一直向下传递,直到被接受并作出处理。一般来说,第一响应这是个视图对象或者其子类,当其被触摸后事件就交由它处理,如果他不处理,时间就会被传递给视图控制器对象;
UIViewController(如果存在),然后是它的父视图对象(superview),以此类推知道顶层视图。接下来会沿着顶层视图(top view)到窗口(UIwindow 对象) 再到程序的(UIApplication对象),如果整个过程都没有响应这个事件,则该事件被丢弃,一般情况下,在响应链中只要有对象处理事件,事件就会被传递 ;
典型的响应路线图如: First Responser --> The Window -->The Applicationn --> App Delegate
熟悉哪些设计模式?
MVC 模式、单例模式、MVVM 模式、代理模式、工厂模式,观察者模式
1.mvc 模式:model 保存应用模型和处理数据逻辑、view 负责 model 数据和交互控件的显示、
controller 负责 model 和 View 之间的通讯
2.单例模式:用一个静态方法返回这个类的对象。这个对象是全局唯一的。整个项目里面只开
辟一块内层,比如登录之后获取的用户数据存储、NSNotificationcenter、NSUserdefaults,
sharedApplication。
缺点:这块内层直到项目推出时才能释放。
优势:使用简单,延时求值,易于跨模块, 便于资源共享控制,方便传值和修改单例的属性
谈谈你对MVC的理解?为什么要用MVC?
MVC是Model-VIew-Controller,就是模型-视图-控制器, MVC把软件系统分为三个部分:Model,View,Controller。
Cocoa中所有的控件、窗口等都继承自 UIView,对应MVC中的 V。UIView及其子类主要负责UI的实现,而UIView所产生的事件都可以采用委托的方式,交给UIViewController实现。对于不同的 UIView,都有相应的UIViewController 对应MVC中的C。比如在iPhone OS上常用的UITableView,它所对应的Controller就是UITableViewController。至于MVC中的M,那需要根据用 户自己的需求来实现了。
MVC可以帮助确保帮助实现程序最大程度的可重用性。各MVC元素彼此独立运作,通过分开这些元素,可以构建可维护,可独立更新的程序组建。
简述NotificationCenter、KVC、KVO、Delegate?并说明它们之间的区别?
Notification:观察者模式,controller向defaultNotificationCenter添加自己的 notification,其他类注册这个notification就可以收到通知,这些类可以在收到通知时做自己的操作(多观察者默认随机顺序发通知给 观察者们,而且每个观察者都要等当前的某个观察者的操作做完才能轮到他来操作,可以用NotificationQueue的方式安排观察者的反应顺序,也 可以在添加观察者中设定反映时间,取消观察需要在viewDidUnload 跟dealloc中都要注销);
KVC键值编码,可以直接通过字符串的名字(key)来间接访问属性的机制,而不是通过调用getter和setter方法访问;
KVO:观测指定对象的属性,当指定对象的属性更改之后会通知相应的观察者;
delegate:一对一,delegate遵循某个协议并实现协议声明的方法;
怎么用 copy 关键字?
NSString、NSArray、NSDictionary 等等经常使用 copy 关键字,是因为他们有对应的可变类型:
NSMutableString、NSMutableArray、NSMutableDictionary;
block 也经常使用 copy 关键字,具体原因见官方文档:Objects Use Properties to Keep Track
of Blocks:
MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻ᨀ醒我们:编译器自动对 block 进行了 copy操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。
copy 此特质所表达的所属关系与 strong 类似。然而设置方法并不保留新值,而是将其“拷 贝” (copy)。 当属性类型为 NSString 时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString 类的实例。这个类是 NSString 的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。
谈谈你对多线程开发的理解?
好处:
1.使多线程可以把占据时间长的程序中的任务放到后台去处理
2.用户界面可以更加吸引力,这样比如用户点击了一个按钮去触发某些事件的处理,
可以弹出一个进度条来显示处理的进度
3.程序的运行速度可能加快
缺点:
1.如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
2.更多的线程需要更多的内存空间。
ios中有几种实现多线程的方法?
多线程参考链接:https://www.jianshu.com/p/f28a50f72bb1
1.NSThread
2.NSOperationQueue
3.GCD Thread
是这三种范式里面相对轻量级的,但也是使用起来最负责的,你需要自己管理thread的生命周期,线程之间的同步。线程共享同一应用程序的部分内存空间, 它们拥有对数据相同的访问权限。你得协调多个线程对同一数据的访问,一般做法是在访问之前加锁,这会导致一定的性能开销。在 iOS 中我们可以使用多种形式的 thread: Cocoa threads: 使用NSThread 或直接从 NSObject 的类方法 performSelectorInBackground:withObject: 来创建一个线程。如果你选择thread来实现多线程,那么 NSThread 就是官方推荐优先选用的方式。
Cocoa operations是基于 Obective-C实现的,类 NSOperation 以面向对象的方式封装了用户需要执行的操作,我们只要聚焦于我们需要做的事情,而不必太操心线程的管理,同步等事情,因为NSOperation已经为我 们封装了这些事情。 NSOperation 是一个抽象基类,我们必须使用它的子类。iOS 提供了两种默认实现:NSInvocationOperation 和 NSBlockOperation。
Grand Central Dispatch (GCD): iOS4 才开始支持,它提供了一些新的特性,以及运行库来支持多核并行编程,它的关注点更高:如何在多个 cpu 上提升效率。
具体实施
这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。
三种方式的优缺点介绍:
1)NSThread:
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销
NSThread实现的技术有下面三种:
一般使用cocoa thread 技术。
(一)NSThread的使用*
NSThread 有两种直接创建方式:
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
- (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
参数的意义:
selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。
target :selector消息发送的对象
argument:传输给target的唯一参数,也可以是nil
第一种方式会直接创建线程并且开始运行线程,第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息
不显式创建线程的方法:
用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
参考链接:https://www.jianshu.com/p/686dbf4bbb52
(二) NSOperation
优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation 相关的类是 NSOperation ,NSOperationQueue。
NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。
创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。
NSOperation 实现多线程的使用步骤分为三步:
创建操作:先将需要执行的操作封装到一个 NSOperation 对象中。
创建队列:创建 NSOperationQueue 对象。
将操作加入到队列中:将 NSOperation 对象添加到 NSOperationQueue 对象中。
之后呢,系统就会自动将 NSOperationQueue 中的 NSOperation 取出来,在新线程中执行操作。
NSOperationQueue 控制串行执行、并发执行
之前我们说过,NSOperationQueue 创建的自定义队列同时具有串行、并发功能,上边我们演示了并发功能,那么他的串行功能是如何实现的?
这里有个关键属性 maxConcurrentOperationCount,叫做最大并发操作数。用来控制一个特定队列中可以有多少个操作同时参与并发执行。
注意:这里 maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。
最大并发操作数:maxConcurrentOperationCount
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
参考链接:https://www.jianshu.com/p/4b1d77054b35
(三)GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。现在的iOS系统都升级到7了,所以不用担心该技术不能使用。
使用 GCD 有很多好处啊,具体如下:
- GCD 可用于多核的并行运算;
- GCD 会自动利用更多的 CPU 内核(比如双核、四核);
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
GCD 拥有以上这么多的好处,而且在多线程中处于举足轻重的地位。那么我们就很有必要系统地学习一下 GCD 的使用方法。
2. GCD 任务和队列
学习 GCD 之前,先来了解 GCD 中两个核心概念:『任务』 和 『队列』。
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:『同步执行』 和 『异步执行』。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
-
同步执行(sync):
- 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
- 只能在当前线程中执行任务,不具备开启新线程的能力。
-
异步执行(async):
- 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
- 可以在新的线程中执行任务,具备开启新线程的能力。
举个简单例子:你要打电话给小明和小白。
『同步执行』 就是:你打电话给小明的时候,不能同时打给小白。只有等到给小明打完了,才能打给小白(等待任务执行结束)。而且只能用当前的电话(不具备开启新线程的能力)。
『异步执行』 就是:你打电话给小明的时候,不用等着和小明通话结束(不用等待任务执行结束),还能同时给小白打电话。而且除了当前电话,你还可以使用其他一个或多个电话(具备开启新线程的能力)。
注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关(下面会讲)。
队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。队列的结构可参考下图:
在 GCD 中有两种队列:『串行队列』 和 『并发队列』。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。
-
串行队列(Serial Dispatch Queue):
- 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)
-
并发队列(Concurrent Dispatch Queue):
- 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列 的并发功能只有在异步(dispatch_async)方法下才有效。
参考链接:https://www.jianshu.com/p/2d57c72016c6
ViewController生命周期
按照执⾏顺序排列:
- initWithCoder:通过nib⽂件初始化时触发。
- awakeFromNib:nib文件被加载的时候,会发生一个awakeFromNib的消息到nib文件中的每个对象。
- loadView:开始加载视图控制器⾃带的view。
- viewDidLoad:视图控制器的view被加载完成。
- viewWillAppear:视图控制器的view将要显示在window上。
- updateViewConstraints:视图控制器的view开始更新AutoLayout约束。
- viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置。
- viewDidLayoutSubviews:视图控制器的view已经更新视图的位置。
- viewDidAppear:视图控制器的view已经展示到window上。
- viewWillDisappear:视图控制器的view将要从window上消失。
- viewDidDisappear:视图控制器的view已经从window上消失。
struct、Class的区别
class可以继承,struct不可以
class是引用类型,struct是值类型
struct在function里修改property时需要mutating关键字修饰
NSArray与NSSet的区别?
NSArray内存中存储地址连续,而NSSet不连续
NSSet效率高,内部使用hash查找;NSArray查找需要遍历
NSSet通过anyObject访问元素,NSArray通过下标访问
NSHashTable与NSMapTable?
NSHashTable是NSSet的通用版本,对元素弱引用,可变类型;可以在访问成员时copy
NSMapTable是NSDictionary的通用版本,对元素弱引用,可变类型;可以在访问成员时copy
(注:NSHashTable与NSSet的区别:NSHashTable可以通过option设置元素弱引用/copyin,只有可变类型。但是添加对象的时候NSHashTable耗费时间是NSSet的两倍。
NSMapTable与NSDictionary的区别:同上)
属性关键字assign、retain、weak、copy
assign:用于基本数据类型和结构体。如果修饰对象的话,当销毁时,属性值不会自动置nil,可能造成野指针。
weak:对象引用计数为0时,属性值也会自动置nil
retain:强引用类型,ARC下相当于strong,但block不能用retain修饰,因为等同于assign不安全。
strong:强引用类型,修饰block时相当于copy。