iOS技术4。iOS 进阶知识—回顾

iOS 基础知识(面试)深度理解-- 不断更新

2016-06-28  本文已影响631人  Civel_Xu

什么是KVC,什么是KVO,他们之间关系.底层实现

键值编码 Key-Value-Coding(KVC)

键值编码是一种使用字符串来标识属性,间接访问对象的属性,而不是通过调用存取方法,直接或通过实例变量访问的机制,非对象类型的变量将被自动封装或者解封成对象,很多情况下会简化程序代码;KVC的缺点:一旦使用 KVC 你的编译器无法检查出错误,即不会对设置的键、键路径进行错误检查,且执行效率要低于合成存取器方法和自定的 setter 和 getter 方法。因为使用 KVC 键值编码,它必须先解析字符串,然后在设置或者访问对象的实例变量。

实现分析

KVC运用了一个isa-swizzling技术。isa-swizzling就是类型混合指针机制。KVC主要通过isa-swizzling,来实现其内部查找定位的。isa指针,如其名称所指,(就是is a kind of的意思),指向维护分发表的对象的类。该分发表实际上包含了指向实现类中的方法的指针,和其它数据。

比如说如下的一行KVC的代码:
[site setValue:@"sitename" forKey:@"name"];
就会被编译器处理成:
SEL sel = sel_get_uid ("setValue:forKey:");
IMP method = objc_msg_lookup (site->isa,sel);
method(site, sel, @"sitename", @“name");

一个对象在调用setValue的时候,
(1)首先根据方法名找到运行方法的时候所需要的环境参数。
(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。
(3)再直接查找得来的具体的方法实现。

键值观察 Key-Value Observing (KVO)

键值观察机制是一种能使得对象获取到其他对象属性变化的通知 ,极大的简化了代码。
实现 KVO 键值观察模式,被观察的对象必须使用 KVC 键值编码来修 改它的实例变量,这样才能被观察者观察到。因此,KVC是KVO的基础。

实现分析 :
KVO的实现是基于runtime运行时的
1、当一个object有观察者时,动态创建这个object的类的子类在addObserver:forKeyPath:options:context:之后。对象的isa变为了子类派生类NSKVONotifying_XX。实际上是对象p的isa即NSKVONotifying_XX类的setAge方法,并非原类的setAge方法。
2、对于每个被观察的property,重写其set方法
3、在重写的set方法中调用- willChangeValueForKey:和- didChangeValueForKey:通知观察者
4、当一个property没有观察者时,删除重写的方法
5、当没有observer观察任何一个property时,删除动态创建的子类

什么是block,delegate,通知中心Notification,使用区别

block
block被ObjC看成是对象,它封装了一段代码,这段代码可以在任何时候执行。Blocks可以作为函数参数或者函数的返回值,而其本身又可以带输入参数或返回值。它和传统的函数指针很类似,但是有区别:blocks是inline的,并且它对局部变量是只读的。block类似一些其它Web编程语言中的“匿名函数”。在objc中通常使用block实现代理方法实现的功能,也就是回调。使用代理需要设置代理的数据接收者,而且代理方法是被分离开来处理的,block可以将这些分离的代码放到一个代码块中。

delegate
delegate,又称委托或者代理,它是一种设计模式.delegate主要是用于两个对象之间的交互,并且解除两个通信对象的耦合性,iOS大量使用代理模式,主要是用于视图与使用对象之间的通信交互.

官方文档翻译解释:
代理是一种简单而功能强大的设计模式,这种模式用于一个对象“代表”另外一个对象和程序中其他的对象进行交互。 主对象(这里指的是delegating object)中维护一个代理(delegate)的引用并且在合适的时候向这个代理发送消息。这个消息通知“代理”主对象即将处理或是已经处理完了某一个事件。这个代理可以通过更新自己或是其它对象的UI界面或是其它状态来响应主对象所发送过来的这个事件的消息。或是在某些情况下能返回一个值来影响其它即将发生的事件该如何来处理。代理的主要价值是它可以让你容易的定制各种对象的行为。注意这里的代理是个名词,它本身是一个对象,这个对象是专门代表被代理对象来和程序中其他对象打交道的。委托是objC中使用非常频繁的一种设计模式,它的实现与协议的使用是分不开的.

Notification
通知中心概述:通知中心实际上是在程序内部提供了消息广播的一种机制。通知中心不能在进程间进行通信。实际上就是一个二传手,把接收到的消息,根据内部的一个消息转发表,来将消息转发给需要的对象。通知中心是基于观察者模式的,它允许注册、删除观察者。
一个NSNotificationCenter可以有许多的通知消息NSNotification,对于每一个NSNotification可以有很多的观察者Observer来接收通知。

使用区别

delegate与block一般用于两个对象1对1之间的通信交互、delegate需要定义协议方法,代理对象需要实现协议方法并且需要建立代理关系才可以实现通信。 block更加简洁,不需要定义繁琐的协议方法,但是如果通信时间比较多的话,建议使用delgate。 Notfication主要用于1对多的通信,而且通信对象之间不需要建立关系,但是使用通知,代码的可读性差。

内存管理MRC、ARC管理机制的区别

iOS中采用的是引用计数的机制来管理内存,如果一块内存区域的引用计数不为0,那么就说明有对象持或者是在使用这一块内存,如果引用计数为0的话那么说明这块内存没有对象使用,可以被系统回收掉。iOS借助于引用计数的增减来辅助我们进行内存的申请和释放.

内存管理的原则:
1、自己创建的对象,自己可以持有(比如以alloc、new、copy、mutableCopy开头的方法可以创建对象);
2、不是自己创建的对象也可以持有,通过retain;
3、自己持有的对象在不需要使用的时候要负责释放、释放可以通过release或者是autorelease进行释放,
4、不是自己持有的对象不能进行释放,比如便利构造器得到的对象。

在内存管理的过程中我们需要谨记的原则就是我们造成的引用计数的增加和我们造成的引用计数的减少要保持一致。在我们使用属性的过程中,要注意不同的语义控制(assign、retain、copy)的setter方法实现的不同,对于retain和copy来说,他们的内部实现都是先把旧值release和把新值retain。另外对于发送autorelease消息的对象会被加到最近的自动释放池中,当自动释放池释放的时候,会给里面的所有对象发送一次release消息。iOS5.0之后苹果推出了ARC、ARC是编译器的特性,不是OC的语言特性,是编译器在静态编译的基础上(command + shift + B),编译器在合适的地方给我们加了retain、release、autorelease这些代码,不用我们自己去手动写这些代码了。ARC中属性的关键字是strong和weak,其中strong和MRC下的retain作用相同,都是持有一个对象,weak和MRC下的assigin类型,是一个弱引用,不持有一个对象,但是weak只能修饰对象类型,不能修饰基本类型,并且weak会在指向的对象被销毁的时候指针自动置nil。

#import、#include、@class、#import<>和#import”"的区别

import

import<> 和 import”"

iOS assign,weak,strong,copy ,atomic, nonatomic详解

assign 与weak区别

strong 与copy的区别

nonatomic/atomic

OC之面向对象的三大特征

封装是对象和类概念的主要特性。它是隐藏内部实现,稳定外部接口,可以看作是“包装”。封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
好处:使用更简单变量更安全可以隐藏内部实现细节开发速度加快
OC中一个类可以继承另一个类,被继承的类成为超类(superclass),继承的类是子类(childclass),可以直接拥有父类中所有非私有成员(相关实例变量)、方法。继承的实现在接口中使用符号“:”。
举个例子:@interfaceStudent:NSObject{}不过大家要注意的是:属性封装实例变量,方法封装具体实现代码,类封装属性和方法。子类可继承父类中的方法,还可重写父类方法。

多态性(polymorphism)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。不同对象以自己的方式响应相同的消息的能力叫做多态。意思就是假设生物类(life)都用有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。也就是不同的对象以自己的方式响应了相同的消息(响应了eat这个选择器)。
实现多态,有二种方式,覆盖,重载。
1)覆盖:是指子类重新定义父类的虚函数的做法。
2)重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

面向对象编程(OOP)语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee是一个人,Manager也是一个人,因此这两个类都可以继承Person类。但是Leg类却不能继承Person类,因为腿并不是一个人。

小结:封装可隐藏实现细节,,使代码模块化;继承可扩展已存在的代码模块(类);它们最终需要的结果(代码重用)。多态是为了实现另一个目的(接口重用)。多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

事件响应者链的概念(responder chain);

响应者链表示一系列的响应者对象.事件被交给由第一响应者对象处理,如果第一响应者不处理,事件被沿着响应者链向上传递,交给下一响应者(next responder).一般来说,第一响应者是视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视图控制器对象(如果存在),然后就是它的父视图(superView)对象(如果存在),以此类推,直到顶层视图.接下来会沿着顶层视图(top view)到窗口(UIWindow对象)再到程序(UIApplication对象).如果整个过程都没有响应这个事件,该事件就会被丢弃.一般情况下,在响应者中只要由对象处理事件,事件就停止传递.但有时候可以在视图扥响应方法中根据一些条件判断来决定是否需要继续传递事件.

请简述对MVC / MVVM 设计模式的理解

MVC设计模式

Model:负责存储、定义、操作数据;
View:负责呈现画面以及与用户进行操作交互
Controller:Model和View的协调者,Controller把Model中的数据拿过来给View用。
Controller可以直接与Model和View进行通信,而View不能和Controller直接通信。View与Controller通信需要利用代理协议的方式,当有数据更新时,Model也要与Controller进行通信,这个时候就要用Notification和KVO,这个方式就像广播一样,Model发信号,Controller设置监听接收信号,当有数据更新时就发信号给Controller,Model和View不能直接进行通信,这样会违背MVC设计模式。

如何理解MVVM设计模式

ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层。
View层,就是ViewController层,他的任务就是从ViewModel层获取数据,然后显示。

请简述HTTP协议中get请求和post请求的区别,同步请求和异步请求的区别

get请求

参数在地址后拼接,进行请求数据,不安全(因为所有参数都拼接在地址后面),不适合传输大量数据(长度限制,为256个字节)。get提交、请求的数据会附在URL之后,即把数据放置在HTTP协议头中。以分割URL和传输数据,多个参数用&连接。如果数据是英文字母或数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密。

post请求

参数在请求数据区放着,相对get请求更安全,并且数据大小没有限制(1G)。把提交的数据放置在HTTP包的包体中,转换成NSData进行传输。
get提交的数据会在地址栏显示出来,而post提交,地址栏不会改变。

传输数据的大小:

get请求时,传输数据就会受到URL长度限制,POST由于不是通过URL传输,理论上不受限。

同步请求/异步请求

多线程编程NSThread/NSoperationQueue/NSObject/GCD 区别

单线程
只有一个线程的程序即为单线程.该线程被称为主线程.在程序一运行的时候就存在了.

问题:
因为只有一个线程,所有代码的执行只能从上往下顺序执行.如果有耗时的操作(网络请求,数据解析等),会造成界面假死,影响用户体验/
解决:
将一些耗时的操作放在子线程中去执行,主线程只用来UI的展示和刷新;

多线程
除了主线程外,还存在其他线程的程序即为多线程程序

注意事项:
所有跟UI相关的操作都必须放在主线程中;

问题:
既然多线程程序有这么多的好处,该如何开辟子线程?

解决方案:
iOS中可以使用NSThread/NSoperationQueue/NSObject/GCD来开辟子线程.

问题:
无论开辟了多少子线程,最终的目的都要回到主线程刷新UI,那么该如何从子线程回主线程?

解决方案:
1.使用performSelectorOnMainThread方法;
2.使用GCD中dispatch_async(dispath_get_mainQueue(),);

问题:
既然多线程可以解决界面假死现象,那么是不是开辟的线程越多越好?
答:
不是。开辟子线程需要消耗内存,而且不管开辟了多少子线程。最终都是要回到主线程的。所以开辟的子线程越多对于CPU性能的消耗越大。所以根据需要开辟适当的线程。

问题:
如何让实现线程同步(里面任务一个个执行)
答:
1.GCD里面使用串行队列.
2.NSOperationQueue里面当把最大并发数发送设置1的时候,也可以实现线程同步;

NSObject

@interface NSObject (NSThreadPerformAdditions)
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);

NSThread:
当需要进行一些耗时操作时会把耗时的操作放到线程中。线程同步:多个线程同时访问一个数据会出问题,NSlock、线程同步块、@synchronized(self){}。

NSThread开辟子线程的方式有两种

  /**
   *   第一种方式
   * - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)
   *  @param target   目标
   *  @param selector 方法(如果方法有参数,那么参数即为object传入的参数)
   *  @param object 参数
   *
   *  @return  NSThread对象
   *  注意事项:需要手动调用start方法
   */
 /**
     *  第二种方法(NSThread类方法)
     *
   * + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
     *  @param SEL 方法(如果方法有参数,那么参数即为object传入的参数)
     *
     *  自动开启线程,需手动取消
     */

使用事项:
使用NSThread开辟的子线程中需要添加@autoreleasepool来释放在对应子线程中开辟的内存,其实在主线程中也设有@autoreleasepool.

NSOperationQueue

NSOperationQueue操作队列(不需考虑线程同步问题)。编程的重点都放在main里面,使用NSInvocationOperation、NSBlockOperation自定义Operation。创建一个操作绑定相应的方法,当把操作添加到操作队列中时,操作绑定的方法就会自动执行了,当把操作添加到操作队列中时,默认会调用main方法。

NSOPeration是系统提供的一个用来封装任务的抽象类.

GCD

GCD(`Grand Central Dispatch)宏大的中央调度,串行队列、并发队列、主线程队列;

串行队列两种:

并行队列分为两种:

总结:
在真正开发的过程中,一般会使用自己创建的串行队列或者是系统提供好的并行队列;

NSOPerationQueue 与 GCD 的区别与选用

GCD 技术是一个轻量的,底层实现隐藏的神奇技术,我们能够通过GCD和block轻松实现多线程编程,有时候,GCD相比其他系统提供的多线程方法更加有效,当然,有时候GCD不是最佳选择,另一个多线程编程的技术 NSOprationQueue 让我们能够将后台线程以队列方式依序执行,并提供更多操作的入口,这和 GCD 的实现有些类似。
这种类似不是一个巧合,在早期,MacOX 与 iOS 的程序都普遍采用Operation Queue来进行编写后台线程代码,而之后出现的GCD技术大体是依照前者的原则来实现的,而随着GCD的普及,在iOS 4 与 MacOS X 10.6以后,Operation Queue的底层实现都是用GCD来实现的。

那这两者直接有什么区别呢?

  1. GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;
  2. 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);
  3. NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
  4. 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;
  5. 在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;
  6. 我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

总的来说,Operation queue 提供了更多你在编写多线程程序时需要的功能,并隐藏了许多线程调度,线程取消与线程优先级的复杂代码,为我们提供简单的API入口。从编程原则来说,一般我们需要尽可能的使用高等级、封装完美的API,在必须时才使用底层API。但是我认为当我们的需求能够以更简单的底层代码完成的时候,简洁的GCD或许是个更好的选择,而Operation queue 为我们提供能更多的选择。

NSOperationQueue和GCD的区别,以及在什么场合下使用

  1. GCD是纯C语言的API 。NSOperationQueue是基于GCD的OC的封装。
  2. GCD只支持FIFO队列,NSOperationQueue可以方便设置执行顺序,设置最大的并发数量。
  3. NSOperationQueue可是方便的设置operation之间的依赖关系,GCD则需要很多代码。
  4. NSOperationQueue支持KVO,可以检测operation是否正在执行(isExecuted),是否结束(isFinished),是否取消(isCanceled)
  5. GCD的执行速度比NSOperationQueue快。

使用场合

iOS中的沙盒机制

iOS应用程序只能对自己创建的文件系统读取文件,这个独立、封闭、安全的空间,叫做沙盒。每个ios应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他应用的文件系统隔离。它一般存放着程序包文件(可执行文件)、图片、音频、视频、plist文件、sqlite数据库以及其他文件。每个应用程序都有自己的独立的存储空间(沙盒)一般来说应用程序之间是不可以互相访问的,在ios8中已经开放访问,模拟器沙盒的位置路径:/User/userName/Library/Application Support/iPhone Simulator;当我们创建应用程序时,在每个沙盒中含有三个文件,分别是Document、Library(下面有Caches和Preferences目录)和temp。
应用程序包:包含所有的资源文件和可执行文件。

对Document、Library(下面有Caches和Preferences目录)和temp做简单介绍

  1. Document:一般需要持久的数据都放在此目录中,可以在当中添加子文件夹,iTunes备份和恢复的时候,会包括此目录。
  2. Library:设置程序的默认设置和其他状态信息,iTunes会自动备份该目录,例如杂志、新闻、地图应用使用的数据库缓存文件和可下载内容应该保存到这个文件夹。一般可以重新下载或者重新生成的数据应该保存在 <Application_Home>/Library/Caches 目录下面。
    Libaray/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除。一般存放体积比较大,不是特别重要的资源。
    Libaray/PreferencePanes:保存应用的所有偏好设置,ios的Settings(设置)应用会在该目录中查找应用的设置信息,iTunes会自动备份该目录。
  3. temp:创建临时文件的目录,当iOS设备重启时,文件会被自动清除,只是临时使用的

防止iCloud backup

- (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString

{

NSURL* URL= [NSURL fileURLWithPath: filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error);
}
return success;
}

initialize 和 load

在Objective-C中,runtime会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。
共同点:两个方法都只会被调用一次。

上一篇 下一篇

猜你喜欢

热点阅读