如何改进iOS App的离线使用体验,改进iosapp离线

iOS 开发App捕获异常, 反馈给服务器, 提高用户体验,iosapp

  在我们开发的app中, 不可避免的, 有时候用户使用软件会崩溃.
 我们就需要捕获异常, 可以在入口类中加入相应的代码,
可以在每次用户打开程序的时候, 检查一下沙盒中是否有崩溃日志, 如果有,
可以发送给服务器, 方便改进软件. 

  

– (BOOL)application:(UIApplication
*)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen
mainScreen] bounds]];

    // Override point for customization after
application launch.

    self.window.backgroundColor =
[UIColor whiteColor];

    [self.window makeKeyAndVisible];

    

    ExceptionHandler  捕获异常的宏定义

    // 这里反馈给服务器

    self.window.rootViewController =
[ViewController new];

    return YES;

}

宏定义

#define ExceptionHandler [ZYExceptionHandler caughtExceptionHandler];

 

#import “ZYExceptionHandler.h”

#include <libkern/OSAtomic.h>

#include <execinfo.h>

@implementation ZYExceptionHandler

 

+ (void)caughtExceptionHandler{

    //指定crash的处理方法。

    NSSetUncaughtExceptionHandler(&
UncaughtExceptionHandler);

}

 

+ (void)fileCreate{

    NSString *path =
[ZYExceptionHandler exceptionPath];

    NSFileManager *manager
=[NSFileManager defaultManager];

    //文件不存在时创建

    if (![manager fileExistsAtPath:path])

    {

        NSString *dateString = [ZYExceptionHandler currentTime];

        NSString
*logStr = [NSString stringWithFormat:@”================\n文件创建时间:%@\n================”,dateString];

        NSData *data
= [logStr dataUsingEncoding:NSUTF8StringEncoding];

        

        [data writeToFile:path atomically:YES];

 

    }

}

 

void UncaughtExceptionHandler(NSException *exception) {

    /**

     *  获取异常崩溃信息

     */

    //在这里创建一个接受crash的文件

    [ZYExceptionHandler fileCreate];

 

    NSArray *callStack = [exception callStackSymbols];

    NSString *reason = [exception reason];

    NSString *name = [exception name];

    

    NSString *dateString = [ZYExceptionHandler currentTime];

 

    NSString *systemName = [[UIDevice currentDevice] systemName];

    

    NSString *strModel = [[UIDevice
currentDevice] model];

 

    NSDictionary*
infoDict =[[NSBundle mainBundle] infoDictionary];

    NSString *bundleIdentifier = infoDict[@”CFBundleIdentifier”];

    NSString* versionNum = [infoDict objectForKey:@”CFBundleShortVersionString”];

    

    NSString
*content = [NSString stringWithFormat:@”\n\n\n========异常错误报告========\n错误时间:%@ 系统:%@ 设备:%@\n当前版本:%@ 当前唯一标示符:%@\n\n错误名称:%@\n错误原因:\n%@\ncallStackSymbols:\n%@\n\n========异常错误结束========\n”,dateString,systemName,strModel,versionNum,bundleIdentifier,name,reason,[callStack
componentsJoinedByString:@”\n”]];

 

 

    NSString *path =
[ZYExceptionHandler exceptionPath];

 

    NSFileHandle
*outFile = [NSFileHandle
fileHandleForWritingAtPath:path];

    //找到并定位到outFile的末尾位置(在此后追加文件)

    [outFile seekToEndOfFile];

    

    [outFile writeData:[content
dataUsingEncoding:NSUTF8StringEncoding]];

    //关闭读写文件

    [outFile closeFile];

 

    

}

+ (NSString *)exceptionPath{

    

    NSLog(@”—–>>>%@”,NSHomeDirectory());

    

    NSString
*documents = [NSHomeDirectory()stringByAppendingPathComponent:@”Documents”];

    NSString *path =
[documents stringByAppendingPathComponent:@”exceptionHandler.txt”];

    

    return path;

 

}

+ (NSString *)currentTime{

    NSDate *date = [NSDate date];

    NSDateFormatter *formatter =
[[NSDateFormatter alloc] init];

    [formatter setDateFormat:@”yyyy-MM-dd hh:mm”];

    NSString *dateString = [formatter stringFromDate:date];

    return dateString;

 

}

//获取调用堆栈

+ (NSArray *)backtrace

{

    void* callstack[128];

    int frames = backtrace(callstack,
128);

    char **strs = backtrace_symbols(callstack,frames);

    

    NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];

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

    {

        [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];

    }

    free(strs);

    

    return backtrace;

}

@end

开发App捕获异常, 反馈给服务器,
提高用户体验,iosapp 在我们开发的app中, 不可避免的,
有时候用户使用软件会崩溃. 我们就需要捕获异常…

iOS IM开发建议(一)App框架设计,iosapp

  先说一下为什么要讲框架的设计。

  第一、IM应用一般是基于长连接的,也就是后台一直在收发数据,那这里就有一个后台的概念;

  第二、如果用户是一个人群里面的中心人物的话,那么他的的数据量就会很大。页面的显示及数据库的处理就需要关注了;

  第三、分解app有利于我们降低耦合,在后期维护和升级时,稍微容易一点。

 

  我觉得框架就是先拆解部件再建立联系。框架有很多种,我借鉴的是依赖注入。

如何改进iOS App的离线使用体验,改进iosapp离线

  

依赖

  这个模块是所有部件运行的中间节点,负责app内的信息传递和数据处理。因此,app运行时他就必须存在。那这里有两个合适的人选,一个是AppDelegate,一个是他的RootViewController。这里我选择的是RootViewController,原因我说一下一下:1、我使用了CoreData,也需要处理APNS,所以AppDelegate已经很魁梧了;2、我的app是基于TabBarViewController,而TabBarViewController对用户是不可见的,他不需要处理UI,而且几个主要页面都是他的viewcontrollers,方便调用。

  选好了之后,我们需要明确他的作用。我给他分配了这几件事情:处理网络模块推送来的数据,存入数据库,推送数据更新的通知到各个页面。也就是外部的数据,到这里就止步了,不会直接操作UI界面。

App Store中的App分析

App已经与我们形影不离了,不管在地铁上、公交上还是在会场你总能看到很多人拿出来手机,刷一刷微博,看看新闻。

据不完全统计有近一半的用户在非Wifi环境打开App,以下为一个典型iPhone和Android
App(50W+用户)的友盟后台数据:

1 2 3 4

- (``void``)viewDidLoad {  
``[self getArticleList:0 length:SECTION_LENGTH useCacheFirst:YES];
}

然后在viewDidAppear中向服务器请求最新数据,如

1 2 3 4 5 6 7 8 - (void)viewDidAppear:(BOOL)animated {           [super viewDidAppear:animated];       //...       [self getArticleList:0 length:SECTION_LENGTH useCacheFirst:NO] }

当然这里的getArticleList接口有useCacheFirst参数,我们需要网络请求模块能够支持这一点,下面就介绍这些库和工具。
(借助一些工具很容易能做到这些,而不用自己造轮子。遵循“凡事都应该最简单,而不过于简陋”的原则,这里整理一下,方便项目中使用)。

 

网络通讯

  这个模块负责和服务器的数据传输,app运行阶段都不可以被销毁。所以,这个模块需要使用单利模式来创建,并且放在全局线程中。这个模块对外就是收发数据;对内就是传递数据到依赖和接受UI界面的发送指令。也就是他只管收发数据,不操作UI和数据库。

1.NSMutableURLRequest

Sample(参考麒麟的文章《iOS开发之缓存(一):内存缓存》来使用NSURLCache):

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 NSString *paramURLAsString= @"http://www.baidu.com/"; if ([paramURLAsString length] == 0){     NSLog(@"Nil or empty URL is given");     return; } NSURLCache *urlCache = [NSURLCache sharedURLCache]; /* 设置缓存的大小为1M*/ [urlCache setMemoryCapacity:1*1024*1024];  //创建一个nsurl NSURL *url = [NSURL URLWithString:paramURLAsString];     //创建一个请求 NSMutableURLRequest *request = [NSMutableURLRequest  requestWithURL:url  cachePolicy:NSURLRequestUseProtocolCachePolicy  timeoutInterval:60.0f];  //从请求中获取缓存输出 NSCachedURLResponse *response = [urlCache cachedResponseForRequest:request]; //判断是否有缓存 if (response != nil){     NSLog(@"如果有缓存输出,从缓存中获取数据");     [request setCachePolicy:NSURLRequestReturnCacheDataDontLoad]; } self.connection = nil; /* 创建NSURLConnection*/ NSURLConnection *newConnection = [[NSURLConnection alloc] initWithRequest:request                                 delegate:self                         startImmediately:YES]; self.connection = newConnection; [newConnection release];

但是NSMutableURLRequest使用起来不够简便,在实际项目中我很少用它,而基本使用ASIHTTPRequest来代替。

 

数据库

  他负责增删改查。。。(他好轻松,只要出个API就好了)

2.ASIHTTPRequest

你可以从这里找到它的介绍:
HTTP requests library,它本身也是Github中的开源项目,但是从iOS
5.0之后逐渐停止维护了。未来的项目可以使用AFNetworking或者MKNetworkKit代替ASIHTTPRequest。

ASIHTTPRequest的简介如下:

ASIHTTPRequest is an easy to use wrapper around the CFNetwork API that makes some of the more tedious aspects of communicating with web servers easier. It is written in Objective-C and works in both Mac OS X and iPhone applications.
It is suitable performing basic HTTP requests and interacting with REST-based services (GET / POST / PUT / DELETE). The included ASIFormDataRequest subclass makes it easy to submit POST data and files usingmultipart/form-data.

ASIHTTPRequest库API设计的简单易用,并且支持block、queue、gzip等丰富的功能,这是该开源项目如此受欢迎的主要原因。

ASIHTTPRequest库中提供了ASIWebPageRequest组件用于请求网页,并且能把网页中的外部资源一并请求下来,但是我在实际项目中使用后发现有严重Bug,所以不建议使用。

ASIHTTPRequest库的介绍中也提到了它可以支持REST-based
service,但是与Restfull API打交道我们往往使用下面介绍的的RestKit。

Sample:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 NSMutableString *requestedUrl = [[NSMutableString alloc] initWithString:self.url];   //如果优先使用本地数据 ASICachePolicy policy = _useCacheFirst ? ASIOnlyLoadIfNotCachedCachePolicy     : (ASIAskServerIfModifiedCachePolicy | ASIFallbackToCacheIfLoadFailsCachePolicy);   asiRequest = [ASIHTTPRequest requestWithURL:                    [NSURL URLWithString:[requestedUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];   [asiRequest setDownloadCache:[ASIDownloadCache sharedCache]]; [asiRequest setCachePolicy:policy]; [asiRequest setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];   // Connection if (_connectionType == ConnectionTypeAsynchronously) {           [asiRequest setDelegate:self];     [asiRequest startAsynchronous];           // Tell we're receiving.     if (!_canceled && [_delegate respondsToSelector:@selector(downloaderDidStart:)])         [_delegate downloaderDidStart:self]; } else {     [asiRequest startSynchronous];           NSError *error = [asiRequest error];           if (!error)     {         [self requestFinished:asiRequest];     }     else     {         [self requestFailed:asiRequest];     } }   [requestedUrl release];

 

UI界面

  这里指app所有可视、可交互页面。所有你想掐死产品的原因都展示在这里。然而这是用户可见的,也就是说,不能卡顿,要好操作等等。有些页面会有很多的UI交互,为此我们不能给他太多负担。那我就让他做两件事,展示和发送请求。展示是他本来的工作,取一下数据库,更新UI;请求是一个接口,他只要抓取页面的数据填进去就好了。

 

  总结一下:将每个模块拆开之后,他们所做的事情就很明确,数据的来源也得到了保证,UI的处理逻辑也简单。全API的调用方式便于后期拓展。

附简图:

图片 1

IM开发建议(一)App框架设计,iosapp
先说一下为什么要讲框架的设计。
第一、IM应用一般是基于长连接的,也就是后台一直在收发数据,…

3.RestKit

官方网站: Restfull API 的
Web服务打交道,这个库非常便捷,它也提供了很完整的Cache机制。

Sample:

1 2 3 4 5 6 7 8 9 10 11 12 + (void)setCachePolicy:(BOOL)useCacheFirst {     RKObjectManager* objectManager = [RKObjectManager sharedManager];           if (useCacheFirst) {         objectManager.client.cachePolicy = RKRequestCachePolicyEnabled; //使用本地Cache,如果没有Cache请求服务器     }     else     {         objectManager.client.cachePolicy = RKRequestCachePolicyLoadIfOffline|RKRequestCachePolicyTimeout; //离线或者超时时使用本地Cache     } }
1 2 3 4 5 6 7 8 9 10 11 12 + (BOOL)getHomeTimeline:(NSInteger)maxId                  length:(NSInteger)length                delegate:(id<RKObjectLoaderDelegate>)delegate           useCacheFirst:(BOOL)useCacheFirst {     if (delegate == nil)         return NO;           [iKnowAPI setCachePolicy:useCacheFirst];       //... }

Cache请求只是RestKit最基本的功能,RestKit真正强大的地方在于处理与RESTful
web
services交互时的相关工作非常简便(
data model到Core Data中:

Core Data support. Building on top of the object mapping layer, RestKit provides integration with Apple's Core Data framework. This support allows RestKit to persist remotely loaded objects directly back into a local store, either as a fast local cache or a primary data store that is periodically synced with the cloud. RestKit can populate Core Data associations for you, allowing natural property based traversal of your data model. It also provides a nice API on top of the Core Data primitives that simplifies configuration and querying use cases through an implementation of the Active Record access pattern.

但实际上RKRequestCachePolicy已经解决了大部分Cache需求。

 

4.SDWebImage

SDWebImage是Github开源项目:

Asynchronous image downloader with cache support with an UIImageView category.

SDWebImage作为UIImageView的一个Category提供的,所以使用起来非常简单:

1 2 3 // Here we use the new provided setImageWithURL: method to load the web image [imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]                placeholderImage:[UIImage imageNamed:@"placeholder.png"]];

AFNetworking也提供了类似功能(UIImageView+AFNetworking):

1 2 UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)]; [imageView setImageWithURL:[NSURL URLWithString:@"http://i.imgur.com/r4uwx.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder-avatar"]];

 

5.UIWebView中的图片Cache

如果你使用UIWebView来展示内容,在离线情况下如果也想能显示的话需要实现2点:

  • Cache Html页面
  • Cache 图片等元素

使用上面介绍的网络组件来Cache Html页面比较便捷,之后使用webView
loadHTMLString即可加载本地Html页面,而Cache图片需要更换NSURLCache公共实例为自定义的
NSURLCache(UIWebView使用的即是+[NSURLCache sharedURLCache]):

1 2 3 4 5 //设置使用自定义Cache机制 LocalSubstitutionCache *cache = [[[LocalSubstitutionCache alloc] init] autorelease]; [cache setMemoryCapacity:4 * 1024 * 1024]; [cache setDiskCapacity:10 * 1024 * 1024]; [NSURLCache setSharedURLCache:cache];

自定义NSURLCache:

1 2 3 4 5 6 7 8 9 10 #import <Foundation/Foundation.h>   @interface LocalSubstitutionCache : NSURLCache {     NSMutableDictionary *cachedResponses; }   + (NSString *)pathForURL:(NSURL*)url;   @end

详细的见NewsReader中的LocalSubstitutionCache.h/.m和WebViewController.m中的viewDidLoad,News
Reader开源项目这里参考的是:

 

NewsReader中的介绍

《iOS News Reader开源项目》这篇文章介绍到的开源项目改进了离线使用体验:

图片 2图片 3

在没有网络的情况下使用已经Cache过的所有数据:文章、图片、音频等等,用到的主要方案已经在上面介绍了,详细的请看源码:

NewsReader项目因为历史演进的原因已经有些庞大了,需要进一步重构,在之后的项目中我们的客户端结构更精简。

App的离线使用体验,改进iosapp离线 App
Store中的App分析
App已经与我们形影不离了,不管在地铁上、公交上还是在会场你总能看到…

相关文章