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中释放这些引用.
在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来完成多线程操作。
网友评论