OC的简单总结
一.OC的三大特性
封装、继承、多态
1> 什么是多态
多态:不同对象以自己的方式响应相同的消息的能力叫做多态。
由于每个类都属于该类的名字空间,这使得多态称为可能。类定义中的名字和类定义外的名字并不会冲突。类的实例变量和类方法有如下特点:
和C语言中结构体中的数据成员一样,类的实例变量也位于该类独有的名字空间。
类方法也同样位于该类独有的名字空间。与C语言中的方法名不同,类的方法名并不是一个全局符号。一个类中的方法名不会和其他类中同样的方法名冲突。两个完全不同的类可以实现同一个方法。
方法名是对象接口的一部分。对象收到的消息的名字就是调用的方法的名字。因为不同的对象可以有同名的方法,所以对象必须能理解消息的含义。同样的消息发给不同的对象,导致的操作并不相同。
多态的主要好处就是简化了编程接口。它容许在类和类之间重用一些习惯性的命名,而不用为每一个新加的函数命名一个新名字。这样,编程接口就是一些抽象的行为的集合,从而和实现接口的类区分开来。
Objective-C支持方法名的多态,但不支持参数和操作符的多态。
2> OC中如何实现多态
在Objective-C中是通过一个叫做selector的选取器实现的。在Objective-C中,selector有两个意思, 当用在给对象的源码消息时,用来指方法的名字。它也指那个在源码编译后代替方法名的唯一的标识符。 编译后的选择器的类型是SEL有同样名字的方法、也有同样的选择器。你可以使用选择器来调用一个对象的方法。
选取器有以下特点:
* 所有同名的方法拥有同样的选取器
* 所有的选取器都是不一样的
(1) SEL和@selector
选择器的类型是 SEL。@selector指示符用来引用选择器,返回类型是SEL。
例如:
SEL responseSEL;
responseSEL = @selector(loadDataForTableView:);
可以通过字符串来得到选取器,例如:
responseSEL = NSSelectorFromString(@"loadDataForTableView:");
也可以通过反向转换,得到方法名,例如:
NSString *methodName = NSStringFromSelector(responseSEL);
(2) 方法和选取器
选取器确定的是方法名,而不是方法实现。这是多态性和动态绑定的基础,它使得向不同类对象发送相同的消息成为现实;否则,发送 消息和标准C中调用方法就没有区别,也就不可能支持多态性和动态绑定。
另外,同一个类的同名类方法和实例方法拥有相同的选取器。
(3) 方法返回值和参数类型
消息机制通过选取器找到方法的返回值类型和参数类型,因此,动态绑定(例:向id定义的对象发送消息)需要同名方法的实现拥有相 同返回值类型和相同的参数类型;否则,运行时可能出现找不到对应方法的错误。
有一个例外,虽然同名类方法和实例方法拥有相同的选取器,但是它们可以有不同的参数类型和返回值类型。
3> 动态绑定
二.类和对象
1.category
1> 分类 拓展 协议中哪些可以声明属性?
都可以,但分类和协议创建的属性只相当于方法,但是内部没有对成员变量的操作(无法创建成员变量),拓展可以
代理中声明属性,没有实际创建成员变量,相当于声明了属性名对应的访问方法,遵守协议的类需要实现对应的访问器方法,否则运行报错
分类中声明属性,警告提示需要手动实现访问器方法(Swift中叫计算型属性),而分类中不能创建成员变量,可以在手写访问器方法中使用runtime的 objc_setAssociatedObject方法关联对象间接创建属性(静态库添加属性)
拓展里可以声明属性,直接可以使用
2> 继承和类别的区别
1> 使用继承:
1.1> 添加新方法和父类方法一致,但父类方法仍需要使用
1.2> 添加新属性
2> 类别:
2.1> 针对系统提供的一些类,系统本身不提倡继承,因为这些类的内部实现对继承有所限制(NSString initWithFormat继承崩溃)
2.2> 类别可以将自己构建的类中的方法进行分组,对于大型的类,提高可维护性
3> 分类的作用
将类的实现分散到多个不同文件或多个不同框架中。
创建对私有方法的前向引用。
向对象添加非正式协议。
4> 分类的局限性
无法向类中添加新的实例变量,类别没有位置容纳实例变量。
名称冲突,即当类别中的方法与原始类方法名称冲突时,类别具有更高的优先级。类别方法将完全取代初始方法从而无法再使用初始方法。
无法添加实例变量的局限可以使用字典对象解决.
三.Foundation
1.字符串
2.NSArray和NSDictionary
1> iOS遍历数组/字典的方法
数组: for循环 for in enumerateObjectsUsingBlock(正序) enumerateObjectsWithOptions:usingBlock:(多一个遍历选项,不保证顺序)
字典:
1. for(NSString *object in [testDic allValues])
2. for(id akey in [testDic allKeys]){
[sum appendString:[testDic objectForKey:akey]]; }
3. [testDic enumerateKeysAndObjectsUsingBlock:^(idkey,idobj,BOOL*stop) {
[sum appendString:obj]; } ];
速度: 对于数组, 增强for最快,普通for和block速度差不多,增强最快是因为增强for语法会对容器里的元素的内存地址建立缓冲,遍历的时候直接从缓冲中取元素地址而不是通过调用方法来获取,所以效率高.这也是使用增强for时不能在循环体中修改容器元素的原因之一(可以在循环体中添加标记,在循环体外修改元素)
对于字典,allValues最快,allKey和block差不多,原因是allKey需要做objcetForKey的方法
3.NSValue NSNumber
1> 归档视图尺寸,坐标
4.其他
nil Nil null NSNull 的区别
四.关键字
1.@property
一个区分度很大的面试题
考察一个面试者基础咋样,基本上问一个 @property 就够了:
@property 后面可以有哪些修饰符?
线程安全的:
atomic,nonatomic
访问权限的
readonly,readwrite
内存管理(ARC)
assign,strong,weak,copy
内存管理(MRC)
assign,retain,copy
指定方法名称
setter=
getter=
1>readwrite,readonly,assign,retain,copy,nonatomic属性的作用
@property是一个属性访问声明,扩号内支持以下几个属性:
1.1> getter setter
getter=getterName,setter=setterName,设置setter与getter的方法名
1.2> weak assign strong copy
assign 用于非指针变量。用于基础数据类型 (例如NSInteger)和C数据类型(int, float, double, char, 等),另外还有id,其setter方法直接赋值,不进行任何retain操作
weak 用于指针变量,比assign多了一个功能,当对象消失后自动把指针变成nil,由于消息发送给空对象表示无操作,这样有效的避免了崩溃(野指针),为了解决原类型与循环引用问题
strong 用于指针变量,setter方法对参数进行release旧值再retain新值
copy 用于指针变量,setter方法进行copy操作,与retain处理流程一样,先旧值release,再copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。
1、什么情况使用 weak 关键字,相比 assign 有什么不同?比如:
在ARC中,出现循环引用的时候,必须要有一端使用weak,比如:自定义View的代理属性
已经自身已经对它进行一次强应用,没有必要在强引用一次,此时也会使用weak,自定义View的子控件属性一般也使用weak;但b是也可以使用strong
weak当对象销毁的时候,指针会被自动设置为nil,而assign不会* assigin 可以用非OC对象,而weak必须用于OC对象
2、怎么用 copy 关键字?
对于字符串和block的属性一般使用copy
字符串使用copy是为了外部把字符串内容改了,不影响该属性
block使用copy是在MRC遗留下来的,在MRC中,方法内部的block是在在栈区的,使用copy可以把它放到堆区.在ACR中对于block使用copy还是strong效果是一样的
3、这个写法会出什么问题: @property (copy) NSMutableArray *array;
添加,删除,修改数组内的元素的时候,程序会因为找不到对于的方法而崩溃.因为copy就是复制一个不可变NSArray的对象
1.3> readwrite,readonly,设置可供访问级别
1.4> nonatomic,非原子性访问,不加同步,多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。所以约定俗成只在主线程更新UI,防止多线程设置UI属性,出现资源抢夺现象
2> 如何避免循环引用
两个对象相互强引用,都无法release,解决办法为一个使用strong,一个使用assign(weak)
3> delegate的属性为什么使用assign/weak
避免出现循环引用,场景如UITableViewController强引用视图UITableView,而该视图的代理又是控制器,为避免循环引用,让delegate为弱引用
2.copy
1> copy的使用场景
当多个指针指向同一个对象时,为避免一个指针对对象的改动对其他指针的使用产生影响,使用copy来创建对象的副本
如页面间传值使用copy,A向B控制器传属性(属性为自定义对象),为避免因A的属性变化对B的属性产生影响
再如多人开发或封装库,在不明确传入值为可变还是不可变的情况下,使用copy更安全
2> 什么是深拷贝浅拷贝
对于非容器类对象,不可变对象进行copy操作为浅拷贝,引用计数器加1,其他三种为深拷贝
对于容器类对象,基本和非容器类对象一致,但注意其深拷贝是对象本身是对象复制,其中元素仍为指针复制,系统将initWithArray方法归为了元素深拷贝,但其实如果元素为不可变元素,仍为指针复制,使用归解档可以实现真正的深拷贝,元素也是对象拷贝NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject: array]];
3> 字符串什么时候使用copy,strong
属性引用的对象由两种情况,可变和不可变字符串
引用对象不可变情况下,copy和strong一样,copy为浅拷贝
引用对象可变情况下,如果希望属性跟随引用对象变化,使用strong,希望不跟随变化使用copy
4> 字符串所在内存区域
@“abc” 常量区 stringwithformat 堆区
5> mutablecopy和copy @property(copy) NSMutableArray *arr;这样写有什么问题
mutablecopy返回可变对象,copy返回不可变对象
6> 如何让自定义类可以使用copy修饰符
实现协议,重写copyWithZone方法
五.runtime/消息转发机制
1.runtimehttp://www.cocoachina.com/ios/20150715/12540.html
1> 什么是runtime
runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语言API。
在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码, runtime算是OC的幕后工作者,objc_msgSend
2> runtime干什么用,使用场景
runtime是属于OC的底层, 可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)
在程序运行过程中, 动态创建一个类(比如KVO的底层实现) objc_allocateClassPair,class_addIvar,objc_registerClassPair
在程序运行过程中, 动态地为某个类添加属性\方法, 修改属性值\方法(修改封装的框架) objc_setAssociatedObject object_setIvar
遍历一个类的所有成员变量(属性)\所有方法(字典转模型,归解档) class_copyIvarList class_copyPropertyList class_copyMethodList
2.消息机制
1> 消息转发的原理
当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的类,然后在类的调度表(dispatch table)中查找selector。如果无法找到selector,objc_msgSend通过指向父类的指针找到父类,并在父类的调度表(dispatch table)中查找selector,以此类推直到NSObject类。一旦查找到selector,objc_msgSend方法根据调度表的内存地址调用该实现。 通过这种方式,message与方法的真正实现在执行阶段才绑定。
为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的 selector以及继承自父类的selector。查找调度表(dispatch table)前,消息发送系统首先检查receiver对象的缓存。
缓存命中的情况下,消息发送(messaging)比直接调用方法(function call)只慢一点点点点。
2> SEL isa super cmd 是什么
sel: 一种类型,表示方法名称,类似字符串(可互转)
isa:在方法底层对应的objc_msgSend调用时,会根据isa找到对象所在的类对象,类对象中包含了调度表(dispatch table),该表将类的sel和方法的实际内存地址关联起来
super_class:每一个类中还包含了一个super_class指针,用来指向父类对象
_cmd在Objective-C的方法中表示当前方法的selector,正如同self表示当前方法调用的对象实例
IMP定义为 id (*IMP) (id, SEL, …)。这样说来, IMP是一个指向函数的指针,这个被指向的函数包括id(“self”指针),调用的SEL(方法名),再加上一些其他参数.说白了IMP就是实现方法
3> 动态绑定
—在运行时确定要调用的方法
动态绑定将调用方法的确定也推迟到运行时。在编译时,方法的 调用并不和代码绑定在一起,只有在消实发送出来之后,才确定被调用的代码。通过动态类型和动态绑定技术,您的代码每次执行都可以得到不同的结果。运行时因 子负责确定消息的接收者和被调用的方法。运行时的消息分发机制为动态绑定提供支持。当您向一个动态类型确定了的对象发送消息时,运行环境系统会通过接收者 的isa指针定位对象的类,并以此为起点确定被调用的方法,方法和消息是动态绑定的。而且,您不必在Objective-C 代码中做任何工作,就可以自动获取动态绑定的好处。您在每次发送消息时,特别是当消息的接收者是动态类型已经确定的对象时,动态绑定就会例行而透明地发生。
5> 通知的内存管理 线程问题
六.数据传递
1.block
1> block属性为什么用copy?
栈->堆
2> block使用注意什么?
循环引用 修改外部变量
3> block的主要使用场景 ?
动画
数组字典排序遍历
回调状态
错误控制
多线程GCD
4>block原理
block属性是指向结构体的指针,
2.Delegate
1> 什么时候用delegate,什么时候用Notification
delegate针对one-to-one关系,并且reciever可以返回值给sender,notification 可以针对one-to-one/many/none,reciever无法返回值给sender.所以,delegate用于sender希望接受到 reciever的某个功能反馈值,notification用于通知多个object某个事件。
2> delegate和block
block使代码更紧凑,便于阅读,delegate可以设置必选和可选的方法实现,相比block
block可以访存局部变量. 不需要像以前的回调一样,把在操作后所有需要用到的数据封装成特定的数据结构, 你完全可以直接访问局部变量.
3.KVC和KVO
1> 如何调用私有变量 如何修改系统的只读属性 KVC的查找顺序
KVC在某种程度上提供了访问器的替代方案。不过访问器方法是一个很好的东西,以至于只要是有可能,KVC也尽量再访问器方法的帮助下工作。为了设置或者返回对象属性,KVC按顺序使用如下技术:
①检查是否存在-、-is(只针对布尔值有效)或者-get的访问器方法,如果有可能,就是用这些方法返回值;
检查是否存在名为-set:的方法,并使用它做设置值。对于 -get和 -set:方法,将大写Key字符串的第一个字母,并与Cocoa的方法命名保持一致;
②如果上述方法不可用,则检查名为-_、-_is(只针对布尔值有效)、-_get和-_set:方法;
③如果没有找到访问器方法,可以尝试直接访问实例变量。实例变量可以是名为:或_;
④如果仍为找到,则调用valueForUndefinedKey:和setValue:forUndefinedKey:方法。这些方法的默认实现都是抛出异常,我们可以根据需要重写它们。
2> 什么是键-值,键路径是什么
模型的性质是通过一个简单的键(通常是个字符串)来指定的。视图和控制器通过键来查找相应的属性值。在一个给定的实体中,同一个属性的所有值具有相同的数据类型。键-值编码技术用于进行这样的查找—它是一种间接访问对象属性的机制。
键路径是一个由用点作分隔符的键组成的字符串,用于指定一个连接在一起的对象性质序列。第一个键的性质是由先前的性质决定的,接下来每个键的值也是相对于其前面的性质。键路径使您可以以独立于模型实现的方式指定相关对象的性质。通过键路径,您可以指定对象图中的一个任意深度的路径,使其指向相关对象的特定属性。
3> 什么是KVC和KVO
KVC(Key-Value-Coding)内部的实现:一个对象在调用setValue的时候,(1)首先根据方法名找到运行方法的时候所需要的环境参数。(2)他会从自己isa指针结合环境参数,找到具体的方法实现的接口。(3)再直接查找得来的具体的方法实现。KVO(Key-Value- Observing):当观察者为一个对象的属性进行了注册,被观察对象的isa指针被修改的时候,isa指针就会指向一个中间类,而不是真实的类。所以 isa指针其实不需要指向实例对象真实的类。所以我们的程序最好不要依赖于isa指针。在调用类的方法的时候,最好要明确对象实例的类名
4> kvo的实现机制
当某个类的对象第一次被观察时,系统就会在运行时动态地创建该类的一个派生类,在这个派生类中重写原类中被观察属性的setter方法,派生类在被重写的setter方法实现真正的通知机制(Person->NSKVONotifying_Person).
派生类重写了 class 方法以“欺骗”外部调用者它就是起初的那个类。然后系统将这个对象的isa指针指向这个新诞生的派生类,因此这个对象就成为该派生类的对象了,因而在该对象上对setter的调用就会调用重写的setter,从而激活键值通知机制。此外,派生类还重写了dealloc方法来释放资源。
5> kvo使用场景
①实现上下拉刷新控件 contentoffset
②webview混合排版 contentsize
③监听模型属性实时更新UI
六.设计模式
1> 常用的设计模式
代理 观察者 工厂 单例 策略
2> 代理属性的内存策略是什么,为什么?
3> 观察者模式的使用场景
4> 工厂模式(类方法)为什么没有释放对象? autorelease工作原理? arc下还需要手动使用autorelease吗?为什么?什么场景?
5> 手写单例
6> 策略 cell多种响应效果 代理方法
(一)代理模式
应用场景:当一个类的某些功能需要由别的类来实现,但是又不确定具体会是哪个类实现。
优势:解耦合
敏捷原则:开放-封闭原则
实例:tableview的数据源delegate,通过和protocol的配合,完成委托诉求。
列表row个数delegate
自定义的delegate
(二)观察者模式
应用场景:一般为model层对controller和view进行的通知方式,不关心谁去接收,只负责发布信息。
优势:解耦合
敏捷原则:接口隔离原则,开放-封闭原则
实例:Notification通知中心,注册通知中心,任何位置可以发送消息,注册观察者的对象可以接收。
kvo,键值对改变通知的观察者,平时基本没用过。
(三)MVC模式
应用场景:是一中非常古老的设计模式,通过数据模型,控制器逻辑,视图展示将应用程序进行逻辑划分。
优势:使系统,层次清晰,职责分明,易于维护
敏捷原则:对扩展开放-对修改封闭
实例:model-即数据模型,view-视图展示,controller进行UI展现和数据交互的逻辑控制。
(四)单例模式
应用场景:确保程序运行期某个类,只有一份实例,用于进行资源共享控制。
优势:使用简单,延时求值,易于跨模块
敏捷原则:单一职责原则
实例:[UIApplication sharedApplication]。
注意事项:确保使用者只能通过getInstance方法才能获得,单例类的唯一实例。
java,C++中使其没有公有构造函数,私有化并覆盖其构造函数。
object c中,重写allocWithZone方法,保证即使用户用alloc方法直接创建单例类的实例,返回的也只是此单例类的唯一静态变量。
(五)策略模式
应用场景:定义算法族,封装起来,使他们之间可以相互替换。
优势:使算法的变化独立于使用算法的用户
敏捷原则:接口隔离原则;多用组合,少用继承;针对接口编程,而非实现。
实例:排序算法,NSArray的sortedArrayUsingSelector;经典的鸭子会叫,会飞案例。
注意事项:
1,剥离类中易于变化的行为,通过组合的方式嵌入抽象基类
2,变化的行为抽象基类为,所有可变变化的父类
3,用户类的最终实例,通过注入行为实例的方式,设定易变行为
防止了继承行为方式,导致无关行为污染子类。完成了策略封装和可替换性。
(六)工厂模式
应用场景:工厂方式创建类的实例,多与proxy模式配合,创建可替换代理类。
优势:易于替换,面向抽象编程,application只与抽象工厂和易变类的共性抽象类发生调用关系。
敏捷原则:DIP依赖倒置原则
实例:项目部署环境中依赖多个不同类型的数据库时,需要使用工厂配合proxy完成易用性替换
注意事项:项目初期,软件结构和需求都没有稳定下来时,不建议使用此模式,因为其劣势也很明显,
增加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。