OC的简单总结

2015-12-24  本文已影响635人  蓝心儿的蓝色之旅

一.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完成易用性替换

注意事项:项目初期,软件结构和需求都没有稳定下来时,不建议使用此模式,因为其劣势也很明显,

增加了代码的复杂度,增加了调用层次,增加了内存负担。所以要注意防止模式的滥用。

上一篇下一篇

猜你喜欢

热点阅读