iOS面试旗开得胜之答案篇

如下问题的答案是以前写的一篇文章 《iOS面试旗开得胜之问题篇》 如今把问题的答案整理了一份出来给你们。但愿对你们有所帮助。若是整理的答案有问题,请联系我。shavekevin@gmail.com

一、属性readwrite,readonly,assign,retain,copy,nonatomic 各自什么做用,他们在那种状况下用?

  • readwrite:默认的属性,可读可写,生成setter和getter方法。
  • readonly:只读,只生成getter方法,也就是说不能修改变量。
  • assign:用于声明基本数据类型(int、float)仅设置变量,是赋值属性。
  • retain:持有属性,setter方法将传入的参数先保留,再赋值,传入的参数 引用计数retaincount 会加1

    在堆上开辟一块空间,用指针a指向,而后将指针a赋值(assign)给指针b,等因而a和b同时指向这块堆空间,当a不使用这块堆空间的时候,是否要释放这块堆空间?答案是确定要的,可是这件堆空间被释放后,b就成了野指针。java

    如何避免这样的问题? 这就引出了引用计数器,当a指针这块堆空间的时候,引用计数器+1,当b也指向的时候,引用计数器变成了2,当a再也不指向这块堆空间时,release-1,引用计数器为1,当b也不指向这块堆空间时,release-1,引用计数器为0,调用dealloc函数,空间被释放ios

    总结:当数据类型为int,float原生类型时,可使用assign。若是是上面那种状况(对象)就是用retain。git

  • copy:是赋值特性,setter方法将传入对象赋值一份;须要彻底一份新的变量时,直接从堆区拿。

    当属性是 NSString、NSArray、NSDictionary时,既能够用strong 修饰,也能够用copy修饰。当用strong修饰的NSString 指向一个NSMutableString时,若是在不知情的状况下这个NSMutableString的别的引用修改了值,就会出现:一个不可变的字符串却被改变了的状况, 使用copy就不会出现这种状况。github

  • nonatomic:非原子性,能够多线程访问,效率高。
  • atomic:原子性,属性安全级别的表示,同一时刻只有一个线程访问,具备资源的独占性,可是效率很低。
  • strong:强引用,引用计数+ 1,ARC下,一个对象若是没有强引用,系统就会释放这个对象。
  • weak:弱引用,不会使引用计数+1.当一个指向对象的强引用都被释放时,这块空间依旧会被释放掉。

    使用场景:在ARC下,若是使用XIB 或者SB 来建立控件,就使用 weak。纯代码建立控件时,用strong修饰,若是想用weak 修饰,就须要先建立控件,而后赋值给用weak修饰的对象。面试

    查找了一些资料,发现主要缘由是,controller须要拥有它本身的view(这个view是因此子控件的父view),所以viewcontroller对view就必须是强引用(strong reference),得用strong修饰view。对于lable,它的父view是view,view须要拥有label,可是controller是不须要拥有label的。若是用strong修饰,在view销毁的状况下,label还仍然占有内存,由于controller还对它强引用;若是用wak修饰,在view销毁的时label的内存也同时被销毁,避免了僵尸指针出现。sql

    用引用计数回答就是:由于Controller并不直接“拥有”控件,控件由它的父view“拥有”。使用weak关键字能够不增长控件引用计数,确保控件与父view有相同的生命周期。控件在被addSubview后,至关于控件引用计数+1;父view销毁后,全部的子view引用计数-1,则能够确保父view销毁时子view当即销毁。weak的控件在removeFromSuperview后也会当即销毁,而strong的控件不会,由于Controller还保有控件强引用。数据库

    总结概括为:当控件的父view销毁时,若是你还想继续拥有这个控件,就用srtong;若是想保证控件和父view拥有相同的生命周期,就用weak。固然在大多数状况下用两个都是能够的。设计模式

    使用weak的时候须要特别注意的是:先将控件添加到superview上以后再赋值给self,避免控件被过早释放。数组

二、Objective-C如何对内存管理的,说说你的见解以及你遇到的问题以及解决方法?

​ Objective-C使用引用计数来管理内存,对象有个计数器,用以表示当前有多少个事物想令此对象继续存活下去。缓存

  • MRC 手动内存计数 (Reference Counted)

    retain 递增保留计数

    release 递减保留计数

    对象被建立出来,对象的保留计数至少为1,若想令某对象继续存活,则调用retain方法。要是不想令其继续存活,就调用release或autorelease。当保留计数归零时,对象就回收了(deallocated),系统会将其占用的内存标记为“可重用”。(拖对象保留计数为1 的时候 调用release或autorelease,不会让计数为0,会直接释放,由于这样能够省一步操做)

    ​注意:对象建立出来以后,并非说对象此时的保留计数一定是1.在alloc或initWith方法的实现代码中,也许还有其余对象也保留了此对象,因此,其保留计数可能会大于1.毫不应该说保留计数必定是某个值,只能说你执行的操做是递增了引用计数或递减了引用计数。

    autoreleasepool(自动释放池)

    调用autorelease时,对象的引用计数不会立刻递减,而是先对象放进自动释放池,一般是在下一次“事件循环”时递减。

    由于自动释放池中的释放操做要等到下一次事件循环时才会执行,因此NSLog语句在使用str对象前不须要手工保留。可是,假如要持有此对象的话(好比将其设置给实例变量),那就须要保留。

  • ARC 自动内存计数(Garbage Collection)

    使用ARC时,引用计数实际仍是要执行的,只不过保留与释放操做如今是由ARC自动添加。

    这种方式和java相似,在你的程序的执行过程当中。始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它何时开始工做,怎样工做。你只须要明白,我申请了一段内存空间,当我再也不使用从而这段内存成为垃圾的时候,我就完全的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人须要消耗必定的资源。

    ARC在调用这些方法时(retain、release、autorelease、dealloc),并不经过普通的Objective-C消息派发机制,而是直接调用其底层C语言版本。这样作性能更好,由于保留及释放操做须要频繁执行,因此直接调用底层函数能节省不少CPU周期。比方说,ARC会调用与retain等价的底层函数objc_retain。这也是不能覆写retain、release、autorelease的缘由。

三、内存管理的几条原则时什么?按照默认法则.哪些关键字生成的对象须要手动释放?在和property结合的时候如何有效的避免内存泄露?

  • 谁申请,谁释放 
    遵循Cocoa Touch的使用原则; 
    内存管理主要要避免“过早释放”和“内存泄漏”,对于“过早释放”须要注意@property设置特性时,必定要用对特性关键字,对于“内存泄漏”,必定要申请了要负责释放,要细心。 关键字alloc 或new 生成的对象须要手动释放; 
    设置正确的property属性,对于retain须要在合适的地方释放,
  • 使用new、alloc或copy方法建立一个对象时,该对象引用计数器为1。若是不须要使用该对象,能够向其发送release或autorelease消息,在其使用完毕时被销毁。

    ​ 若是经过其余方法获取一个对象,则能够假设这个对象引用计数为1,而且被设置为autorelease,不须要对该对象进行清理,若是确实须要retain这个对象,则须要使用完毕后release。

    ​ 若是retain了某个对象,须要release或autorelease该对象,保持retain方法和release方法使用次数相等。

    ​ 使用new、alloc、copy关键字生成的对象和retain了的对象须要手动释放。设置为autorelease的对象不须要手动释放,会直接进入自动释放池。

四、MVC设计模式是什么? 你还熟悉什么设计模式?他们和MVC有什么不一样的地方?

  • MVC设计模式是什么

    MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑汇集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不须要从新编写业务逻辑。MVC被独特的发展起来用于映射传统的输入、处理和输出功能在一个逻辑的图形化用户界面的结构中。

    Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
      一般模型对象负责在数据库中存取数据。

    View(视图)是应用程序中处理数据显示的部分。
      一般视图是依据模型数据建立的。

    Controller(控制器)是应用程序中处理用户交互的部分。
      一般控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

  • 你还熟悉什么设计模式?

    • 代理模式

      解决什么场景:当一个类的某些功能须要被别人来实现,可是既不明确是些什么功能,又不明确谁来实现这些功能的时候,委托代理模式就能够派上用场。

      在cocoa框架中的Delegate模式中,委托人每每是框架中的对象(视图中的控件、表视图神马的),代理人每每是视图控制器对象。

      自定义一个delegate模式

@interface A:UIView
    id transparendValueDelegate;
    @property(nomatic, retain) id transparendValueDelegate;

    @end

    @implementation A
    @synthesize transparendValueDelegate

    -(void)Call
    { 
    NSString* value = @"你好";
    [transparendValueDelegate transparendValue: value];
    }

    @end
    @interface B:UIView

    NSString* value;
    @end

    @implementation B
    -(void)transparendValue:(NSString*)fromValue
    {
    value = fromValue;
    NSLog(@"%@ ,我是B",value); 
    }
    @end

使用时:

A* a = [[A alloc] init];
    B* b = [[B alloc] init];
    a. transparendValueDelegate = b;//设置A代理委托对象为B
    [a Call];

这样就会输出:

**你好,我是B**

 委托模式关键就在于一个“**被”**字。这个B是很被动的,随时就会被你A Call一下。
  • 观察者模式

    观察者模式本质上时一种发布-订阅模型,用以消除具备不一样行为的对象之间的耦合,经过这一模式,不一样对象能够协同工做,同时它们也能够被复用于其余地方Observer从Subject订阅通知,ConcreteObserver实现重现ObServer并将其重载其update方法。一旦SubJect的实例须要通知Observer任何新的变动,Subject会发送update消息来通知存储在其内部类中所注册的Observer、在ConcreteObserverupdate方法的实际实现中,Subject的内部状态可被取得并进行后续处理。

    通知

    在Cocoa Touch框架中NSNotificationCenterNSNotification对象实现了一对多的模型。经过NSNotificationCenter可让对象之间进行通信,即使这些对象之间并不认识。

    KVO

    KVO是Cocoa提供的一种称为键值观察的机制,对象能够经过它获得其余对象特定属性的变动通知。而这个机制是基于NSKeyValueObserving非正式些,Cocoa经过这个协议为全部遵循协议的对象提供了一种自动化的属性监听的功能。

    虽然通知KVO均可以对观察者进行实现,可是他们之间仍是略有不一样的,由上面的例子咱们能够看出通知是由一个中心对象为全部观察者提供变动通知,主要是广义上关注程序事件,而KVO则是被观察的对象直接想观察者发送通知,主要是绑定于特定对象属性的值。

  • 单例模式

    单例设计模式确保对于一个给定的类只有一个实例存在,这个实例有一个全局惟一的访问点。它一般采用懒加载的方式在第一次用到实例的时候再去建立它。

    注意:苹果大量使用了此模式。例如:[NSUserDefaults standardUserDefaults], [UIApplication sharedApplication], [UIScreen mainScreen], [NSFileManager defaultManager],全部的这些方法都返回一个单例对象。

    有一些状况下,只有一个实例显得很是合理。举例来讲,你不须要有多个Logger的实例,除非你想去写多个日志文件。或者一个全局的配置处理类:实现线程安全的方式访问共享实例是容易的,好比一个配置文件,有好多个类同时修改这个文件。

  • 工厂模式

    正式的解释是:在基类中定义建立对象的一个接口,让子类决定实例化哪一个类。工厂方法让一个类的实例化延迟到子类中进行。工厂方法要解决的问题是对象的建立时机,它提供了一种扩展的策略,很好地符合了开放封闭原则。工厂方法也叫作虚构造器(Virtual Constructor)。

    经过工厂方法建立工厂对象,而后在工厂类中定义建立基类的子类对象的方法并经过外部传入的条件判断去建立哪个子类对象,不过因为OC是运行时语言,因此工厂类虽然提供了建立子类对象的方法,可是在编译时期并不能肯定对象类型,编译时期建立的子类对象类型是基类类型,真正的类型在运行时期由子类来肯定,也即此时肯定为子类类型。

    优势

    极大地优化了代码,若是须要100个子类对象,不用再一直调用alloc方法去建立,而是直接经过其工厂类的一句代码便可实现,提升了对代码的复用性。同时,也能够将大量的操做放到工厂类中去处理,业务类中只负责去调用建立须要的对象便可。

    缺点

    由于它的实现条件之一必须存在继承关系,因此模式中工厂类集中了全部的建立逻辑,造成一个庞大的全能类,当全部的类不是继承自同一个父类的时候扩展比较困难。

五、浅复制和深复制的区别?

  • 浅拷贝

    浅拷贝就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间。

    浅拷贝只是对对象的简单拷贝,让几个对象共用一片内存,当内存销毁的时候,指向这片内存的几个指针须要从新定义才可使用,要否则会成为野指针。

    在 iOS 里面, 使用retain 关键字进行引用计数,就是一种更加保险的浅拷贝。他既让几个指针共用同一片内存空间,又能够在release 因为计数的存在,不会轻易的销毁内存,达到更加简单使用的目的。

  • 深拷贝

    深拷贝是指拷贝对象的具体内容,而内存地址是自主分配的,拷贝结束以后,两个对象虽然存的值是相同的,可是内存地址不同,两个对象也互不影响,互不干涉。

    copy 与 retain 的区别:

    copy 是建立一个新对象,retain 是建立一个指针,引用对象计数加一。 copy属性标识两个对象内容相同,新的对象retain count为1, 与旧有对象引用计数无关,旧有对象没有变化。copy减小对象对上下文的依赖。

    iOS提供了copy和mutableCopy方法,顾名思义,copy就是复制了一个imutable的对象,而mutableCopy就是复制了一个mutable的对象。如下将举几个例子来讲明。
    这里指的是NSString, NSNumber等等一类的对象。

NSString *string = @”dddd";

NSString *stringCopy = [string copy];

NSMutableString *stringDCopy = [string mutableCopy];

[stringMCopy appendString:@``"!!"``];

查看内存能够发现,string和stringCopy指向的是同一块内存区域(weak reference),引用计数没有发生改变。而stringMCopy则是咱们所说的真正意义上的复制,系统为其分配了新内存,是两个独立的字符串内容是同样的。

  • 固然在 ios 中并非全部的对象都支持copy,mutableCopy,遵照NSCopying协议的类能够发送copy消息,遵照NSMutableCopying协议的类才能够发送mutableCopy消息。

    copy构造

- (id)copyWithZone:(NSZone *)zone{

    MyObj *copy = [[[self class] allocWithZone :zone] init];

    copy->name = [_name copy];

    copy->imutableStr = [_imutableStr copy];

    copy->age = age;

    return copy;

}

mutableCopy构造

- (id)mutableCopyWithZone:(NSZone *)zone{

    MyObj *copy = NSCopyObject(self, 0, zone);

    copy->name = [_name mutableCopy];

    copy->age = age;

    return copy;

}

六、什么是KVO和KVC?他们的使用场景是什么?

  • KVC

    KVC,便是指 NSKeyValueCoding,一个非正式的 Protocol,提供一种机制来间接访问对象的属性。KVO 就是基于 KVC 实现的关键技术之一。

    一个对象拥有某些属性。好比说,一个 Person 对象有一个 name 和一个 address 属性。以 KVC 说法,Person 对象分别有一个 value 对应他的 name 和 address 的 key。 key 只是一个字符串,它对应的值能够是任意类型的对象。从最基础的层次上看,KVC 有两个方法:一个是设置 key 的值,另外一个是获取 key 的值。

    说白了就是经过指定的key得到想要的值value。而不是经过调用Setter、Getter方法访问。

    • 注意

      (1). key的值必须正确,若是拼写错误,会出现异常

      (2). 当key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,若是你本身写了这个方法,key的值出错就会调用到这里来

      (3). 由于类key反复嵌套,因此有个keyPath的概念,keyPath就是用.号来把一个一个key连接起来,这样就能够根据这个路径访问下去

      (4). NSArray/NSSet等都支持KVC

    • 底层原理

      • 当一个对象调用setValue:forKey: 方法时,方法内部会作如下操做:

        1.判断有没有指定key的set方法,若是有set方法,就会调用set方法,给该属性赋值
        2.若是没有set方法,判断有没有跟key值相同且带有下划线的成员属性(_key).若是有,直接给该成员属性进行赋值
        3.若是没有成员属性_key,判断有没有跟key相同名称的属性.若是有,直接给该属性进行赋值
        4.若是都没有,就会调用 valueforUndefinedKey 和setValue:forUndefinedKey:方法。

    • 使用场景

      一、赋值:setValue:forkey。

      二、字典转模型:KVC,使用setValuesForKeysWithDictionary:方法,该方法默认根据字典中每一个键值对,调用setValue:forKey方法

      缺点:字典中的键值对必须与模型中的键值对彻底对应,不然程序会崩溃

      三、取值:valueForKey

  • KVO

    Key-Value Observing (KVO) 创建在 KVC 之上,它可以观察一个对象的 KVC key path 值的变化。说白了就是你关心的一个值改变了,你就会获得通知。你就能够在你想处理的地方处理这个值。

    一、为对象添加一个观察者(监听器)

    二、设置监听事件

    三、取消监听

    • 底层实现原理

      • KVO是基于runtime机制实现的
      • 当某个类的属性对象第一次被观察时,系统就会在运行期动态地建立该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
      • 若是原类为Person,那么生成的派生类名为NSKVONotifying_Person
      • 每一个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
      • 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey:didChangevlueForKey:;在一个被观察属性发生改变以前, willChangeValueForKey:必定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
      • 补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让咱们误认为仍是使用的当前类,从而达到隐藏生成的派生类

  • 主要分为三大步

    - 第一步:寻找该属性有没有setsetter方法?有,就直接赋值
    - 第二步:寻找有没有该属性的成员属性?有,就直接赋值
    - 第三步:寻找有没有该属性带下划线的成员属性?有,就直接赋值

七、通知和协议有哪些不一样之处?

  • 通知

    须要有一个通知中心:NSNotificationCenter,自定义通知的话须要给一个名字,而后监听。

    • 优势: 通知的发送者和接受者都不须要知道对方。能够指定接收通知的具体方法。通知名能够是任何字符串。
    • 缺点: 较键值观察(KVO)须要多点代码,在删掉前必须移除监听者。
  • 协议

    经过setDelegate来设置代理对象,最典型的例子是经常使用的 TableView.

    • 优势:支持它的类有详尽和具体信息。
    • 缺点:该类必须支持委托。某一时间只能有一个委托链接到某一对象。

八、在iOS应用有哪些方式保存本地数据?他们都应用在哪些场景?

  • 沙盒

    • Documents:保存用户产生的数据,iTunes同步设备的时候会备份该目录。用户产生的数据就是指用户在使用当前app的时候保存的一些数据,好比保存app中的图片、保存下载的文件等。
    • Library:这个目录下有2个文件夹,一个是Caches、一个是Preferences,Caches主要保存缓存数据,好比SDWebImage把缓存的图片就存放到该目录下。当程序退出后,改目录保存的文件一直存在。
      PreferencesXcode6以前保存的是偏好设置,好比NSUserDefaults保存的文件。可是Xcode6以上就保存到/Users/用户名/Library/ Developer/CoreSimulator/Devices/模拟器UDID/data/Library/Preferences/文件夹下。
    • tmp:保存程序中的临时数据,当程序退出后系统会自动删除tmp中全部的文件。
  • NSUserDefaults

    • NSUserDefaults 是个单例对象,在整个程序的生命周期中都只有一个实例。
    • NSUserDefaults 保存的数据类型:NSNumber, 基本数据类型(int,NSInter,float,double,CGFlat......), NSString, NSData, NSArray, NSDictionary, NSURL。
    • NSUserDefaults:通常保存配置信息,好比用户名、密码、是否保存用户名和密码、是否离线下载等一些配置条件信息。
    • 用法
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    //保存值(key值同名的时候会覆盖的)  

    [defaults setObject:@"用户名" forKey:kUsernameKey];

    //当即保存

    [defaults synchronize];

    //取值

    NSString *username = [defaults objectForKey:kUsernameKey];
  • 保存的一些方法
//保存NSInteger
    [defaults setInteger:(NSInteger) forKey:(nonnull NSString *)];
    //保存BOOL
    [defaults setBool:(BOOL) forKey:(nonnull NSString *)];
    //保存NSURL
    [defaults setURL:(nullable NSURL *) forKey:(nonnull NSString *)];
    //保存float
    [defaults setFloat:(float) forKey:(nonnull NSString *)];
    //保存double
    [defaults setDouble:(double) forKey:(nonnull NSString *)];
  • 取值方法
[defaults integerForKey:(nonnull NSString *)];
    [defaults boolForKey:(nonnull NSString *)];
    [defaults URLForKey:(nonnull NSString *)];
    [defaults floatForKey:(nonnull NSString *)];
    [defaults doubleForKey:(nonnull NSString *)];
  • 删除方法
[defaults removeObjectForKey:(nonnull NSString *)];
  • 归档(序列化)

    • 通常保存自定义的对象,可是只有遵照NSCoding的类才能只用归档。
    • 准守NSCoding协议必需要实现两个require方法

      • (void)encodeWithCoder:(NSCoder *)aCoder //归档会触发
      • - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder //解归档会触发
    • Coding 类具体实现:

      @interface Coding : NSObject<NSCoding>
      @property (nonatomic, copy) NSString *name;
      @property (nonatomic, assign) NSInteger age;
#import "Coding.h"
    #import <objc/runtime.h>
    @implementation Coding
     /**
     *  根据类动画获取类的全部属性,不要忘记导入#import <objc/runtime.h>
     *
     *  @param cls <#cls description#>
     *
     *  @return <#return value description#>
     */
    - (NSArray *)perperiesWithClass:(Class)cls
    {

        NSMutableArray *perperies = [NSMutableArray array];

        unsigned int outCount;
        //动态获取属性
        objc_property_t *properties = class_copyPropertyList(cls, &outCount);

        //遍历person类的全部属性
        for (int i = 0; i < outCount; i++)
        {
            objc_property_t property = properties[i];
            const char *name = property_getName(property);
            NSString *s = [[NSString alloc] initWithUTF8String:name];

            [perperies addObject:s];

        }

        return perperies;
    }

    /**
     *  归档会触发
     *
     *  @param aCoder <#aCoder description#>
     */
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        for (NSString *perperty in [self perperiesWithClass:[self class]])
        {
            [aCoder encodeObject:perperty forKey:perperty];
        }
    }

    /**
     *  解归档会触发
     *
     *  @param aDecoder <#aDecoder description#>
     *
     *  @return <#return value description#>
     */
    - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        if (self = [super init])
        {
            for (NSString *perperty in [self perperiesWithClass:[self class]])
            {
                [self setValue:[aDecoder decodeObjectForKey:perperty] forKey:perperty];;
            }

        }

        return self;
    }

    @end
  • 归档具体使用
Coding *coding1 = [[Coding alloc] init];
     coding1.name = @"小明";
     coding1.age = 12;

     Coding *coding2 = [[Coding alloc] init];
     coding1.name = @"小王";
     coding1.age = 20;

     NSArray *array = @[coding1, coding2];

     //保存对象转化为二进制数据(必定是可变对象)
     NSMutableData *data = [NSMutableData data];

     //1.初始化
     NSKeyedArchiver *archivier = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
     //2.归档
     [archivier encodeObject:array forKey:@"key"];

     //3.完成归档
     [archivier finishEncoding];

     //4.保存
     [[NSUserDefaults standardUserDefaults] setObject:data forKey:@"data"];
  • 解归档的具体使用:
//1.获取保存的数据
     NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"data"];

     //2.初始化解归档对象
     NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

     //3.解归档
     NSArray *persons = [unarchiver decodeObjectForKey:@"key"];

     //4.完成解归档
     [unarchiver finishDecoding];
  • plist文件保存

    • 通常在iOS用plist保存,plist自己就是XML文件,名字后缀为.plist
    • plist主要保存的数据类型为NSStringNSNumberNSDataNSArrayNSDictionary
    • 具体实现:
//把字典写入到plist文件,好比文件path为:~/Documents/data.plist
    [dictionary writeToFile:path atomically:YES];
    //把数组写入到plist文件中
    [array writeToFile:path atomically:YES];
  • 读取数据
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:(nonnull NSString *)]];
    NSDictionary *dictionary =  [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:(nullable NSString *) ofType:(nullable NSString *)]];
NSArray *array = [NSArray arrayWithContentsOfURL:[NSURL fileURLWithPath:(nonnull NSString *)]];
    NSArray *array = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:(nullable NSString *) ofType:(nullable NSString *)]];
  • 数据库

    • iOS用的sqlite3, 使用sqlite3须要配置库文件libsqlite3.tbd或者导入libsqlite3.0.tbd,这两个库导入任何一个均可以
    • 保存大量数据能够优先考虑用数据库,sql语句对查询操做有优化做用,因此从查询速度或者插入效率都是很高的。
    • sqlite使用步骤:

      • 指定数据库路径。
      • 建立sqlite3对象而且打开数据库。
      • 建立表。
      • 对数据库操做,包括增删改查。
      • 关闭数据库。

    • 具体实现:

      • 数据库路径

        //返回数据库路径,保存到Cache目录下
        -(NSString *)databasePath
        {
          NSString *path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
        
          return [path stringByAppendingPathComponent:@"contacts.db"];
        }
      • 建立sqlite3对象而且打开数据库,若是数据库打开成功,就建立表。
//数据库对象
        sqlite3 *contactDB;
        
        const char *path = [[self databasePath] UTF8String];

       if (sqlite3_open(path, &contactDB) == SQLITE_OK)
       {
            char *errMsg;
            const char *sql_stmt = "CREATE TABLE IF NOT EXISTS CONTACTS(ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, ADDRESS TEXT,PHONE TEXT)";
           //执行语句
          if (sqlite3_exec(contactDB, sql_stmt, NULL, NULL, &errMsg) != SQLITE_OK)
            {
                    //建立表失败
             }
       }
       else 
        {
                //打开数据库失败
        }
        sqlite3_close(contactDB);
  • 代码解释:

    • sqlite3_open:打开指定路径的数据库,若是数据库不存在,就会建立一个新的数据库。
    • SQLITE_OK 是一个常量,表示打开数据库成功。
    • contactDB 就是数据库对象。
    • sqlite3_exec就是执行sql语句方法。
    • sqlite3_close关闭数据库,通常暂时不用数据库的时候手动关闭,防止资源浪费。
    • 保存数据到数据库
//是一个抽象类型,是一个句柄,在使用过程当中通常以它的指针进行操做
      sqlite3_stmt *statement;

      //数据库路径 
      const char *path = [[self databasePath] UTF8String];

      //使用的时候打开数据库
      if (sqlite3_open(path, &contactDB) == SQLITE_OK)
      {
          NSString *insertSQL = [NSString stringWithFormat:@"INSERT INTO CONTACTS (name,address,phone) VALUES(\"%@\",\"%@\",\"%@\")",name.text,address.text,phone.text];

          const char *insert_stmt = [insertSQL UTF8String];
         // 这个函数将sql文本转换成一个准备语句(prepared statement)对象,同时返回这个对象的指针。这个接口须要一个数据库链接指针以及一个要准备的包含SQL语句的文本。它实际上并不执行这个SQL语句,它仅仅为执行准备这个sql语句
          sqlite3_prepare_v2(contactDB, insert_stmt, -1, &statement, NULL);
          //执行这个sql
          if (sqlite3_step(statement) == SQLITE_DONE)
          {
              //TODO:已存储到数据库;
          }
          else
          {
              //TODO:保存失败
          }
          //销毁statement对象
          sqlite3_finalize(statement);
          //关闭数据库
          sqlite3_close(contactDB);
      }
  • 查询操做
//数据库路径
      const char *path = [[self databasePath] UTF8String];
      //查询结果集对象句柄
      sqlite3_stmt *statement;

      //打开数据库
      if (sqlite3_open(path, &contactDB) == SQLITE_OK)
      {
          //查询的sql语句
          NSString *querySQL = [NSString stringWithFormat:@"SELECT address,phone from contacts where name=\"%@\"",name.text];
          const char *query_stmt = [querySQL UTF8String];

          //执行查询sql语句
          if (sqlite3_prepare_v2(contactDB, query_stmt, -1, &statement, NULL) == SQLITE_OK) 
          {
              //遍历每条数据
              if (sqlite3_step(statement) == SQLITE_ROW) 
              {
                  //获取每条数据的字段。
                  NSString *addressField = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 0)];
                  address.text = addressField;

                  NSString *phoneField = [[NSString alloc] initWithUTF8String:(const char *)sqlite3_column_text(statement, 1    )];
                  phone.text = phoneField;

                  //TODO:已查到结果
              }
              else
              {
                  //TODO:未查到结果
              }
              sqlite3_finalize(statement);
          }

          sqlite3_close(contactDB);
      }
  • CoreData

    • CoreData提供了一种“对象-关系映射”的功能,能将OC对象转化成数据,保存Sqlite中。
    • CoreData的好处就是可以合理管理内存,避免sql语句的麻烦(不用写sql语句)。
    • CoreData构成

      • NSManagedObjectContext:被管理的数据上下文,主要做用:插入、查询、删除。
      • NSManagedObjectModel:数据库全部的表结构和数据结构,包含各个实体的定义的信息。主要做用就是添加实体、实体属性,创建属性之间的关系。
      • NSPersistentStoreCoordinator持久化存储助理对象,至关于数据库的链接器。主要做用就是设置存储的名字、位置、存储方式。
      • NSFetchRequest至关于select语句。查询封装对象。
      • NSEntityDescription实体结构对象,至关于表格结构。
      • 后缀为xxx.xcdatamodeld文件,编译后为xxx.momd的文件。
    • 保存数据
- (NSManagedObjectContext *)context
    {
     AppDelegate *app = [UIApplication sharedApplication].delegate;

     return app.managedObjectContext;
    }
//建立Person对象
    /*
    insertNewObjectForEntityForName:就是建立的实体名字。
    inManagedObjectContext:上下文,appDelegate里面已经建立完成。
    */
     Person *person = [NSEntityDescription
                       insertNewObjectForEntityForName:@"Person"
                       inManagedObjectContext:[self context]];

     //赋值
     [person setValue:@"小王" forKey:@"name"];
     [person setValue:@(35) forKey:@"age"];

     //保存
     if (![[self context] save:nil])
     {
        //TODO:保存失败
     }
  • 查询
//建立查询对象
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];

    #if 0
        //条件查询
        //NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age<=35"];
        //查询名字带有王的
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name like[cd]'*王*'"];
    //设置查询条件
        request.predicate = predicate;
    #endif

        //排序
        NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:NO];
     //设置排序条件
        request.sortDescriptors = @[sort];

        //执行查询
        NSArray *objectArray = [[self context] executeFetchRequest:request error:nil];

       //遍历查询结果
        for (Person *p in objectArray)
        {
            NSLog(@"%@ - %@",[p valueForKey:@"name"],[p valueForKey:@"age"]);
        }
  • 修改
//先查询要修改的对象
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];

        //设置查询条件
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name='小王' and age = 35"];
        request.predicate = predicate;

        //执行查询
        NSArray *objectArray = [[self context] executeFetchRequest:request error:nil];

        //遍历要修改的对象
        for (Person *p in objectArray)
        {
            //修改(修改内存数据,没有同步数据库)
            [p setValue:@(45) forKey:@"age"];
        }
        //同步数据库
        [[self context] save:nil];
  • 删除
//查询要删除的数据
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];

        //设置查询条件
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"name='小王'"];
        request.predicate = predicate;

        //执行查询
        NSArray *objectArray = [[self context] executeFetchRequest:request error:nil];

        //遍历删除
        for (Person *p in objectArray)
        {
            //删除内存中的数据
            [[self context] deleteObject:p];
         }

        //同步数据库
        [[self context] save:nil];
    • 当app更新版本,而且表结构有修改,须要版本升级和数据迁移操做,不然app就是崩掉。
    • KeyChain

      • 钥匙串(英文: KeyChain)是苹果公司Mac OS中的密码管理系统。
      • 一个钥匙串能够包含多种类型的数据:密码(包括网站,FTP服务器,SSH账户,网络共享,无线网络,群组软件,加密磁盘镜像等),私钥,电子证书和加密笔记等。
      • iOS的KeyChain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式。每一个iOS程序都有一个独立的KeyChain存储。从iOS 3.0开始,跨程序分享KeyChain变得可行。
      • 当应用程序被删除后,保存到KeyChain里面的数据不会被删除,因此KeyChain是保存到沙盒范围之外的地方。
      • KeyChain的全部数据也都是以key-value的形式存储的,这和NSDictionary的存储方式同样。
      • 相比于NSUserDefaults来讲,KeyChain保存更为安全,并且KeyChain里面保存的数据不会由于app删除而丢失。
      • 基本使用

        为了使用方便,咱们使用github上封装好的类KeychainItemWrapperSFHFKeychainUtils

    // 初始化一个保存用户账号的KeychainItemWrapper 
          KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@"Your Apple ID" accessGroup:@"YOUR_APP_ID.com.yourcompany.AppIdentifier"];
          //保存账号
          [wrapper setObject:@"<账号>" forKey:(id)kSecAttrAccount];  
          //保存密码
          [wrapper setObject:@"<账号密码>" forKey:(id)kSecValueData];
    
          //从keychain里取出账号密码
          NSString *password = [wrapper objectForKey:(id)kSecValueData];
    
          //清空设置
          [wrapper resetKeychainItem];
    • 上面代码的setObject: forKey: 里参数forKey的值应该是Security.framework里头文件SecItem.h里定义好的key

    • SFHFKeychainUtils就3个方法:
    //获取密码密码
          +(NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
          //存储密码
          +(BOOL) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting error: (NSError **) error;
          //删除密码
          +(BOOL) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSError **) error;
    • 参数说明

      • username:由于KeyChain保存也是以键值对存在,因此这个能够看做key,根据key取value.
      • forServiceName :这个就是组的名字,能够理解为KeyChain保存是分组保存。通常要惟一哦,命名可使用YOUR_APP_ID.com.yourcompany.AppIdentifier。
      • 若是两个应用的usernameserviceName参数同样,那么这两个app会共用KeyChain里面的数据,也就是能够共享密码。
      • KeyChain还有一个用途,就是替代UDID。UDID已经被废除了,因此只能用UUID代替,因此咱们能够把UUID用KeyChain保存。
    //建立一个uuid
          NSString *uuidString = [self uuidString];
          //31C75924-1D2E-4AF0-9C67-96D6929B1BD3
    
          [SFHFKeychainUtils storeUsername:kKeyChainKey andPassword:uuidString forServiceName:kKeyChainGroupKey updateExisting:NO error:nil];
    
          -(NSString *)uuidString
          {
            //建立一个uuid
            CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
            CFStringRef stringRef = CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
    
            NSString *uuidString = (__bridge NSString *)(stringRef);
    
            CFRelease(uuidRef);
    
            return uuidString;
          }
    相关文章
    相关标签/搜索