一、前言

因为一些原因,软件迄今发了三个版本,就进行了三次大的改版,因为每次都是从底层开始修改,虽然要了命了,但是作为开发来说也只能听天由命啦,大版本一直在抢分多秒的开发阶段,并没有做什么优化,当然现在说的优化也是基于当前的2.3.2版本的,至于后面会不会再次大改版重新优化,或者因为又是从底层修改导致删除掉这些优化,其实都是未知的,所以文章的标题就写成2.3.2版本优化吧。

二、优化内容

2.1、网络接收内容判断

网络库是使用的AFNetworking,所以接受数据已经处理好了,但是之前并没有考虑到网络数据的丢失,后面发现居然会出现响应是ok的,但是返回的数据却是空的,这样就导致在接受到数据之后直接用字典执行[dic setObject: forKey:]的时候,因为对象时nil,直接导致崩溃的问题。

所以在这个版本,重新创建了一个类专门用来检测接受数据的类型和值的,如果指定的字段的类型和指定的值不一样的话,就会报错并且异步返回给服务器报错的接口、接收的数据、发送的数据和UA的内容,比如这样

if ([[responseObject objectForKey:@"ok"] integerValue] == 1) {
     YZNetReciveParamCheckTools *checkDic = [[YZNetReciveParamCheckTools alloc] initWithDictionary:[responseObject objectForKey:@"data"] withPostErrorWithUrl:kYZUrl_Make_UploadVideoPhoto param:dic];
     [checkDic addCheckParamName:@"user_resume_id" withType:kYZNetErrorParamNone canNil:NO];
     if (![checkDic isAccord]) {
         return;
     }
}

判断的内容主要就是基于类型和值来判断,有的值允许为空,所以就增加了允许为空的选项,现在主要是这几个方法。如果有人需要,可以留言,我去整理分享一下。

@interface YZNetReciveParamCheckTools : NSObject


/**
 初始化操作

 @param dic 要检测的返回的dic
 @param url 请求的URL
 @param param 请求数据的参数
 @return 初始化完成
 */
-(instancetype)initWithDictionary:(NSDictionary*)dic withPostErrorWithUrl:(NSString*)url param:(NSDictionary*)param;


/**
 添加检测的字段

 @param name 字段key值
 @param paramType 字段指定的类型
 @param canNil 是否可空
 */
-(void)addCheckParamName:(NSString*)name withType:(YZNetErrorParamType)paramType canNil:(BOOL)canNil;


/**
 批量添加检测的字段,全部为空,或者全部不能为空

 @param nameArray 字段key值的数组
 @param paramTypeArray 类型数组
 @param canNil 是否全部可空
 */
-(void)addCheckParamNameArray:(NSArray*)nameArray withTypeArray:(NSArray*)paramTypeArray canNilTotal:(BOOL)canNil;


/**
 批量添加检测的字段

 @param nameArray 字段key值的数组
 @param paramTypeArray 类型数组
 @param canNilArray 是否可空数组
 */
-(void)addCheckParamNameArray:(NSArray*)nameArray withTypeArray:(NSArray*)paramTypeArray canNilArray:(NSArray*)canNilArray;

/**
 检测是否符合要求

 @return 是否符合要求
 */
-(BOOL)isAccord;

@end

这样在使用之前就会先增加一份网络数据的保障。

2.2、字典的对象空处理

正如前面说的,网络的空数据会导致字典崩溃,那其他的设置为空也是一样的,所以对字典设值的时候也可以验证下,github上面有一个NSDictionry+nil的库,是动态设置的,但是这个库会在全局都生效,会导致在使用AVFoundation合成视频的时候合成失败。

所以根据暂时常使用的字符串、字典、数组来通过类型和值去安全设置,如果是空的就自己初始化,保证不为nil即可

#import <Foundation/Foundation.h>

@interface NSMutableDictionary (SafeValue)

///暂时用于字符串、字典、数组
-(void)setSafeObject:(id)object withClass:(Class)cls forKey:(NSString *)key;


///暂时用于字符串、字典、数组
-(id)objectSafeForKey:(NSString*)key withClass:(Class)cls;
@end

而判空也是根据工程的具体情况去判断空,判断的不仅仅只是nil,还判断了值是否为0,是否因为强转造成的"null",""这些字符串。

///判断是否为空
-(BOOL)IsNotNull
{
    if (!self && ![self isEqual:[NSNull null]]) {
        return YES;
    }
    else{
        if ([self isKindOfClass:[NSString class]]) {
            if ([((NSString*)self) isEqualToString:@"null"] || [((NSString*)self) isEqualToString:@"<null>"]  || [((NSString*)self) isEqualToString:@"(null)"]) {
                return NO;
            }
            return ((NSString*)self).length>0;
        }
        if ([self isKindOfClass:[NSArray class]]) {
            return [((NSArray*)self) count]>0;
        }
        if ([self isKindOfClass:[NSSet class]]) {
            return [(NSSet*)self count]>0;
        }
        if ([self isKindOfClass:[NSDictionary class]]) {
            return [((NSDictionary*)self) allKeys].count>0;
        }
        if ([self isKindOfClass:[NSNumber class]]) {
            return [(NSNumber*)self integerValue] > 0;
        }
        if ([self isKindOfClass:[UIImage class]]) {
            NSData *data1 = UIImagePNGRepresentation((UIImage*)self);
            return data1.length>0;
        }
//        else{
//            DebugLog(@"无法判断该类型:%@",self);
//            NSAssert(NO, @"无法判断该类型");
//        }
        return NO;
    }
}

经过这些处理,在对象为空或者服务器返回错误造成的崩溃会减少很多。

2.3、文件和文件夹的分类管理

之前版本不太考虑多用户共存问题,主要考虑的就是用户切换使用等,但是这个版本我们考虑为了用户更加快捷,便利,所以对每个用户ID的文件夹、文件都分类处理,这个思路主要参考了腾讯QQ的多用户登陆、文件的管理方式。

#import <Foundation/Foundation.h>
#import "YZConfig.h"

///管理软件内的文件创建、删除
@interface YZFileManageTools : NSObject

///单例
+(instancetype)shareYZFileManageTools;

///初始化所需要的所有文件夹
-(void)initSystermFolder;

///在指定文件夹中增加文件
-(BOOL)addFileToFolder:(YZFolderType)folderType withFileName:(NSString*)fileName withFilePath:(NSString*)filePath withCopyModel:(YZCopyModel)copyModel;

///删除指定的文件夹的指定文件内容
-(BOOL)deleteFileFromFolder:(YZFolderType)folderType withFileName:(NSString*)fileName;

///删除指定文件夹里的所有内容
-(BOOL)deleteFilesInFolder:(YZFolderType)folderType;

///获取指定文件夹的路径
-(NSString*)getFilePathFromFolder:(YZFolderType)folderType;

///获取指定文件夹中的所有文件路径
-(NSArray*)getAllFilePathInFolder:(YZFolderType)folderType;

///获取指定文件夹的指定文件的路径
-(NSString*)getFilePathFromFolder:(YZFolderType)folderType withFileName:(NSString*)fileName;

///删除sdkwebimage和网页缓存
-(BOOL)cleanPlusMemory;

///删除账户自己的缓存文件,会删除数据库,导致做的简历缓存丢失
-(BOOL)cleanSelfFileMemory;

///深度清理app内所有的缓存,会删除数据库,导致做的简历缓存丢失
-(BOOL)deepCleanAllFileMemory;

@end

这里在初始化的时候,我们会根据用户ID去创建对应的文件夹类型和文件夹

///初始化所需要的所有文件夹
-(void)initSystermFolder
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [paths objectAtIndex:0];
    
    ////////用户的总目录
    _userFolder = [path stringByAppendingPathComponent:[NSString stringWithFormat:@"userFolder%@",[[YZUserInfoData sharedYZUserInfoData] getUserUIDStr]]];
    DebugLog(@"用户的目录:%@",_userFolder);
    
    ///用户的录制视频的目录
    NSString *recodeFolder = [_userFolder stringByAppendingPathComponent:kYZVideoFolder];
    NSString *tempVideoFolder = [_userFolder stringByAppendingPathComponent:kYZTempVideoFolder];
    NSString *musicVideoFolder = [_userFolder stringByAppendingPathComponent:kYZTempMusicVideoFolder];
    NSString *waterVideoFolder = [_userFolder stringByAppendingPathComponent:kYZWaterVideoFolder];
    ///用户的封面图文件夹
    NSString *coverImageFolder = [_userFolder stringByAppendingPathComponent:kYZCoverimgFolder];
    ///用户的照片文件夹
    NSString *photoFolder = [_userFolder stringByAppendingPathComponent:kYZPhotoFolder];
    ///用户的数据库文件夹
    NSString *dataBaseFolder = [_userFolder stringByAppendingPathComponent:kYZDataBaseFolder];
    
    ////////公共目录
    _commonFolder = [path stringByAppendingPathComponent:@"commonFolder"];
    DebugLog(@"公共目录:%@",_commonFolder);
    
    ///音乐目录
    NSString *music = [_commonFolder stringByAppendingPathComponent:kYZMusicFolder];
    
    ///创建的目录
    NSArray *folerArray = [NSArray arrayWithObjects:_userFolder,recodeFolder,tempVideoFolder,musicVideoFolder,waterVideoFolder,coverImageFolder,photoFolder,dataBaseFolder,_commonFolder,music, nil];
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    for (int i = 0; i<folerArray.count; i++) {
        BOOL isDir = FALSE;
        BOOL isDirExist = [fileManager fileExistsAtPath:[folerArray objectAtIndex:i] isDirectory:&isDir];
        if(!(isDirExist && isDir))
        {
            BOOL bCreateDir = [fileManager createDirectoryAtPath:[folerArray objectAtIndex:i] withIntermediateDirectories:YES attributes:nil error:nil];
            if(!bCreateDir){
                DebugLog(@"创建文件夹失败");
            }
        }
    }
}

通过这些措施,对于每个用户的文件夹和文件路径都能轻易获取管理,清理缓存等等都会很有序进行,而对多用户的存在也不会产生影响,避免数据冲突串用。

2.4、数据库缓存和记录

之前存储更多的是使用NSUserDefault,这次为了更好的管理数据和更快的处理和后期的处理,所以采取用数据库存储部分数据和缓存。经过对比,采用了比iOS自带的sqlite更效率的微信的开源数据库WCDB,这个数据库性能比FMDB更加强悍,所以就采用了这个。

baseline_batch_write.png

管理数据库主要就是判断是否存在,存在就读取数据,不存在去创建或者更新数据

#import <Foundation/Foundation.h>

@interface YZDataBaseTools : NSObject

+(instancetype)sharedYZDataBaseTools;

///是否存在该userID的数据库,以及在指定表中的类是否存在
-(BOOL)isExitTheDataBase:(Class)cls fromTable:(NSString*)tableName;

///获取数据库路径地址
-(NSString*)getDataBasePath;

///清理数据库
-(BOOL)cleanDataBase;
@end

三、后续优化计划

不知道后续版本是否会大改,如果大改的话就省掉这段吧,如果没有的话,后续的优化计划主要就是针对网速差的优化。

3.1、首先网络优化

网络有时候还是不太尽如人意,虽然采取了多种措施,但是谁能保证用户的网络质量呢,有的用户手机上虽然显示的是4G,但是网速却是2G吧,所以还是要优化网络质量差的情况。

3.2、低网速界面操作优化

界面是反馈一个软件质量的最直观反应,之前并没有考虑太多网速的因素,因为并没有考虑太多用户会在地铁上、偏远展厅等地方使用的场景。所以网速垃圾的情况下,会出现一系列问题,虽然这个版本已经进行了不少优化,但是因为相比上个版本,修改的东西太多,所以还是需要分阶段来进行。

3.3、对内存、缓存的控制

下一步会尽量充分使用内存和缓存的使用,尽量在低网速操作也能享受到畅快的舒爽

四、结语

优化是动态的,现在这个版本的优化对版本稳定性起了很大的帮助,当然也会根据项目的变动变动,对该项目比较大的优化就是这几个,当然为了增强用户体验也做了一些小的优化,那些就暂时不提了。该软件版本马上就要上线,欢迎大家使用哦。


☟☟可点击下方广告支持一下☟☟

最后修改:2017 年 09 月 10 日
请我喝杯可乐,请随意打赏: ☞已打赏列表