分类目录归档:iOS

iOS开发入门和提高::::::演道网IOS专栏提供一线IOS研发人员在工作学习过程中的经验总结。让大家少走弯路。

iOS数据持久化之归档NSKeyedArchiver

IOS数据持久化的方式分为三种:

  1. 属性列表 (自定义的Property List 、NSUSErDefaults)
  2. 归档 (NSKeyedArchiver)
  3. 数据库 (SQLite、Core Data、第三方类库等)

  下面主要来介绍一个归档NSKeyedArchiver

   归档(又名序列化),把对象转为字节码,以文件的形式存储到磁盘上;程序运行过程中或者当再次重写打开程序的时候,可以通过解归档(反序列化)还原这些对象。

   归档方式:

  • 对Foundation框架中对象进行归档
  • 对自定义的内容进行归档
  • 对自定义的对象进行归档

<一> 对Foundation框架中对象进行归档

 
//获得文件路径
    NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *filePath = [documentPath stringByAppendingPathComponent:@"file.archiver"];
    
//归档(序列化)
NSArray *archiveAry = @[@"jereh",@"ios"];
if ([NSKeyedArchiver archiveRootObject: archiveAry toFile:filePath]) {
        NSLog(@"Archiver  success");
}
    
//解归档(反序列化)
NSArray *unArchiveAry = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(@"%@",unArchiveAry);
 

  小结:

  1. 归档和解归档操作步骤简单
  2. 一次只能归档一个对象,如果是多个对象归档需要分开进行
  3. 归档的对象是Foundation框架中的对象
  4. 归档和解归档其中任意对象都需要归档和解归档整个文件
  5. 归档后的文件是加密的,所以归档文件的扩展名可以随意取

<二> 对自定义的内容进行归档

 
//获得文件路径
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [documentPath stringByAppendingPathComponent:@"file.archiver"];
    
//1. 使用NSData存放归档数据
NSMutableData *archiverData = [NSMutableData data];
//2. 创建归档对象
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:archiverData];
//3. 添加归档内容 (设置键值对)
 [archiver encodeObject:@"jereh" forKey:@"name"];
 [archiver encodeInt:20 forKey:@"age"];
 [archiver encodeObject:@[@"ios",@"oc"] forKey:@"language"];
//4. 完成归档
 [archiver finishEncoding];
//5. 将归档的信息存储到磁盘上
if ([archiverData writeToFile:filePath atomically:YES]) {
     NSLog(@"archiver success");
}
    
//解归档
//1. 从磁盘读取文件,生成NSData实例
NSData *unarchiverData = [NSData dataWithContentsOfFile:filePath];
//2. 根据Data实例创建和初始化解归档对象
NSKeyedUnarchiver *unachiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:unarchiverData];
//3. 解归档,根据key值访问
NSString *name = [unachiver decodeObjectForKey:@"name"];
int age = [unachiver decodeIntForKey:@"age"];
NSArray *ary = [unachiver decodeObjectForKey:@"language"];
NSLog(@"name=%@ age=%i ary=%@",name,age,ary);
 

  小结:

  1. 在带键的归档中,每个归档字段都有一个key值,解归档时key值要与归档时key值匹配
  2. 带键归档可以一次存储多个对象
  3. 归档的对象是Foundation框架中的对象
  4. 归档和解归档其中任意对象都需要归档和解归档整个文件
  5. 归档后的文件是加密的,所以归档文件的扩展名可以随意取

<三> 对自定义的对象进行归档

 
#define IDNUM @"idNum"
#define NAME @"name"

@interface Student : NSObject 
@property (nonatomic, assign) int idNum;
@property (nonatomic, copy) NSString *name;
@end

@implementation Student

#pragma mark 编码 对对象属性进行编码的处理
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeInt:_idNum forKey:IDNUM];
    [aCoder encodeObject:_name forKey:NAME];
}

#pragma mark 解码 解码归档数据来初始化对象
- (id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {
        _idNum = [aDecoder decodeIntForKey:IDNUM];
        _name = [aDecoder decodeObjectForKey:NAME];
    }
    return self;
}
@end
 

  小结:

  1. 自定义对象与自定义内容归档和解归档步骤和用法完全相同
  2. 自定义的对象归档需要实现NSCoding协议,并且实现协议中的方法
  3. NSCoding协议中有两个方法:
    •   encodeWithCoder方法对对象属性进行编码,在对象归档时调用
    •   initWithCoder方法解码归档数据来初始化对象,在对象解归档时调用

 

  总结:

  1. 归档和解归档可以用于少量数据的持久化存储和读取
  2. 属性列表只能存储Foundation框架中的对象,归档除了可以归档Foundation框架中的对象以外,还可以归档实现了NSCoding协议的自定义对象
  3. 通过归档创建的文件是加密的

ios可借鉴的blog

有用的文章:

调试系列:

http://www.cocoachina.com/applenews/devnews/2013/0725/6677.html

http://blog.csdn.net/dean19900504/article/details/8839125

 

http://blog.sina.com.cn/s/blog_6a35a0f60101eeu3.html

http://onevcat.com/2012/06/arc-hand-by-hand/

http://stackoverflow.com/questions/18969988/getting-errors-when-trying-to-upload-an-app-using-application-loader

ios 国际化

http://blog.csdn.net/tjsjping/article/details/6885020

汉字获取拼音

http://www.cnblogs.com/v2m_/archive/2012/01/17/2324822.html

keyframe几个计算模式

These constants are used by the calculationMode property.

NSString * const kCAAnimationLinear;

从一个状态动画切到另一个状态。

NSString * const kCAAnimationDiscrete;

从一个状态 直接切到另一个状态。。中间没动画效果。

NSString * const kCAAnimationPaced;

NSString * const kCAAnimationCubic;

NSString * const kCAAnimationCubicPaced;

原文地址:http://www.cnblogs.com/smileEvday/archive/2012/03/07/MemoryWarning.html

我们都知道在移动设备上很多资源都是比较紧缺的,尤其时内存,通常都比较小,iPhone4也才只有512MB。而且IOS4.0以后还支持了多任务,这个问题就更加突出了。因此我们在平时设计程序的时候要注意管理好内存,减少不必要的开销,谨防泄露。

由于写的一个小项目存在严重的内存泄漏,程序经常运行时间不长就退出了,调试时候发现运行过程中接受到系统的Memry warning level 1几次以后,紧接着收到一个Memory warning level 2,然后程序退出。通常使用xcode4自带的工具,跟踪发现由于一个图像资源忘记release导致。问题解决了,但我没有打算就此带过。接着上网查找了有关Memory warning Level的资料,后来在stackoverflow上面找到一些有用的数据,这是KennyTM写的,跟大家分享一下:

系统有四种内存警告,定义如下:

typedef enum {

OSMemoryNotificationLevelAny      = -1,

OSMemoryNotificationLevelNormal   =  0,

OSMemoryNotificationLevelWarning  =  1,

OSMemoryNotificationLevelUrgent   =  2,

OSMemoryNotificationLevelCritical =  3

} OSMemoryNotificationLevel;

但是这几种警告之间是怎么变化的,到达什么阈值时会从一个level跳到另外一个level,文章中没有提及。

通常我们在程序中接收到最多的就是Memory warning level 1,这个时候就证明系统内存紧张了,我们的程序必须做出相应,释放不必要的内存。通常如果我们处理得当的话,内存警告就会到此停止,恢复正常状态。如果我们在接收到memory warning level 1以后坐视不理的话,系统可能还会再尝试通知几次level 1,如果还是不去处理的话,系统将会发出更高一级的内存警告 level 2,通常的结果就是我们的App被强制退出,系统收回内存。当然系统在发出level 2的警告时,也会取尝试做一些清理内存的事,例如关闭不必要的后台程序等。很显然我们不该寄希望于系统自己清理,这就好比你的胳膊被划破了,身体肯定会有自修复功能,但是如果伤口比较大的话,肯定还是需要人工处理一下的。

到目前位置我还没有见过level 3的警告,根据stack over flow上面讲的“当发生level 3警告时,系统内核将会接管,很有可能关掉IOS的主界面进程(SpringBorad),甚至会重启”,照这个意思来说我们程序里面接收不到,也实属正常,系统自己的东西都被干掉了,我们肯定早被kill了。

KennyTM写的原文地址: http://stackoverflow.com/questions/2915247/iphone-os-memory-warnings-what-do-the-different-levels-mean

如果打开上面的连接,可以看到定义OSMemoryNotificationLevel的源文件,你可能会注意到里面有一个函数OSMemoryNotificationCurrentLevel()可以用来获取当前内存告警level,不过需要加头文件#import <libkern/OSMemoryNotification.h>,网上有个查看当前内存数据的开源代码MemWatcher,大家可以看看,说实话我没有看懂。

说了这么多,希望能帮大家弄清楚内存警告是怎么回事儿,通常我们程序是不会碰到内存警告,平时写代码注意在所有alloc,retain,copy的地方,对应的写上release,或者直接创建autorelease对象(个人不推荐这么做),发布前养成检查内存泄露的好习惯。

顺便提一下如果运行过程中遇到内存警告的话,程序通常情况下都先调用AppDelegate中的applicationDidReceiveMemoryWarning, 然后程序会通知各ViewController,调用其didRecieveMemoryWarning方法,这个时候我们一定要种,释放不必要的资源

转载:ios 内存不足的处理(ios6 与 ios 6之前分部处理)

原文链接:http://blog.csdn.net/tangaowen/article/details/8511399

自从iPhone4 支持多任务后,我们需要更加仔细处理内存不足的情形。如果用户运行我们程序的时候,后台还跑着N个软件,那前台运行的iphone 程序就很容易收到内存不足的警告。

通常情况下,iOS在内存不足时会给用户一次处理内存资源的机会。当我们的程序在第一次收到内存不足警告时,应该释放一些不用的资源,以节省部分内存。否则,当内存不足情形依然存在,iOS再次向我们程序发出内存不足的警告时,我们的程序将会被iOS kill掉。
iOS的UIViewController 类给我们提供了处理内存不足的接口。在iOS 3.0 之前,当系统的内存不足时,UIViewController的didReceiveMemoryWarining 方法会被调用,我们可以在didReceiveMemoryWarining 方法里释放掉部分暂时不用的资源。

从iOS3.0 开始,UIViewController增加了viewDidUnload方法。该方法和viewDIdLoad相配对。当系统内存不足时,首先UIViewController的didReceiveMemoryWarining 方法会被调用,而didReceiveMemoryWarining 会判断当前ViewController的view是否显示在window上,如果没有显示在window上,则didReceiveMemoryWarining 会自动将viewcontroller 的view以及其所有子view全部销毁,然后调用viewcontroller的viewdidunload方法。如果当前UIViewController的view显示在window上,则不销毁该viewcontroller的view,当然,viewDidunload也不会被调用了。

 

但是到了ios6.0之后,这里又有所变化,ios6.0内存警告的viewDidUnload 被屏蔽,即又回到了ios3.0的时期的内存管理方式。
iOS3-iOS6.0以前版本收到内存警告:
调用didReceiveMemoryWarning内调用super的didReceiveMemoryWarning会将controller的view进行释放。所以我们不能将controller的view再次释放。
处理方法:
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];//如没有显示在window上,会自动将self.view释放。
// ios6.0以前,不用在此做处理,self.view释放之后,会调用下面的viewDidUnload函数,在viewDidUnload函数中做处理就可以了。
}
-(void)viewDidUnload
{
// Release any retained subviews of the main view.不包含self.view
[super viewDidUnload];

//处理一些内存和资源问题。
}

iOS6.0及以上版本的内存警告:
调用didReceiveMemoryWarning内调用super的didReceiveMemoryWarning调只是释放controller的resouse,不会释放view
处理方法:
-(void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];//即使没有显示在window上,也不会自动的将self.view释放。
// Add code to clean up any of your own resources that are no longer necessary.

// 此处做兼容处理需要加上ios6.0的宏开关,保证是在6.0下使用的,6.0以前屏蔽以下代码,否则会在下面使用self.view时自动加载viewDidLoad

float sysVer =[[[UIDevicecurrentDevice] systemVersion] floatValue];

if (sysVer>= 6.0f)

{

if ([self.view window] == nil)// 是否是正在使用的视图
{
// Add code to preserve data stored in the views that might be
// needed later.

// Add code to clean up other strong references to the view in
// the view hierarchy.
self.view = nil;// 目的是再次进入时能够重新加载调用viewDidLoad函数。
}

}

}

关于协议和代理

作者QQ:415074476

QQ群:191280586

协议其实只是声明系列方法,可以简单地理解成接口声明。当类支持这个协议的时候,意味着这个类可能会支持这些里面的接口。

代理是个委托关系,把自己的事请委托给另一个类来做。

 

目前工程里面总有类似的代码:a类需要b类做点事,做完之后反馈结果给a, a再做更进一步的处理。

 

总结起来是两件事:

1,a委托b做事。

2,a需求一个回调。

 

为此我们项目里会做三个事:a类, b类,以及为了支持回调的协议c。

 

没错,你看出来了,我们定义的协议仅是为了支持回调,大材小用啊。仅是使用block对象就能做的事,我们居然定义了一个协议。

 

这不是错误,只是不够优雅。那么什么时候改用协议,什么时候改用block呢?

我观察了cocoa里面的几个类: NSURLConnection,  CLGeocoder, NSXMLParser, UIView(UIViewAnimationWithBlocks).

回调型的都是用的block, 交互型的都是delegate.

 

观察下地址反解析的两个类:

5.0之前是CLLocationCoordinate2D,使用的是协议。

5.0之手CLGeocoder,使用的是Block.

 

可见apple也是认为单纯为了支持回调定义一个协议,是不够优雅简洁的。

 

再使用navigationController时,当调整了view的frame后,

有时会发现视图会有奇怪的变化,原因是

Before displaying your view, the navigation controller automatically positions and sizes it to fit the available space.(在显示你的视图之前,navigation controller会自动地调整它的大小和位置,使其适应可用空间)。

delegate 之assign & retain讨论

作者QQ:415074476

QQ群:191280586

delegate 的property属性到底该用assign 还是retain?

 

网上有说法是该用assign.

但我在实际工作中时常遇到crash的情况(包括umeng,sian,tencent sdk)。另外,在cocoa的api中,异步方法调用时,delegate的retainCount会+1,说明,在这种情况下,其实是使用的retain.

 

本课题就是结合官方文档,官方api测试。自由Delegate设计测试,来给出一个最终答案。

 

首先谈谈网上的看法。

现在网上都是认为应该是assign. 避免循环计数。

这个很容易理解的。。

案例一:

对象A 持有对象B.

对象B有个delegate属性,指向的对象是对象C.

假设delegate为retain的,对象C是对象B本身。

 

描述下引用计数:

对象A 持有对象B.(对象B计数为1)

对象B有个delegate属性(retain),指向的对象是对象B本身.(对象B的计数为2).

 

Ok.现在对象A释放。调用dealloc方法。释放对象B。(对象B的引用计数减低为1).

然后呢?没有然后了。

 

因为delegate的释放是在对象B调用dealloc方法时。

-(void)dealloc

{

….

[delegate release];

[super dealloc];

}

 

明显对象B这个对象占的内存泄露了。

如果有读者用过umeng的Sdk,就会发现他们是怎么处理的呢。

在对象A的dealloc方法做了处理, 不光处理了对象B本身,还把他的delegate置为了nil. 否则会crash哦。

 

好吧。我给的这个案例。不把delegate置为nil,也不会crash的,只会有内存泄露。

 

大家使用cocoa api时,会在dealloc方法里,去把delegate置为nil么?不会。

所以,这种折中的方法显然行不通。挖苦点是太山寨,太不安全。

 

到底该如何设计?

既然上面的例子用retain不行,是不是说明网上的说法是正确的。Delegate的属性必须为assign, 不然会造成内存泄露。

 

好吧。我给出另一个案例:
对象A 持有对象B.

对象B持有 对象C.

对象C有个delegate属性(assign), 指向对象B 。delegate声明了方法callMe.

对象B实现方法callMe.

一般情况下, 以上代码不会有问题。

但在有异步调用就容易出问题了。

假设对象C 有个方法是start. 发起一个异步请求,获取到资源后,在调用callMe方法。

调用栈如下:

[B.C start];

[C.delegate callMe];

由于异步的关系。上述两行代码之间会发生什么事呢?

可能对象B释放掉了,并且它的内存被其它对象所占用。

那么

[C.delegate callMe]一定crash.

怎么办?加上if(delegate && [delegate respondsToSelector:@selector(callMe)])

{

//doSometing

}

 

很遗憾地告诉你。 这样是真不行, 照样crash.

怎么办?既然assign会因为对象的失去而crash, 那我retain呗。 问题回去了,又是内存泄露。

不信?看看:

对象A 持有对象B.(对象B引用计数1)

对象B持有 对象C.

对象C有个delegate属性(assign), 指向对象B.( 对象B引用计数2).

 

对象A调用dealloc.

对象B引用计数减少为1.

然后呢?没然后了。。泄露。

 

因为必须对象B引用计数减少为0时,才会调用dealloc方法。才会release对象C。然后对象在dealloc方法里release delegate.

 

都行不通啊。。

 

我认为assign是没错的。是下面的判断方法不对。

if(delegate && [delegate respondsToSelector:@selector(callMe)])

{

//doSometing

}

 

到底该如何判断呢?

NSObject协议有下面两个方法。

– (BOOL)conformsToProtocol:(Protocol *)aProtocol;

 

– (BOOL)respondsToSelector:(SEL)aSelector;

NSObject对象有下面这个方法:

–   (void)doesNotRecognizeSelector:(SEL)aSelector;

到底哪个方法是有效的呢?

一一测试下吧。

 

– (BOOL)respondsToSelector:(SEL)aSelector;

都不可行。

因为占据内存的可能是一个基本数据类型。

 

看来得分情况了。

一:同步方法用assign.

二:异步方法。在发请求时retain,响应时release. 这种情况就不应该用property属性。看下官方的异步方法里要传delegate参数的Api,是不是在头文件里,找不到delegate的property属性呢。

怎么设置呢? 传参。启动方法里传参。 这才是正确的方法。

有不同看法的,欢迎讨论。

 

 

 

 

 

 

怎么调试网络请求

 

作者QQ:415074476

QQ群:191280586

常规方式 不过是使用NSLog打印出链接 和 返回内容.

这样有两个弊端。 一是只看到了数据,没有看到中间环节, 并且数据可能是来自于缓存, 而不是真的来自于服务器。

二是每次都要加断点,运行程序。有点麻烦。结果还不好保存下来。

 

其实我们可以借助其它工具来观察更多我们想要的信息.

甚至在写实际代码之前就可以模式网络请求。

 

这个先介绍模拟网络请求。

简单的GET请求, 我们可以简单地使用 wget 就可以。

不建议使用浏览器.因为一是,可能会有跳转, 二是有js执行会改变页面代码.

 

不过对于我们目前的应用来说倒是没有这个问题..因为是xml格式的数据,

相反, 使用浏览器, 还可以检查xml语法问题.

 

涉及到 POST请求, 我们就得用专门的命令行工具。curl了。

当然, 你不闲累的话, 也可以自己用telnet.

最简单的命令 curl http://mjweather.sinaapp.com/moji.html

在终端直接显示页面http://mjweather.sinaapp.com/moji.html的内容.

如果想看到更详细的数据加上-v参数.

curl –v http://mjweather.sinaapp.com/moji.html

 

看到没. 不光是内容体.连header信息也打印出来了.

Header信息是个相当有用的内容.

 

以上是使用 GET请求, 是相当容易的.

复杂点的Post请求.

 

看以下示例

curl -d ‘name=maynard&password=pwd’ ‘http://learn.com/login.php’

使用-d就可以带上表单数据了.

以上链接就是常用的模拟登陆了.当然后续应该是拿到cookie之后,

再发请求时把cookie带上. 有兴趣的同学可以自己去研究下. 当然最好是先研究下http协议.

 

再进一步的例子是上传图片了.

我之前测试上传图片的速度时, 就是使用的curl命令.

curl -F ‘pic=@test.jpeg’ ‘http://learn.com/upload.php’

以上命令是把当前目录的test.jpeg作为表单域中pic字段的内容上传到upload.php.

 

其它的还是http版本.

curl -0 ‘http://learn.com/index.html’//指定为1.0

 

curl -1 ‘http://learn.com/index.html’//指定为1.1

 

更多的功能请man curl自己研究.

下面说监控网络这块.

第一个要就介绍的工具是用来抓请求的。

tcpdump,

这个命令只有root用户才可以使用。

 

先用whereis tcpdump找出命令路径

 

 

 

 

失败了。。因为mac上不支持any这个常量。

 

好吧。说明一下。-i是指定端口的意思。

我们先看看哪个端口可用.

在我的机器active的是en1

 

so命令改下。sudo /usr/sbin/tcpdump -Xnlps0 -ien1 host mjweather.sinaapp.com

开始监听了..

试试吧..不管是用curl, wget, telnet还是浏览器.

打开http://mjweather.sinaapp.com/moji.html吧.

我这里用的是curl

curl http://mjweather.sinaapp.com/moji.html

command+t  新建一个终端tab.

输入curl weibo.com 再control+command+[ 回到前一个tab看看结果吧.

 

 

 

呃,内容太多了,好晕.

 

还好, 有软件帮我们做了tcpdump做的事. 使用 起来更方便. wireshark, 相当出名的一个工具。

 

 

上文已说过,我的机器使用的端口是en1.

这里使用wireshark监听网络数据看看.

数据一直刷, 真是烦死人..

那么过滤一下吧.

选中Capture Filters

在接下来的界面中选中 http tcp port (80)

 

 

 

依然很多啊..还是用host来过滤吧.

 

这下干净了.无关的数据都没有了..

但是怎么看数据呢?

 

选中HTTP/1.1 200 ok这条.右键选中Follow TCP Stream.

 

 

 

 

 

 

 

 

 

 

 

然后, 你就得到了看得懂的数据了.

 

 

工具都是死东西.

原理不过是所有数据都是通过网关通信.

然后把对应的数据按协议解析.

So, 认清协议才是玩网络通信的关键. .