美文网首页
知识点2

知识点2

作者: 烟雨平生花飞舞 | 来源:发表于2017-07-19 16:50 被阅读0次

    27、ViewController的didReceiveMemoryWarning是在什么时候调用的?默认的操作是什么?

    当程序接到内存警告时View Controller将会收到这个消息:didReceiveMemoryWarning

    从iOS3.0开始,不需要重载这个函数,把释放内存的代码放到viewDidUnload中去。

    这个函数的默认实现是:检查controller是否可以安全地释放它的view(这里加粗的view指的是controller的view属性),比如view本身没有superview并且可以被很容易地重建(从nib或者loadView函数)。

    如果view可以被释放,那么这个函数释放view并调用viewDidUnload。

    你可以重载这个函数来释放controller中使用的其他内存。但要记得调用这个函数的super实现来允许父类(一般是UIVIewController)释放view。

    如果你的ViewController保存着view的子view的引用,那么,在早期的iOS版本中,你应该在这个函数中来释放这些引用。而在iOS3.0或更高版本中,你应该在viewDidUnload中释放这些引用.

    viewDidUnload 被废弃

    在iOS4和iOS5系统中,当内存不足,应用受到MemoryWarning时,系统就会自动调用当前没有在界面上得ViewController的viewDidUnload方法。通常情况下,未显示在界面的ViewController是UINavigationController Push栈中未在栈顶的ViewController,以及UITabBarController中未显示的子ViewController。这些ViewController都在MemoryWarning事件发生时,让系统自动调用viewDidUnload 方法。

    在iOS6中,由于viewDidUnload 事件任何情况下都不会被触发,所以苹果在文档中建议,应该将回收内存的相关操作移到另一个函数didReceiveMemoryWarning中。但是如果仅仅写成:(以下为错误示例)

    - (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    if(self.isViewLoaded&&!self.view.window)

    {

    self.view=nil;

    }

    }

    在iOS6以后,不建议将view置为nil的原因如下:

    1,UIView有一个CALayer成员变量,CALayer用于将自己画到屏幕上,如下图:

    2,CALayer是一个bitmap图像的容器类,当UIView调用自身的drawRect时,CALayer才会创建bitmap图像类。

    3,具体占内存的其实是一个bitmap图像类,CALayer只占48Bytes,UiView只占96Bytes。而一个iPad的全屏UIView的bitmap类会占到12MB的大小。

    4,在iOS6,当系统发出MemoryWarning时,系统会自动回收bitmap类,但是不回收UIView和CALayer类。这样既能回收大部分内存,又能在需要bitmap类时,通过调用UIView的drawRect:方法重建。

    内存优化:

    苹果系统对上面的内存回收做了一个优化:

    1,当一段内存被分配时,它会被标记成“In User”,以防止被重复使用。当内存被释放时,这段内存被标记为“Not in use”,这样有新的内存申请时,这块内存就可能被分配给其他变量。

    2,CALayer包括的具体的bitmap内容的私有成员变量类型为CABackingStore,当收到MemoryWarning时,CABackingStore类型的内存会被标记为Volatile类型,表示这块内存可能再次被原变量使用。

    这样,有了上面优化后,当收到MemoryWarning时,虽然所有的CALayer所包含的bitmap内存被标记成volatile了,但是只要这块内存没有被复用,当需要重建bitmap内存时,可以直接被复用,避免了再次调用UIView的drawRect:方法。

    简单说:对于iOS6,不需要做任何以前viewDidUnload的事情,更不需要把以前viewDidUnload的代码移到didReceiveMemoryWarning方法中。

    30. frame和bounds有什么不同?

    31.ViewController生命周期

    按照执行顺序排列

    - initWithCoder:通过nib文件初始化时触发

    - awakeFromNib:nib文件被加载的时候,会发送一个awakeFromNib的消息到nib文件中的每个对象

    - loadView:开始加载视图控制器自带的view

    - viewDidLoad:视图控制器的view被加载完成

    - viewWillAppear:视图控制器的view将要显示在window上

    - updateViewConstraints:视图控制器的view开始更新AutoLayout约束

    - viewWillLayoutSubviews:视图控制器的view将要更新内容视图的位置

    - viewDidLayoutSubviews:视图控制器的view已经更新视图的位置

    - viewDidAppear:视图控制器的view已经展现到window上

    - viewWillDisappear:视图控制器的view将要从window上消失

    - viewDidDisappear:视图控制器的view已经从window上消失

    为什么不建议重载loadView?

    永远不要主动调用这个函数。view controller会在view的property被请求并且当前view值为nil时调用这个函数。如果你手动创建view,你应该重载这个函数,且不要在重载的时候调用[super loadview]。如果你用IB创建view并初始化view controller,那就意味着你使用initWithNibName:bundle:方法,这时,你不应该重载loadView函数。

    这个方法系统的默认实现是这样:

    1;寻找有关可用的nib文件的信息,根据这个信息来加载nib文件       //所以,nib的加载过程是在loadview中完成的哦。

    2;如果没有有关nib文件的信息,默认创建一个空白的UIView对象,然后把对象成赋值给view controller的主view。

    所以,如果你决定重载这个函数时,你也应该完成这些步骤:

    把子类的view赋给view属性(property)(你create的view必须是唯一的实例,并且不被其他任何controller共享),而且你重载的这个函数不应该调用super,这个也是为了保持主view与controller的单一映射关系。

    一、结构

    按结构可以对ios的所有ViewController分成两类:

    1、主要用于展示内容的ViewController,这种ViewController主要用于为用户展示内容,并与用户交互,如UITableViewController,UIViewController。

    2、用于控制和显示其他ViewController的ViewController。这种ViewController一般都是一个ViewController的容器。如UINavigationController,UITabbarController。它们都有一个属性:viewControllers。其中UINavigationController表示一种Stack式结构,push一个ViewController或pop一次,因此后一个ViewController一般会依赖前一个ViewController。而UITabbarController表示一个Array结构,各个ViewController是并列的。

    第一种ViewController会经常被继承,用来显示不同的数据给用户。而第二种很少被继承,除非你真的需要自定义它。

    二、Controller和View的生命周期

    这里指的View是指Controller的View。它作为Controler的属性,生命周期在Controller的生命周期内。就是说你的Controller不能在view释放前就释放了。

    图2 ViewController生命周期

    当你alloc并init了一个ViewController时,这个ViewController应该是还没有创建view的。ViewController的view是使用了lazyInit方式创建,就是说你调用的view属性的getter:[self view]。在getter里会先判断view是否创建,如果没有创建,那么会调用loadView来创建view。loadView完成时会继续调用viewDidLoad。loadView和viewDidLoad的一个区别就是:loadView时还没有view。而viewDidLoad时view以及创建好了。

    当view被添加其他view中之前时,会调用viewWillAppear,而之后会调用viewDidAppear。

    当view从其他view中移出之前时,会调用viewWillDisAppear,而之后会调用viewDidDisappear。

    当view不在使用,而且是disappeared,受到内存警告时,那么viewController会将view释放并将其指向nil。

    三、代码组织(如何设计良好的viewcontroller)

    ViewController生命周期中有那么多函数,一个重要问题就是什么代码该写在什么地方。

    1、init里不要出现创建view的代码。良好的设计,在init里应该只有相关数据的初始化,而且这些数据都是比较关键的数据。init里不要掉self.view,否则会导致viewcontroller创建view。(因为view是lazyinit的)。

    2、loadView中只初始化view,一般用于创建比较关键的view如tableViewController的tabView,UINavigationController的navgationBar,不可掉用view的getter(在掉super loadView前),最好也不要初始化一些非关键的view。如果你是从nib文件中创建的viewController在这里一定要首先调用super的loadView方法,但建议不要重载这个方法。

    3、viewDidLoad 这时候view已经有了,最适合创建一些附加的view和控件了。有一点需要注意的是,viewDidLoad会调用多次(viewcontroller可能多次载入view,参见图2)。

    4、viewWillAppear 这个一般在view被添加到superview之前,切换动画之前调用。在这里可以进行一些显示前的处理。比如键盘弹出,一些特殊的过程动画(比如状态条和navigationbar颜色)。

    5、viewDidAppear 一般用于显示后,在切换动画后,如果有需要的操作,可以在这里加入相关代码。

    6、viewDidUnload 这时候viewController的view已经是nil了。由于这一般发生在内存警告时,所以在这里你应该将那些不在显示的view释放了。比如你在viewcontroller的view上加了一个label,而且这个label是viewcontroller的属性,那么你要把这个属性设置成nil,以免占用不必要的内存,而这个label在viewDidLoad时会重新创建。

    ViewController是iOS开发中MVC模式中的C,ViewController是view的controller,ViewController的职责主要包括管理内部各个view的加载显示和卸载,同时负责与其他ViewController的通信和协调。在ios中,有两类ViewController,一类是显示内容的,比如UIViewController、UITableViewController等,同时还可以自定义继承自UIViewController的ViewController;另一类是ViewController容器,UINavigationViewController和UITabBarController等,UINavigationController是以Stack的形式来存储和管理ViewController,UITabBarController是以Array的形式来管理ViewController。和Android中Activity一样,IOS开发中,ViewController也有自己的生命周期(Lifecycle)。

    首先来看看View的加载过程,如下图:

    从图中可以看到,在view加载过程中首先会调用loadView方法,在这个方法中主要完成一些关键view的初始化工作,比如UINavigationViewController和UITabBarController等容器类的ViewController;接下来就是加载view,加载成功后,会接着调用viewDidLoad方法,这里要记住的一点是,在loadView之前,是没有view的,也就是说,在这之前,view还没有被初始化。完成viewDidLoad方法后,ViewController里面就成功的加载view了,如上图右下角所示。

    在Controller中创建view有两种方式,一种是通过代码创建、一种是通过Storyboard或Interface Builder来创建,后者可以比较直观的配置view的外观和属性,Storyboard配合IOS6后推出的AutoLayout,应该是Apple之后主推的一种UI定制解决方案,后期我会专门介绍一篇使用AutoLayout进行UI制作的文章。言归正传,通过IB或Storyboard创建view,在Controller中创建view后,会在Controller中对view进行一些操作,会出现如下代码:

    @interface MyViewController()

    @property (nonatomic) IBOutlet id myButton;

    @property (nonatomic) IBOutlet id myTextField;

    - (IBAction)myAction:(id)sender;

    @end

    这里用IBOutlet标记了一个UIButton和一个UITextField,用IBAction来标记UIButton的响应事件,IBOutlet和IBAction都是一个整形常量,用来标记控件,通过一张图能比较清晰的看清他们之间的关系:

    上图中,MyViewController是继承自UIViewController的一个自定义ViewController,它包含两个View,一个是UIButton,一个是UITextField,从箭头的指向性上就可以比较好的理解IBOutlet和IBAction了。IBOutlet是告诉Interface Builder,此实例变量被连接到nib文件中的view对象,IBOutlet本身不做任何操作,只是一个标记作用。IBAction同样是个标记关键字,它只能标记方法,它告诉IB用IBAction标记的方法可以被某个控件触发。

    通过编程的方式创建view,如下代码:

    - (void)loadView

    {

    CGRect applicationFrame = [[UIScreen mainScreen] applicationFrame];

    UIView *contentView = [[UIView alloc] initWithFrame:applicationFrame];

    contentView.backgroundColor = [UIColor blackColor];

    self.view = contentView;

    levelView = [[LevelView alloc] initWithFrame:applicationFrame viewController:self];

    [self.view addSubview:levelView];

    }

    上述代码首先得到屏幕的frame,然后根据该frame生成了一个contentView,并指定当前ViewController的root view为contentView,然后生成了一个LevelView的自定义View并将它通过addSubview:方法添加到当前ViewController当中,完成view的初始化加载。

    关于loadView方法的重写,官方文档中有一个明显的注释,原文如下:

    Note: When overriding the loadView method to create your views programmatically, you should not call super. Doing so initiates the default view-loading behavior and usually just wastes CPU cycles. Your own implementation of the loadView method should do all the work that is needed to create a root view and subviews for your view controller.

    意思是当通过代码方式去创建你自己的view时,在loadView方法中不应该调用super,如果调用[super loadView]会影响CPU性能。

    接下来我们看看ViewController中的view是如何被卸载的:

    从图中可以看到,当系统发出内存警告时,会调用didReceiveMemoeryWarning方法,如果当前有能被释放的view,系统会调用viewWillUnload方法来释放view,完成后调用viewDidUnload方法,至此,view就被卸载了。此时原本指向view的变量要被置为nil,具体操作是在viewDidUnload方法中调用self.myButton = nil;

    小结一下:

    loadView和viewDidLoad的区别就是,loadView时view还没有生成,viewDidLoad时,view已经生成了,loadView只会被调用一次,而viewDidLoad可能会被调用多次(View可能会被多次加载),当view被添加到其他view中之前,会调用viewWillAppear,之后会调用viewDidAppear。当view从其他view中移除之前,调用viewWillDisAppear,移除之后会调用viewDidDisappear。当view不再使用时,受到内存警告时,ViewController会将view释放并将其指向为nil。

    ViewController的生命周期中各方法执行流程如下:

    init—>loadView—>viewDidLoad—>viewWillApper—>viewDidApper—>viewWillDisapper—>viewDidDisapper—>viewWillUnload->viewDidUnload—>dealloc

    33. OC中是如何实现线程同步的?

    @synchronized: 添加同步锁

    NSLock:加锁

    NSCondition:加条件锁

    dispatch_async(dispatch_get_main_queue(), ^{}); :异步主线程

    NSOperationQueue:添加线程依赖

    NSOperationQueue:设置最大并发数为1

    36. 编程中,保存数据有哪几种方式?

    NSKeyedArchiver:

    1.1关于数据的持久化存储的几种方式

    说到NSKeyedArchiver,也就先要了解下iOS开发中关于数据持久化存储的几种方式:1.属性列表 2.对象归档 3.数据库存储(SQLite) 4.Apple提供的CoreData存储工具,关于以上存储方式的使用场景和各自的优缺点,我在此就不再赘述了,今天主要谈一谈关于第二类存储方式-对象归档的使用方法和特点。

    1.2什么是对象归档

    归档是一种很常用的文件储存方法,几乎任何类型的对象都能够被归档储存(实际上是一种文件保存的形式)。

    苹果提供了NSKeyedArchiver和NSKeyedUnarchiver两个类以供我们把对象序列化和反序列化,在存储之前使用NSKeyedArchiver进行序列化操作,并且写入本地文件,在使用之前使用NSKeyedUnarchiver进行反序列化的操作,以供提取使用!

    1.3什么场景下会使用到对象归档

    在实际的开发过程中,我们会使用各种数据存储的方式。

    如果是简单的进行一些系统提供的类型,例如NSArray,NSDictionary,NSString,BOOL,NSInteger,NSfloat等基本数据类型或者对象,我们可以选择系统提供的NSUserDefault这个单例,使用简单方便,但是仅仅只能对以上这些特定的数据格式进行存储,是否有些局限性?而且属性属性列表这种方式又是否安全呢?可能这些这些条件NSUserDefault都无法满足!

    对于一些规律性的,量级比较大的数据,又有规律可循的数据,我们可以选择建表或者使用Apple提供的CoreData进行持久化的存储!

    那么如果数据的量级不是很大,没有必要动用数据库或者是CoreData这种大规模的杀伤性武器的时候,而且又对数据的安全性和持久性有那么些要求的时候,我们最好去选择对象序列化这种中等杀伤性工具了!

    1.4对象归档的使用方法

    使用归档的方法对系统提供的基本类型和基本对象进行归档的操作,在这里不再阐述了,如果明白了对自定义对象的归档和解档,那么系统的基本数据类型和基本对象的归档和解档也就相对很easy了!

    具体的使用方法我就借用目前我正在开发维护的代码进行以下说明:

    目前我们产品的需求是在用户登录之后,就持久化存储用户的登陆相关信息,在后续使用中不需要再次登陆。当用户没有退出登录,卸载程序后,重新从App Store中现在app后,同样保持登陆状态。

    如果仅仅是登陆之后的登陆状态,使用NSUserDefault完全可以实现,只是安全性不是太好而已,但是当用户在登陆状态卸载并且重新安装app后,仍然要保持登陆状态的话,那这个问题就值得思考下了!

    基于以上需求,也就是说我们要把用户的登陆信息存储在一个地方,这个地方要满足的条件是1.持久化存储,用户登录后,再次打开app不需要重新进行登录      2.用户在登录的状态下卸载app,再次重新安装后,仍然保持卸载前的登陆状态,也就是要完美重现卸载前的状态!

    基于以上的条件,我们自然而然地联想到苹果的sandBox机制,关于苹果的sandBox机制,我们不再详述,最关键的一点是:在sandBox中的Document目录下存储的文件,会根据用户的appleID同步到apple的服务端,也就是说如果再次安装app的时候,此app中的沙盒(sandBox)的Document目录下的文件会被再次还原(用户的app购买信息是和用户的appleID绑定的),那么需求就被完美的满足了,具体的代码实现以及注意事项请继续向下阅读:

    @interface IHomeSession : NSObject

    @property (nonatomic, strong) NSString *sessionId;              //会话Id

    @property (nonatomic, strong) NSDate   *lastSessionDate;    //记录上一次请求时间

    @property (nonatomic, strong) NSString *token;                   //ut值

    @property (nonatomic, strong) NSString *alias;                    //设备别名

    @property (nonatomic, strong) NSString *username;            //用户名

    //序列化对象的单例

    + (IHomeSession *)sharedMemory;

    //保存

    - (void)save;

    //获取ssesionId

    - (NSString *)getSessionId;

    //重置

    - (void)reset;

    @end

    以上是.h文件

    具体属性可以根据自己的需求进行添加

    提供获取序列化单例的方法,方便在项目全局进行获取和使用

    提供保存(save),重置(reset),获取sessionId(getSessionId)的api接口以供使用,也可以根据自己的需求添加api

    最重要的一点,是当前类需要遵循NSCoding协议,NSCoding协议中有两个方法,都是requred方法,遵循该协议后,必须实现。

    以下是.m文件中NSCoding协议的具体实现

    #pragma mark - NSCoding

    - (void)encodeWithCoder:(NSCoder *)aCoder

    {

    [aCoder encodeObject:_sessionId forKey:@"_sessionId"];

    [aCoder encodeObject:_lastSessionDate forKey:@"_lastSessionDate"];

    [aCoder encodeObject:_token forKey:@"_token"];

    [aCoder encodeObject:_username forKey:@"_username"];

    [aCoder encodeObject:_alias forKey:@"_alias"];

    }

    通过以上编码的方法,对当前类中的属性进行逐一的键值编码!

    - (id)initWithCoder:(NSCoder *)aDecoder

    {

    if (self = [super init])

    {

    _sessionId = [aDecoder decodeObjectForKey:@"_sessionId"];

    _alias = [aDecoder decodeObjectForKey:@"_alias"];

    _lastSessionDate = [aDecoder decodeObjectForKey:@"_lastSessionDate"];

    _token = [aDecoder decodeObjectForKey:@"_token"];

    _username = [aDecoder decodeObjectForKey:@"_username"];

    }

    return self;

    }

    通过以上解码的方法,对当前类中的属性,根据键进行逐一的逆向编码,并返回一个当前类的实例!

    实现了以上的协议方法后,我们就可对当前的类对象进行归档和解档的操作了:

    首先我们规定一个归档文件在沙盒中的存储路径,写在一个类方法中,方便取用,为了满足app重新安装后仍然可以获取到最后一次登陆信息的需求,我们把文件存储在沙盒中的第一个文件夹(Document)中,这样可以在程序重新安装后自动回复,原理我在需求分析上已经做了阐述!

    + (NSString *)path

    {

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *documentDir = [paths objectAtIndex:0];

    NSString *dstPath = [documentDir stringByAppendingPathComponent:@"user.data"];

    return dstPath;

    }

    归档的方法,我们集成在save的接口中:

    - (void)save

    {

    [NSKeyedArchiver archiveRootObject:self toFile:[IHomeSession path]];

    }

    解档的方法我们集成在单例的获取中,一定要先查找对应路径下的文件是否存在,如果存在进行解档操作,不存在的话重新生成一个单例,这样会增强程序的健壮性,防止误取单例,造成程序崩溃!

    + (IHomeSession *)sharedMemory

    {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

    if ([[NSFileManager defaultManager] fileExistsAtPath:[IHomeSession path]]) {

    instance = [NSKeyedUnarchiver unarchiveObjectWithData:

    [NSData dataWithContentsOfFile:[IHomeSession path]]];

    }

    else

    {

    instance = [[IHomeSession alloc] init];

    }

    });

    return instance;

    }

    重置的接口中,我们需要删除本地文件,同时将单例中的各种属性恢复到初始状态,然后将初始状态下的对象保存归档!

    - (void)reset

    {

    [[NSFileManager defaultManager] removeItemAtPath:[IHomeSession path] error:nil];

    instance = [[IHomeSession alloc] init];

    instance.sessionId = nil;

    instance.lastSessionDate = nil;

    instance.token = @"default";

    instance.username = nil;

    instance.alias = nil;

    [instance save];

    }

    例2:

    @implementation person

    #pragma mark 写入文件

    -(void)encodeWithCoder:(NSCoder *)encoder{

    [super encodeWithCoder:encoder];//不要忘了这个

    [encoder encodeInt:self.age forKey:@"age"];

    [encoder encodeObject:self.name forKey:@"name"];

    [encoder encodeFloat:self.height forKey:@"height"];

    }

    #pragma mark 从文件中读取

    -(id)initWithCoder:(NSCoder *)decoder{

    self = [super initWithCoder:decoder];//不要忘了这个

    self.age = [decoder decodeIntForKey:@"age"];

    self.name = [decoder decodeObjectForKey:@"name"];

    self.height = [decoder decodeFloatForKey:@"height"];

    return self;

    }

    //创建

    -(void)createPerson{

    person *p = [[[person alloc] init] autorelease];

    p.age = 20;

    p.name = @"Rio";

    p.height =1.75f;

    //获得Document的路径

    NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

    NSString *path = [documents stringByAppendingPathComponent:@"person.archiver"];//拓展名可以自己随便取

    [NSKeyedArchiver archiveRootObject:p toFile:path];

    }

    //读取

    -(void)readPerson{

    NSString *documents = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

    NSString *path = [documents stringByAppendingPathComponent:@"person.archiver"];

    person *person1 = [NSKeyedUnarchiver unarchiveObjectWithFile:path];

    NSLog(@"%@",person1);

    }

    特点:

    可以存储自定义模型对象

    NSKeyedArchiver归档相对较plist存储而言,它可以直接存储自定义模型对象,而plist文件需要将模型转为字典才可以存储自定义对象模型;

    2.归档不能存储大批量数据(相比较Sqlite而言),存储数据到文件是将所有的数据一下子存储到文件中,从文件中读取数据也是一下子读取所有的数据;

    缺点:

    假如你的文件中有100个对象了,然后你想在利用归档添加一个对象,你需要先把所有的数据解档出来,然后再加入你想添加的那个对象,同理,你想删除一个文件中的一个对象也是,需要解档出所有的对象,然后将其删除。性能低这样处理

    4.1 基本使用:需要归档的模型类必须要遵守NSCoding协议,然后模型实现类中必须实现两个方法:1>encodeWithCoder ->归档;2> initWithCoder:  - >解档

    4.2 使用注意:

    如果父类也遵守了NSCoding协议,请注意:

    应该在encodeWithCoder:方法中加上一句[superencodeWithCode:encode];// 确保继承的实例变量也能被编码,即也能被归档应该在initWithCoder:方法中加上一句self = [superinitWithCoder:decoder];// 确保继承的实例变量也能被解码,即也能被恢复

    plist文件读与写:

    通过代码来创建plist文件,代码如下:

    //建立文件管理

    NSFileManager *fm = [NSFileManager defaultManager];

    //找到Documents文件所在的路径

    NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUSErDomainMask, YES);

    //取得第一个Documents文件夹的路径

    NSString *filePath = [path objectAtIndex:0];

    //把TestPlist文件加入

    NSString *plistPath = [filePath stringByAppendingPathComponent:@"test.plist"];

    //开始创建文件

    [fm createFileAtPath:plistPath contents:nil attributes:nil];

    //删除文件

    [fm removeItemAtPath:plistPath error:nil];

    在写入数据之前,需要把要写入的数据先写入一个字典中,创建一个dictionary:

    //创建一个字典

    NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:@"zhangsan",@"1",@"lisi",@"2", nil];

    //把数据写入plist文件

    [dic writeToFile:plistPath atomically:YES];

    读取plist中的数据,形式如下:

    //读取plist文件,首先需要把plist文件读取到字典中

    NSDictionary *dic2 = [NSDictionary dictionaryWithContentsOfFile:plistPath];

    //打印数据

    NSLog(@"key1 is %@",[dic2 valueForKey:@"1"]);

    NSLog(@"dic is %@",dic2);

    关于plist中的array读写,代码如下:

    //把TestPlist文件加入

    NSString *plistPaths = [filePath stringByAppendingPathComponent:@"tests.plist"];

    //开始创建文件

    [fm createFileAtPath:plistPaths contents:nil attributes:nil];

    //创建一个数组

    NSArray *arr = [[NSArray alloc] initWithObjects:@"1",@"2",@"3",@"4", nil];

    //写入

    [arr writeToFile:plistPaths atomically:YES];

    //读取

    NSArray *arr1 = [NSArray arrayWithContentsOfFile:plistPaths];

    //打印

    NSLog(@"arr1is %@",arr1);

    偏好设置:

    //1.获取NSUserDefaults对象

    NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];

    //2保存数据(如果设置数据之后没有同步, 会在将来某一时间点自动将数据保存到Preferences文件夹下面)

    [defaults setObject:@"yangyong" forKey:@"name"];

    [defaults setInteger:23 forKey:@"age"];

    [defaults setDouble:1.73f forKey:@"height"];

    [defaults setObject:@"man" forKey:@"gender"];

    //3.强制让数据立刻保存

    [defaults synchronize];

    (1)偏好设置是专门用来保存应用程序的配置信息的, 一般情况不要在偏好设置中保存其他数据。如果利用系统的偏好设置来存储数据, 默认就是存储在Preferences文件夹下面的,偏好设置会将所有的数据都保存到同一个文件中。

    (2)使用偏好设置对数据进行保存之后, 它保存到系统的时间是不确定的,会在将来某一时间点自动将数据保存到Preferences文件夹下面,如果需要即刻将数据存储,可以使用[defaults synchronize];

    设置数据时,synchornize方法强制写入

    UserDefaults设置数据时,不是立即写入,而是根据时间戳定时地把缓存中的数据写入本地磁盘。所以调用了set方法之后数据有可能还没有写入磁盘应用程序就终止了。出现以上问题,可以通过调用synchornize方法强制写入

    (3)注意点:所有的信息都写在一个文件中,对比简单的plist可以保存和读取基本的数据类型。

    好处:

    1.存储数据不需要关心文件名称

    2.快速存储键值对

    底层实现:

    它其实就是一个字典

    Write写入方式:永久保存在磁盘中。具体方法为:

    第一步:获得文件即将保存的路径:

    NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);//使用C函数NSSearchPathForDirectoriesInDomains来获得沙盒中目录的全路径。该函数有三个参数,目录类型、User domain mask、布尔值。其中布尔值表示是否需要通过~扩展路径。而且第一个参数是不变的,即为NSSearchPathDirectory 。在iOS中后两个参数也是不变的,即为:NSUserDomainMask 和 YES。

    NSString *ourDocumentPath =[documentPaths objectAtIndex:0];

    还有一种方法是使用NSHomeDirectory函数获得sandbox的路径。具体的用法为:

    NSString *sandboxPath = NSHomeDirectory();

    // Once you have the full sandbox path, you can create a path from it,但是不能在sandbox的本文件层上写文件也不能创建目录,而应该是此基础上创建一个新的可写的目录,例如Documents,Library或者temp。

    NSString *documentPath = [sandboxPath stringByAppendingPathComponent:@"Documents"];//将Documents添加到sandbox路径上,具体原因前面分析了!

    这两者的区别就是:使用NSSearchPathForDirectoriesInDomains比在NSHomeDirectory后面添加Document更加安全。因为该文件目录可能在未来发送的系统上发生改变。

    第二步:生成在该路径下的文件:

    NSString *FileName=[documentDirectory stringByAppendingPathComponent:fileName];//fileName就是保存文件的文件名

    第三步:往文件中写入数据:

    [data writeToFile:FileName atomically:YES];//将NSData类型对象data写入文件,文件名为FileName

    最后:从文件中读出数据:

    NSData data=[NSData dataWithContentsOfFile:FileName options:0 error:NULL];//从FileName中读取出数据

    Sqlite:

    生成路径

    +(NSString*)path{

    NSArray*documentArr =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);NSString*documentPath = [documentArr firstObject];// crylown.db 为数据库的名字NSString*path = [NSString stringWithFormat:@"%@/crylown.db",documentPath];returnpath;}

    创建/打开数据库

    sqlite3 *database;

    int databaseResult = sqlite3_open([[selfpath] UTF8String], &database);

    if(databaseResult != SQLITE_OK) {

    NSLog(@"创建/打开数据库失败,%d",databaseResult);

    }

    创建表

    char*error;//    建表格式: create table if not exists 表名 (列名 类型,....)    注: 如需生成默认增加的id: id integer primary key autoincrement

    const char*createSQL ="create table if not exists list(id integer primary key autoincrement,name char,sex char)";

    int tableResult = sqlite3_exec(database, createSQL, NULL, NULL, &error);

    if(tableResult != SQLITE_OK) {   

         NSLog(@"创建表失败:%s",error); 

       }

    添加数据

    // 对SQL语句执行预编译intsqlite3_prepare(sqlite3 *db,constchar*sql,intbyte,sqlite3_stmt **stmt,constchar**tail)

    1.db代表打开的数据库连接

    2.sql代表的sql语句

    3.byte代表SQL语句的最大长度

    4.传出参数,指向预编译SQL语句产生的sqlite3_stmt

    5.指向SQL语句中未使用的部分

    int sqlite3_prapare_v2()版本,代表该函数的最新版本。

    //  添加//  sql语句格式: insert into 表名 (列名)values(值)

    constchar*insertSQL ="insert into haha (name,sex)values('iosRunner','male')";

    int insertResult = sqlite3_prepare_v2(database, insertSQL,-1, &stmt,nil);

    if(insertResult != SQLITE_OK) {

    NSLog(@"添加失败,%d",insertResult);      

      }else{//        

      执行sql语句sqlite3_step(stmt);     

       }

    查找数据

    //返回sqlite3_stmt(预编译SQL语句产生的结果)const char* sqlite3_colum_int/text...(sqlite3_stmt *,intN)

    根据结果返回的值的类型不同选择int/text等,N代表列名在表中的位置。

    //    查找//  sql语句格式: select 列名from表名 where 列名 = 参数      注:前面的列名为查询结果里所需要看到的 列名,后面的 列名 = 参数 用于判断删除哪条数据

    const char*searchSQL ="select id,name,sex from haha where name = 'puyun2'";

    intsearchResult = sqlite3_prepare_v2(database, searchSQL, -1, &stmt,nil);

    if(searchResult !=SQLITE_OK) {

    NSLog(@"查询失败,%d",searchResult);       

     }else{

    while(sqlite3_step(stmt) ==SQLITE_ROW) {          // 查询的结果可能不止一条,直到 sqlite3_step(stmt) !=SQLITE_ROW,查询结束。

    int idWord = sqlite3_column_int(stmt,0);

    char *nameWord = (char*) sqlite3_column_text(stmt,1);

    char*sexWord = (char*)sqlite3_column_text(stmt,2);

    NSLog(@"%d,%s,%s",idWord,nameWord,sexWord);        

        }       

     }

    修改数据

    // 修改      // sql语句格式: update 表名set列名 = 新参数 where 列名 = 参数  注:前面的 列名 = 新参数 是修改的值, 后面的 列名 = 参数 用于判断删除哪条数据

    const char*changeSQL ="update haha set name = 'buhao' where name = 'iosRunner'";

    int updateResult = sqlite3_prepare_v2(database, changeSQL, -1, &stmt,nil);

    if(updateResult !=SQLITE_OK) {

    NSLog(@"修改失败,%d",updateResult);     

       }else{            

    sqlite3_step(stmt);    

        }

    删除数据

    //        删除//        sql语句格式: deletefrom表名 where 列名 = 参数    注:后面的 列名 = 参数 用于判断删除哪条数据

    const char*deleteSQL ="delete from haha where name = 'iosRunner'";

    intdeleteResult = sqlite3_prepare_v2(database, deleteSQL, -1, &stmt,nil);

    if(deleteResult !=SQLITE_OK) {

    NSLog(@"删除失败,%d",deleteResult);      

      }else{        

        sqlite3_step(stmt); 

           }

    结束处理

    //        销毁stmt,回收资源

    sqlite3_finalize(stmt);

    //    关闭数据库

    sqlite3_close(database);

    注意:写入数据库,字符串可以采用char方式,而从数据库中取出char类型,当char类型有表示中文字符时,会出现乱码。这是因为数据库默认使用ascII编码方式。所以要想正确从数据库中取出中文,需要用NSString来接收从数据库取出的字符串。

    NSfileManager:

    /**

    获取Documents路径

    @return 返回Documents路径

    */

    - (NSString *)getDocumentsPath {

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

    NSString *path = [paths objectAtIndex:0];

    return path;

    }

    /**

    创建文件夹

    @param folderName 文件夹的名字

    */

    - (void)createDirectoryWithFolderName:(NSString *)folderName {

    NSString *documentsPath =[self getDocumentsPath];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSString *iOSDirectory = [documentsPath stringByAppendingPathComponent:folderName];

    BOOL isSuccess = [fileManager createDirectoryAtPath:iOSDirectory withIntermediateDirectories:YES attributes:nil error:nil];

    if (isSuccess) {

    [self remindMessage:CreateFolderSuccess];

    } else {

    [self remindMessage:CreateFolderFail];

    }

    }

    /**

    创建文件

    @param name 文件的名字

    */

    - (void)createFileWithName:(NSString *)name {

    NSString *documentsPath =[self getDocumentsPath];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSString *iOSPath = [documentsPath stringByAppendingPathComponent:name];

    BOOL isSuccess = [fileManager createFileAtPath:iOSPath contents:nil attributes:nil];

    if (isSuccess) {

    [self remindMessage:CreateFileSuccess];

    } else {

    [self remindMessage:CreateFileFail];

    }

    }

    /**

    往文件中写内容

    @param name 文件的名字

    @param content 要写入的内容

    */

    - (void)writeFileWithName:(NSString *)name withContent:(NSString *)content {

    NSString *documentsPath = [self getDocumentsPath];

    NSString *iOSPath = [documentsPath stringByAppendingPathComponent:name];

    BOOL isSuccess = [content writeToFile:iOSPath atomically:YES encoding:NSUTF8StringEncoding error:nil];

    if (isSuccess) {

    [self remindMessage:WriteFileSuccess];

    } else {

    [self remindMessage:WriteFileFail];

    }

    }

    /**

    读取文件内容

    @param name 需要读取的文件的名字

    @return 返回读取的内容

    */

    - (NSString *)readFileContentWithName:(NSString *)name {

    NSString *documentsPath =[self getDocumentsPath];

    NSString *iOSPath = [documentsPath stringByAppendingPathComponent:name];

    NSString *content = [NSString stringWithContentsOfFile:iOSPath encoding:NSUTF8StringEncoding error:nil];

    return content;

    }

    /**

    判断文件是否存在

    @param filePath 文件路径

    @return 返回BOOL值

    */

    - (BOOL)isExistAtPath:(NSString *)filePath {

    NSFileManager *fileManager = [NSFileManager defaultManager];

    BOOL isExist = [fileManager fileExistsAtPath:filePath];

    if (!isExist) {

    [self remindMessage:NoFileExist];

    }

    return isExist;

    }

    /**

    判断文件是否存在,如果不存在,则拷贝

    @param fileName 文件的名字

    */

    - (void)isExistFileWithName:(NSString *)fileName {

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSString *filePath = [[self getDocumentsPath] stringByAppendingPathComponent:fileName];

    if(![fileManager fileExistsAtPath:filePath]) { //如果不存在

    NSLog(@"xxx.txt is not exist");

    NSString *nameStr = [NSString stringWithFormat:@"/%@",fileName];

    NSString *dataPath = [[[NSBundle mainBundle] bundlePath] stringByAppendingString:nameStr];//获取程序包中相应文件的路径

    NSError *error;

    if ([fileManager copyItemAtPath:dataPath toPath:filePath error:&error]) { //拷贝

    [self remindMessage:CopyFileSuccess];

    } else {

    [self remindMessage:CopyFileFail];

    }

    }

    }

    /**

    计算文件大小

    @param filePath 文件路径

    @return 返回文件大小

    */

    - (unsigned long long)fileSizeAtPath:(NSString *)filePath {

    NSFileManager *fileManager = [NSFileManager defaultManager];

    BOOL isExist = [fileManager fileExistsAtPath:filePath];

    if (isExist) {

    unsigned long long fileSize = [[fileManager attributesOfItemAtPath:filePath error:nil] fileSize];

    return fileSize;

    } else {

    [self remindMessage:NoFileExist];

    return 0;

    }

    }

    /**

    计算整个文件夹中所有文件大小

    @param folderPath 文件夹路径

    @return 返回文件夹中所有文件大小

    */

    - (unsigned long long)folderSizeAtPath:(NSString*)folderPath {

    NSFileManager *fileManager = [NSFileManager defaultManager];

    BOOL isExist = [fileManager fileExistsAtPath:folderPath];

    if (isExist) {

    NSEnumerator *childFileEnumerator = [[fileManager subpathsAtPath:folderPath] objectEnumerator];

    unsigned long long folderSize = 0;

    NSString *fileName = @"";

    while ((fileName = [childFileEnumerator nextObject]) != nil) {

    NSString *fileAbsolutePath = [folderPath stringByAppendingPathComponent:fileName];

    folderSize += [self fileSizeAtPath:fileAbsolutePath];

    }

    return folderSize / (1024.0 * 1024.0);

    } else {

    [self remindMessage:NoFileExist];

    return 0;

    }

    }

    /**

    删除文件

    @param name 需要删除的文件名字

    */

    - (void)deleteFileWithName:(NSString *)name {

    NSString *documentsPath =[self getDocumentsPath];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSString *iOSPath = [documentsPath stringByAppendingPathComponent:name];

    BOOL isSuccess = [fileManager removeItemAtPath:iOSPath error:nil];

    if (isSuccess) {

    [self remindMessage:DeleteFileSuccess];

    } else {

    [self remindMessage:DeleteFileFail];

    }

    }

    /**

    移动文件

    @param name 需要移动的文件名字

    */

    - (void)moveFileWithName:(NSString *)name {

    NSString *documentsPath =[self getDocumentsPath];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSString *filePath = [documentsPath stringByAppendingPathComponent:name];

    NSString *moveToPath = [documentsPath stringByAppendingPathComponent:name];

    BOOL isSuccess = [fileManager moveItemAtPath:filePath toPath:moveToPath error:nil];

    if (isSuccess) {

    [self remindMessage:MoveFileSuccess];

    } else {

    [self remindMessage:MoveFileFail];

    }

    }

    /**

    文件重命名

    @param name1 需要重命名的文件名字

    @param name2 作为重命名的文件名字

    */

    - (void)renameFileName:(NSString *)name1 willChangeFileName:(NSString *)name2 {

    //通过移动该文件对文件重命名

    NSString *documentsPath = [self getDocumentsPath];

    NSFileManager *fileManager = [NSFileManager defaultManager];

    NSString *filePath = [documentsPath stringByAppendingPathComponent:name1];

    NSString *moveToPath = [documentsPath stringByAppendingPathComponent:name2];

    BOOL isSuccess = [fileManager moveItemAtPath:filePath toPath:moveToPath error:nil];

    if (isSuccess) {

    [self remindMessage:RenameFileSuccess];

    } else {

    [self remindMessage:RenameFileFail];

    }

    }

    38. OC中创建线程的方法是什么?如果在主线程中执行代码,方法是什么?如果想延时执行代码,方法又是什么?

    方法一:

    NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(mutableThread) object:nil];

    方法二:

    [NSThread detachNewThreadSelector:@selector(mutableThread) toTarget:self withObject:nil];

    方法三:

    [self performSelectorInBackground:@selector(mutableThread) withObject:nil];

    方法四:多线程blog创建

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

    //会开启一个多线程

    [operationQueue addOperationWithBlock:^{

    for(int i = 0; i < 50 ;i++)

    {

    NSLog(@"多线程:%d",i);

    }

    }];

    方法五:

    //相当于是一个线程池

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];

    operationQueue.maxConcurrentOperationCount = 1;//设置并发数

    //创建线程

    NSInvocationOperation *opertion1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(thread1) object:nil];

    //设置线程的优先级

    [opertion1 setQueuePriority:NSOperationQueuePriorityVeryLow];

    //创建另一个线程

    NSInvocationOperation *opertion2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(thread2) object:nil];

    [opertion2 setQueuePriority:NSOperationQueuePriorityHigh];

    //NSOperation就是一个操作单元,用来执行方法,是一个抽象类,必须子类化或者使用系统创建好的子类(NSInvocationOperation or NSBlockOperation)

    // //NSOperation是最小的操作单元;只能够执行一次;

    // //NSInvocationOperation第一步:创建

    NSInvocationOperation *invocation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(banZhuanPlus) object:nil];

    // //第二步:(不设置的话不添加到队列)在主线程中执行

    // [invocation start];

    //NSBlockOperation第一步:创建

    NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{

    [self banZhuanPlus];

    }];

    // //第二步:执行(在主线程中执行)

    // [block start];//如果添加到队列就不要start了,如果不添加,当前线程就是在主线程中执行,如果添加,就不是在主线程了

    // 这个队列会自动帮咱们创建一个辅助的线程,这个时候当前线程就不是主线程了

    //这个队列里面只能够添加NSOperation以及子类的对象;

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    [queue setMaxConcurrentOperationCount:2];//设置最大并行数;

    [queue addOperation:block];//只要把操作队列添加到队列中就会执行;

    [queue addOperation:invocation];

    方法六:

    dispatch_queue_t queue = dispatch_queue_create("test",NULL);

    dispatch_async(queue,^{

    for(int i=0;i<50;i++)

    {

    NSLog(@"多线程:%d",i);

    });

    创建线程的方法:

    - [NSThread detachNewThreadSelector:nil toTarget:nil withObject:nil]

    - [self performSelectorInBackground:nil withObject:nil];

    - [[NSThread alloc] initWithTarget:nil selector:nil object:nil];

    - dispatch_async(dispatch_get_global_queue(0, 0), ^{});

    - dispatch_sync(dispatch_get_global_queue(0, 0), ^{});

    - [[NSOperationQueue new] addOperation:nil];

    主线程中执行代码的方法:

    -[self performSelectorOnMainThread:@selector() withObject:nil waitUntilDone:NO];

    - dispatch_async(dispatch_get_main_queue(), ^{});//还有异步

    -[[NSOperationQueue mainQueue] addOperationWithBlock:^{

    // UI更新代码

    }];

    延迟执行代码

    NSTimer启动定时器

    sleep(2)  睡两秒钟

    + (void)sleepUntilDate:(NSDate *)date;

    + (void)sleepForTimeInterval:(NSTimeInterval)ti;

    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]];

    [NSThread sleepForTimeInterval:1.0f];

    1.performSelector方法

    [self performSelector:@selector(delayMethod) withObject:nil afterDelay:1.0f];

    此方式要求必须在主线程中执行,否则无效。

    是一种非阻塞的执行方式,

    暂时未找到取消执行的方法。

    2.定时器:NSTimer

    [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];

    此方式要求必须在主线程中执行,否则无效。

    是一种非阻塞的执行方式,

    可以通过NSTimer类的- (void)invalidate;取消执行。

    3. sleep方式

    [NSThread sleepForTimeInterval:1.0f]; [self delayMethod];

    此方式在主线程和子线程中均可执行。

    是一种阻塞的执行方式,建方放到子线程中,以免卡住界面

    没有找到取消执行的方法。

    GCD延迟执行

    double delayInSeconds = 1.0;

    __block ViewController* bself = self;

    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));

    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

    [bself test1]; });

    此方式在可以在参数中选择执行的线程。

    是一种非阻塞的执行方式,

    没有找到取消执行的方法。

    39. iOS中有哪些多线程方案?

    常用的有三种: NSThread NSOperationQueue GCD。

    1、NSThread 是这三种范式里面相对轻量级的,但也是使用起来最负责的,

    你需要自己管理thread的生命周期,线程之间的同步。线程共享同一应用程序的部分内存空间,

    它们拥有对数据相同的访问权限。你得协调多个线程对同一数据的访问,

    一般做法是在访问之前加锁,这会导致一定的性能开销。

    2、NSOperationQueue 以面向对象的方式封装了用户需要执行的操作,

    我们只要聚焦于我们需要做的事情,而不必太操心线程的管理,同步等事情,

    因为NSOperation已经为我们封装了这些事情。

    NSOperation 是一个抽象基类,我们必须使用它的子类。

    3、 GCD: iOS4 才开始支持,它提供了一些新的特性,以及运行库来支持多核并行编程,

    它的关注点更高:如何在多个cpu上提升效率。

    总结:

    - NSThread是早期的多线程解决方案,实际上是把C语言的PThread线程管理代码封装成OC代码。

    - GCD是取代NSThread的多线程技术,C语法+block。功能强大。

    - NSOperationQueue是把GCD封装为OC语法,额外比GCD增加了几项新功能。

    * 最大线程并发数

    * 取消队列中的任务

    * 暂停队列中的任务

    * 可以调整队列中的任务执行顺序,通过优先级

    * 线程依赖

    * NSOperationQueue支持KVO。 这就意味着你可以观察任务的状态属性。

    但是NSOperationQueue的执行效率没有GCD高,所以一半情况下,我们使用GCD来完成多线程操作。

    相关文章

      网友评论

          本文标题:知识点2

          本文链接:https://www.haomeiwen.com/subject/jbpckxtx.html