设计模式分析比较?
1、单例设计模式:在项目中,单例是必不可少的。比如UIApplication、NSUserDefaults就是苹果提供的单例。在项目中经常会将用户数据管理封装成一个单例类,因此用户的信息需要全局使用。单例确实给我们带来的便利,但是它也会有代价的。单例一旦创建,整个App使用过程都不会释放,这会占用内存,因此不可滥用单例。首先,单例写法有好几种,通常的写法是基于线程安全的写法,结合dispatch_once来使用,保证单例对象只会被创建一次。如果不小心销毁了单例,再调用单例生成方法是不会再创建的。其次,由于单例是约定俗成的,因此在实际开发中通常不会去重写内存管理方法。
2、MVC设计模式:现在绝大部分项目都是基于MVC设计模式的,现在有一部分开发者采用MVVM、MVP等模式。
3、通知(NSNotification)模式:通知在开发中是必不可少的,对于跨模块的类交互,需要使用通知;对于多对多的关系,使用通知更好实现。
4、工厂设计模式:在我的项目中使用了大量的工厂设计模式,特别是生成控件的API,都已经封装成一套,全部是扩展的类方法,可简化很多的代码。
5、KVC/KVO设计模式:有的时候需要监听某个类的属性值的变化而做出相应的改变,这时候会使用KVC/KVO设计模式。在项目中,我需要监听model中的某个属性值的变化,当变化时,需要更新UI显示,这时候使用KVC/KVO设计模式就很方便了。
通知中心?
本质可变数组,首先创建单例并开辟空间负责管理所有的通知,接受已经注册的通知对象,这个对象的属性包括观察者,方法,名字,传递的对象!每当注册一个新的通知时,必须通过NSNotificationCenter这个类创建一个defaultCenter通知中心管理对象通过addObserver、postNotification、removeObserver来管理所有已经注册的通知!内部的实现原理除了传参就是可变数组的遍历,移除通知时传入参数1(通知名字)和参数2(传递的对象值),原理就是Block方式遍历可变数组,只不过判断时不仅要判断名字,更要满足观察者的对比进行比较。
通知中心的本质就是两个类,一个通知数据模型类,一个单例通知中心类,通知数据模型类的属性有发送通知的方法、观察者(控制器)、注册通知的名字、通知传递的对象。通知数据模型类的方法主要有带所有属性输入参数的初始化方法。这个方法主要是为了在普通通知类实例化对象的时候就能给所有的属性进行赋值操作。那么还有一个通知中心单例类,这里面首先有一个可变数组全局变量用来用来添加所有注册的通知。接下来就是一系列方法的声明和实现,声明包括创建单例的类方法、向通知中心注册通知的对象方法、输入注册名字和传递对象发送通知的方法、输入观察者和注册名字删除通知的方法。首先就是实现创建单例的类方法,使用static初始化一个通知中心的对象为空,设置static dispatch_once_t onceToken和dispatch_once(&onceToken,^{});语句让实例化的通知中心对象只开辟一次内存空间。而且最重要的是这里开辟内存空间的方式有一些不一样。采用的是父类调用allocWithZone:NULL来开辟内存空间。当然init初始化方法需要重写,重写的意义就在于为前期声明的全局数组变量开辟内存空间。然后就是实现添加注册的方法,说白了就是添加一个通知版的数据模型对象到可变数组里。实现移除数组里通知的方法,本质就是遍历这个装满通知版数据模型对象的可变数组,判断里面的每一个数据模型对象的属性是否与方法输入的参数相等。现在就是关键了,如何实现发送通知的方法,同样Block遍历数组,找到所有与输入名字相同的通知版数据模型对象,现在就开始正式的调用注册通知里的那个方法了,将输入的参数传递给数据模型里的其它属性。包括方法和对象。然后使用数据模型的对象的观察者属性(这本质上是控制器对象),调用performSelector方法将方法和对象数据传递出去。
发送通知?
就发送通知时的属性对象值赋值给可变数组中的对象的object属性!同时调用出数组对象的方法 SEL sel = noti.selector;现在开始执行可变数组中对象的方法属性(调用对象的方法函数)[noti.selector performSelector:sel withObject:noti]
单例?
单例是整个Cocoa中被广泛使用的核心设计模式之一,作为一个iOS开发者,我们经常和单例打交道,比如UIApplication、NSFileManager、NSNotificationCenter、UIDevice等等。Xcode甚至有一个默认的"Dispatch Once"代码片段,可以使我们非常简单地在代码中创建一个单例。单例,由于其具有全局和多状态的特性,导致隐式地在两个看起来完全不相关的模块之间建立了耦合。单例里最需要注意的一个问题就是要考虑到多线程的影响,由于分线程与主线程的速度差异很大,也就是说主线程的进行过程中不止一次创建分线程去判断单例类对象的内存空间是否已经被开辟。第一种方法就是通过上锁保证在异步创建单例也能保证唯一性@synchronized(self),第二种方法使用static dispatch_once_t onceToken来保证代码只被执行一次。
如果想要在多个控制器使用一个属性的值,那么并不需要用户在程序中设置全局变量,需要在哪里使用单例类的数据,那么就在哪里创建一个单例类对象,然后进行读或者写操作。就是把这个整个工程都需要的用到的属性值当成是单例类对象的属性,如此哪里要使用到这个属性里的值,只需要实例化一个单例对象调用出相应的属性即可。
KVO观察者?
如果类1想要随时监听类2的属性是否发生变化,并把类2变化后的属性值传递到类1里面,只需要三步就可以实现,第一步:在类1跳转到类2之前,先给类2安装一个摄像头,这个观察者就是类1的对象self,而且Key只能指向对象的属性,而KeyPath则能指向属性的属性。注册了观察者之后,自然再实现KVO里的类1观察者监听到类2的KeyPath属性值发生变化会回调的方法。这个回调方法几乎传递过来了整个类2的对象,所以对于KeyPath(点语法)观察的属性轻轻松松获取到现在的值。当然最后需要在视图消失时调用的dealloc方法里通过类2的对象调用移除观察者的方法。还有注意一点就是,KVO监听只能在一个线程中,只能观察一个对象的属性而不能观察集合。
KVO的使用场景与通知的使用场景有什么不同?
1、KVO一次性只能实现两个控制器之间的传值,而通知能够实现将一个控制器的值传递到多个控制器
2、KVO的传递的值只能时被监听的控制器的某一个属性,而通知可以传递一个控制器的任何数据类型的对象
block的本质?
1、指向结构体的指针,调用block之前,Block里面的变量是一个值,在调用block之后,就会通过block里面的方法对变量的值进行改变,也就是说,可以在特定的判断下执行特定的方法。
block为什么要用copy修饰?
1、我们都知道堆里面的东西不会被系统自动回收,因此必须将一个Block放在堆里面。而默认情况下任何block被创建时都在栈内存空间里面,而栈里面的内存随时可能被系统自动回收,更会在大括号结束之后被全部释放。那我们如何才能将block放在堆内存空间里面呢?只要对block做一次copy操作就可以将block指针指向堆内存空间里面。这样block就不会被自动释放了,可以长期持有block。这样就能保证只要对象还在,那么对象的block属性就会还在。
为什么不能用MRC中的retain或ARC中的strong?
1、确实retain和strong已经能够保证对象的属性不会被自动释放,而且只要对象还在,那么属性就一定存在。可是retain和strong仅仅是增加了属性对象引用计数,根本不会改变属性对象的内存地址。只有copy 方法才能改变内存地址。将属性从默认的栈地址改变到堆地址里。
Block使用过程中会出现什么问题?如何解决?
1、block属性必须使用copy来修饰,既然是copy那就是强引用,然后就会出现一个内存泄露的情况,内存泄露就是对象在堆内存空间的内存得不到释放。
2、当初所说的循环引用就是简单的对象1强引用对象2,对象2强引用对象1,MRC里一个retain,另一个assign。ARC里一端是strong,另一端使用weak。虽然这里不是对象1直接强引用对象2,但是因为block使用copy修饰符又作为对象1的属性就间接地强引用了对象2。解决方案当然不能改变block的修饰符,改的只能是如何让对象2不强引用对象1,方法就是在用alloc方法创建了强指针之后,再声明一个弱指针将强指针的内存地址赋值给弱指针,如此便可以使得对象2不再强引用对象1。
KVC的本质是什么?这是一个问题。跟KVO的区别又是什么?KVC有与普通意义上的传值有什么联系?
对于任何一个类的实例化的对象来说,每一个对象肯定都有许多的属性,当然对于一个对象来说,在没有给属性赋值以前,这些属性都是nil.那么现在能想到的赋值方式就是通过等于号直接进行赋值了。那么KVC就为我们提供了一种给对象的属性赋值的全新方法!不需在用对象点语法加属性的方式通过等于号来进行赋值,而是直接以方法[对象 setValue:@"" forKey:@“属性”/forKeyPath:@"属性”]来进行属性的赋值。至于forKey和forKeyPath的区别,在KVO里面就已经说过了!还有过去来说,一想到获取对象的属性值肯定首先会想到对象点语法属性,而现在因为有了KVC则提供了一种全新的获取对象属性值的全新方法。即[对象 valueForKey/KeyPath/UndefinedKey:@"属性”],其实如果发明KVC的意义就这么点功能的话实在是没有什么存在的价值,KVC最大的价值还是在于KVC给对象的属性批量赋值的功能。这次才是KVC的最大贡献,至于单个给某一个属性赋值或提取某一单个属性的值完全不是KVC对于这个世界的贡献,批量给对象的属性赋值才是存在的意义,通过方法[有着批量属性的对象 setValuesForKeysWithDictionary:字典]实现给对象的属性批量赋值的前提条件就是对象里的属性名称必须与字典里的键一模一样,原因就在于传进来的是一个字典,字典决定取哪一个键完全是由属性的名称来决定,格式通常就是字典[@“属性名”]来提取字典里的值来赋值给对象里的属性,那么经常会出现一个问题就是,属性里有的名称字典里并没有,如果只是造成对象里的这个属性赋值不成共也就罢了,问题是这竟然会导致程序崩溃!唯一的解决方案就是在对象的.m文件里添加方法-(void)setValue:(id)value forUndefinedKey:(NSString *)key{}。还有一个情况就是在逐个提取对象里属性的值时,会出现一种情况就是提前想要提取的属性在对象里并不存在,这就造成了崩溃,你可能会问,为什么在用点语法来提取对象时并不会发生崩溃,因为原因就是用点语法提取数据的时候,如果对象没有这个属性,就根本连显示都没有呀!而通过KVC的[对象 valueForKey/KeyPath/UndefinedKey:@"属性”]的方法则无法进行提前判断这个对象里到底有没有这个属性。唯一能做的就是事后诸葛亮,程序崩溃让你崩溃,你自然就知道了!
-(id)valueForUndefinedKey:(NSString *)key{ return nil; }
KVO使用方法?
当我点击按钮时,会将类2的对象的属性值传递给类1的对象的属性值。这就相当于使用KVO来进行反向传值,我在使用KVO进行反向传值时遇到了一个很严重的问题,设置监听通知时必须注意一个问题就是设置观察者时务必注意一个问题,千万不要多创建一个观察者对象,这样尽管反向传值时发现监听发生了,而且也调用了那个监听到属性值发生改变时就会调用的方法,而且你会发现尽管这个方法就是我们设置的那个观察者控制器里的方法,但要知道这此时的观察者其实是一个全新的观察者!所以原本的观察者控制器的属性值全部变为空。所以你会发现尽管程序进入了那个监听路径的内容里面,但是奇怪的是你会发现原本创建的UI视图的属性值全部变为空!所以这其中的问题在那儿呢?关键就在于当控制器1通过导航控制器Push到控制器2时,在控制器2里面注册监听,相当于在self(控制器2)上注册监听,将控制器1设置为观察者,用来监听控制器2里面的KeyPath所对应的值是否发生了变化。那么问题来了,一定要注意设置观察者的时候必须考虑好现在正在设置的观察者是否是新创建的控制器1。然后还有一个问题,就是在监听路径的时候,务必注意控制器的属性KeyPath必须使用点语法,不能使用下划线!
那么现在需要整理一下这个思路,首先就是注册监听,会出现四个参数,参数一也就是说是谁调用通知的这种方法,谁调用这个注册监听的方法就相当于谁是被监听的对象。举个例子,如果控制器2的对象调用这个方法,就相当于在控制器2里面安装了一个摄像头用来监听控制器2里面的所有属性值的变化!参数2就是设置观察者,表示已经安装在控制器2里面的摄像头最终将数据的变化情况传给谁?通常这里填self。也就是说,尽量把KVO的注册和调用以及销毁放在观察者的这个控制器里。那么问题来了,为什么我总是会有有一种错觉就是,要想实现监听功能,必须和被监听的控制器里发生联系呢?就有一种错觉就是,总感觉要想实现决定何时调用监听方法就必须让这个KVO里面的某一个方法必须与被监听的控制器发生某种联系。可是,事实上,并非如此,因为有了参数三:KeyPath,这个参数直接让被监听的控制器精确到一个属性,准确的说,精确到某一个路径才更正确。精确到属性只是相当于精确到key,而keyPath就相当于能够精确到属性里面的属性,反正keyPath远比key强大!当然既然设计传值,其实就少不了SEL方法,因为通常来说,反向传值,会用到几个常见的方法:方法1就是协议代理,就是通过调用函数时候。两个控制器都会出现同一句包含参数的函数,这样就实现了传值,而且可以在函数的具体实现里对传入的参数进行处理。方法2就是通过Block来进行传值,同样也是在block的具体实现里对调用block时传入的参数进行编辑和使用!其实从这个角度来讲,KVO的监听传值更像是一个协议呀!
1、[被监听的控制器 addObserver:观察者控制器 forKeyPath:@“被监听的属
2、- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
(被监听控制器 *)object.被监听属性;}
3、-(void)dealloc{
[被监听的控制器 removeObserver:so forKeyPath:@“被监听的属性"]}
网友评论