利用NSStream肾砂、NSOutputStream、NSInputStream寫文件宏悦,一句話實現(xiàn)實時動態(tài)打日志镐确,UIDocumentInteractionController 分享日志文件包吝。

利用NSStream、NSOutputStream源葫、NSInputStream寫文件诗越,一句話實現(xiàn)實時動態(tài)打日志,UIDocumentInteractionController 分享日志文件息堂。

一句話實現(xiàn)實時動態(tài)打日志

一嚷狞、在開發(fā)中我們會遇到各種各樣的問題,各式的坑輪番轟炸储矩。個人認(rèn)為基本上大部分是數(shù)據(jù)處理和數(shù)據(jù)對接造成的比較多感耙,也比較難找,如果是聯(lián)機(jī)調(diào)試持隧,那么還好找一點,但是如果是打包給測試同事逃片,或者是線上的奔潰屡拨,那么跟蹤數(shù)據(jù)就變得不那么好找了。

要是有數(shù)據(jù)日志記錄該多好褥实,那么為了方便解bug呀狼,動態(tài)打日志,并且能夠方便分享發(fā)送日志文件损离,產(chǎn)生了這個demo哥艇。

二、所涉及的技術(shù)NSStream僻澎、NSOutputStream貌踏、NSInputStream 采用數(shù)據(jù)流,動態(tài)把數(shù)據(jù)寫入本地存儲窟勃。UIDocumentInteractionController 采用系統(tǒng)的分享來發(fā)送日志文件祖乳。

三、demo 代碼

3.1JWDOutputInputStream

- (void)doTestInputStream {

    [[JWDOutputInputStream shareOutputInputStream] creatInputStreamWithFilePath:@"/Users/jiangweidong/Desktop/Sign.txt"];
}

- (void)doTestOutputStream {
    
    [[JWDOutputInputStream shareOutputInputStream] creatOutputStreamWithFilePath:@"/Users/jiangweidong/Desktop/Sign-test.txt"];
}

上面 doTestInputStream 和 doTestOutputStream 是利用 NSStream 的代理來實現(xiàn) 把一個文件的數(shù)據(jù) 寫入到指定的地址秉氧,
導(dǎo)入 JWDOutputInputStream 類即可使用眷昆。

JWDOutputInputStream.h
#import <Foundation/Foundation.h>

@interface JWDOutputInputStream : NSObject

+ (JWDOutputInputStream *)shareOutputInputStream;

/**
 讀取文件
 
 @param filePath 需要讀取文件的地址
 */
- (void)creatInputStreamWithFilePath:(NSString *) filePath;
/**
 創(chuàng)建 寫入流
 
 @param filePath 內(nèi)容寫入的文件地址
 */
- (void)creatOutputStreamWithFilePath:(NSString *) filePath;
@end
JWDOutputInputStream.m
#import "JWDOutputInputStream.h"


@interface JWDOutputInputStream ()<NSStreamDelegate>

@property (nonatomic, assign) NSInteger        location;//!< <#value#>
@property (nonatomic, strong) NSString         *contentFilePath;//!< 讀取內(nèi)容的地址


@end

@implementation JWDOutputInputStream


static JWDOutputInputStream *outputInputStream = nil;
+ (JWDOutputInputStream *)shareOutputInputStream {
 
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        outputInputStream = [[JWDOutputInputStream alloc] init];
    });
    return outputInputStream;
}

/**
 讀取文件

 @param filePath 需要讀取文件的地址
 */
- (void)creatInputStreamWithFilePath:(NSString *) filePath {
    NSInputStream *readStream = [[NSInputStream alloc]initWithFileAtPath:filePath];
    [readStream setDelegate:self];
    [readStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [readStream open]; //調(diào)用open開始讀文件
}


#pragma mark -
#pragma mark - 寫

/**
 把需要寫入的內(nèi)容轉(zhuǎn)換成 data

 @return data
 */
- (NSData *)dataWillWrite{
    static  NSData *data = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        data = [NSData dataWithContentsOfFile:@"/Users/jiangweidong/Desktop/Sign.txt"];//@"/Users/jiangweidong/Desktop/Sign.txt"
    });
    return data;
}

/**
 創(chuàng)建 寫入流

 @param filePath 內(nèi)容寫入的文件地址
 */
- (void)creatOutputStreamWithFilePath:(NSString *) filePath{ // @"/Users/jiangweidong/Desktop/Signstream-write.txt";
    NSOutputStream *writeStream = [[NSOutputStream alloc] initToFileAtPath:filePath append:YES];
    [writeStream setDelegate:self];
    [writeStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [writeStream open];
}

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
    switch (eventCode) {
        case NSStreamEventHasSpaceAvailable: {
            
            NSInteger bufSize = 5;
            uint8_t buf[bufSize];
            if (self.location > [self dataWillWrite].length) {
                [[self dataWillWrite] getBytes:buf range:NSMakeRange(self.location, self.location + bufSize - [self dataWillWrite].length)];
            }else if(self.location == [self dataWillWrite].length){
                [aStream close];
                [[self dataWillWrite] getBytes:buf range:NSMakeRange(self.location, bufSize)];
            }else {
                [[self dataWillWrite] getBytes:buf range:NSMakeRange(self.location, bufSize)];
            }
            
            NSOutputStream *writeStream = (NSOutputStream *)aStream;
            [writeStream write:buf maxLength:sizeof(buf)]; //把buffer里的數(shù)據(jù),寫入文件
            
            self.location += bufSize;
            if (self.location >= [[self dataWillWrite] length] ) { //寫完后關(guān)閉流
                [aStream close];
            }
            

        }
            break;
            
        case NSStreamEventEndEncountered: {
            // 結(jié)束的時候關(guān)閉和一處流操作
            [aStream close];
            [aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            aStream = nil;
        }
            break;
            
            //錯誤和無事件處理
        case NSStreamEventErrorOccurred:{
            
        }
            break;
        case NSStreamEventNone:
            break;
            //打開完成
        case NSStreamEventOpenCompleted: {
            NSLog(@"NSStreamEventOpenCompleted");
        }
            break;
            
        default:
            break;
    }
}

@end

網(wǎng)上這樣使用NSStream 的很多汁咏,這里就不細(xì)致說明亚斋。

3.2 JWDInteractionLogger

重點說一下 JWDInteractionLogger

3.2.1

+(JWDInteractionLogger *)shareInteractionLogger {

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        interactionLogger = [[JWDInteractionLogger alloc] init];
    });
    return interactionLogger;
}

使用單例作為全局統(tǒng)一打日志,只要引入頭文件 即可一句話動態(tài)打日志攘滩,
方式一:宏 引入

/**
 日志文件名在這里 統(tǒng)一 標(biāo)記帅刊,作為全局的名稱

 */
#define JWDlogName     @"star"
#define JWDINSlogName  @"more-star"

/**
 只需要引入 宏 傳遞相應(yīng)的參數(shù)

 @param logString 需要記錄的日志內(nèi)容
 @param fileName 日志文件名
 @return 日志宏
 */
#define JWDINSlog(logString,fileName) [[JWDInteractionLogger shareInteractionLogger] writeLogWithLogString:logString withfileName:fileName]

方式二:方法引入

+(JWDInteractionLogger *)shareInteractionLogger;

/**
 使用單例 調(diào)用

 @param logString 需要記錄的日志內(nèi)容
 @param fileName 日志文件名
 */
- (void)writeLogWithLogString:(NSString *)logString withfileName:(NSString *)fileName;

3.2.2
說明 JWDInteractionLogger.m 實現(xiàn)

/**
 創(chuàng)建文件路徑

 @param fileName 文件名稱
 @return 路徑
 */
-(NSURL *)getPathWithFileName:(NSString *)fileName {
    NSFileManager *fileManager = [NSFileManager defaultManager];
    
    NSString *cacheDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    BOOL isDir = TRUE;
    BOOL isDirExists = [fileManager fileExistsAtPath:cacheDir isDirectory:&isDir];
    
    if (!isDirExists) {
        NSError *err = nil;
        BOOL isCreateDirSuccess = [fileManager createDirectoryAtPath:cacheDir withIntermediateDirectories:NO attributes:nil error:&err];
        if (!isCreateDirSuccess) {
            NSLog(@"創(chuàng)建cache路徑失敗:%@",err.description);
            return nil;
        }
    }
    
    NSString *filePath = [[cacheDir stringByAppendingPathComponent:fileName] stringByAppendingPathExtension:@"txt"];
    BOOL isFileExists = [fileManager fileExistsAtPath:filePath];
    if (isFileExists) {
        //存在
        NSError *err = nil;
        float fileSizeKB = [[fileManager attributesOfItemAtPath:filePath error:&err] fileSize] / 1024.0f;
        if (err) {
            NSLog(@"獲取文件大小失敽洳怠:%@",err.description);
        }
        if (fileSizeKB > 10240) {
            NSError *err = nil;
            BOOL isRmSuccess = [fileManager removeItemAtPath:filePath error:&err];
            if (!isRmSuccess) {
                NSLog(@"刪除文件失敽裰馈:%@",err.description);
            }
            BOOL isCreateFileSuccess = [fileManager createFileAtPath:filePath contents:nil attributes:nil];
            if (!isCreateFileSuccess) {
                NSLog(@"創(chuàng)建文件失數茏啤:%@",err.description);
                return nil;
            }
        }
    } else {
        //不存在
        NSError *err = nil;
        BOOL isCreateFileSuccess = [fileManager createFileAtPath:filePath contents:nil attributes:nil];
        if (!isCreateFileSuccess) {
            NSLog(@"創(chuàng)建文件失敗:%@",err.description);
            return nil;
        }
    }
    NSURL *fileURL = [NSURL fileURLWithPath:filePath];
    if (!fileURL) {
        NSLog(@"獲取沙盒相對文件URL失敗");
        return nil;
    }
    return fileURL;
}

上面創(chuàng)建文件路徑冒黑,做了幾種容錯田绑,也對太大的日志文件直接刪除。

3.2.3

/**
 創(chuàng)建 流寫 實例

 @param fileName 類型名
 */
- (NSOutputStream *)creatStreamWithFileName:(NSString *)fileName {

    NSURL *url = [self getPathWithFileName:fileName];
    NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:url append:YES];
    outputStream.delegate = self;
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    [outputStream open];
    return outputStream;
}

根據(jù)日志名稱創(chuàng)建寫入流抡爹,做到即時創(chuàng)建掩驱。這里的用法,把流加入到 [NSRunLoop currentRunLoop] 中等待數(shù)據(jù)的寫入冬竟。

3.2.4

- (NSData *)getDataWithLogString:(NSString *)logString {

    NSString *tempString = [[NSString alloc] init];
    if (logString.length == 0) {
        tempString = @"\n";
    }else {
        NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
        NSString *appName = [infoDictionary objectForKey:@"CFBundleDisplayName"];
        NSString *version = [infoDictionary objectForKey:@"CFBundleShortVersionString"];
        
        NSDate *date = [NSDate date];
        NSDateFormatter *forMatter = [[NSDateFormatter alloc] init];
        [forMatter setDateFormat:@"yyyy年MM月dd日 HH時mm分ss秒"];
        NSString *dateStr = [forMatter stringFromDate:date];

        tempString = [tempString stringByAppendingFormat:@"appName--%@,version--%@,date--%@ ,[文件名:%s]" "[函數(shù)名:%s]" "[行號:%d] \n %@ \n",appName,version,dateStr,[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __FUNCTION__,__LINE__,logString];
    }
    NSData *data = [tempString dataUsingEncoding:NSUTF8StringEncoding];

    return data;
}

把需要記錄的日志數(shù)據(jù)轉(zhuǎn)化成NSData欧穴,這里本人拼接進(jìn)去了 APP名,版本號泵殴,日志時間涮帘,文件名,函數(shù)名笑诅,行號调缨,日志數(shù)據(jù)。記錄這些數(shù)據(jù)吆你,做到查看日志的快速定位弦叶,便于查找問題所在,是不是很爽妇多。

3.2.5

- (void)writeLogWithLogString:(NSString *)logString withfileName:(NSString *)fileName{
    
    if (self.writeLogStream == nil) {
        self.writeLogStream = [self creatStreamWithFileName:fileName];
    }
    NSData *data = [self getDataWithLogString:logString];
    [self.writeLogStream write:[data bytes] maxLength:data.length];
    [self.writeLogStream close];
    [self.writeLogStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    self.writeLogStream = nil;   
}

這里就是供外界調(diào)用的日志入口伤哺,數(shù)據(jù)轉(zhuǎn)換,在流對象不存的情況下者祖,做到即時創(chuàng)建立莉,在一條數(shù)據(jù)寫入成功之后,做到即時關(guān)閉數(shù)據(jù)流對象咸包,保護(hù)數(shù)據(jù)不出錯桃序,和 數(shù)據(jù)庫讀寫數(shù)據(jù)地址 一個道理,用時打開烂瘫,不用時及時關(guān)閉媒熊。

四、日志分享

現(xiàn)在本地有了相應(yīng)的日志文件坟比,那么打包安裝的包芦鳍,為了及時方便獲取日志數(shù)據(jù),那么很快的把文件分享發(fā)送出來葛账,豈不更好柠衅。
利用系統(tǒng)的 UIDocumentInteractionController 來實現(xiàn)

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

    NSURL *fileurl = [self getPathWithFileName:fileName];
    
    if (!fileurl) {
        NSLog(@"日志文件不存在");
        return;
    }
    
    // 判斷文件是否存在
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isExists = [fileManager fileExistsAtPath:[fileurl path]];
    if (!isExists) {
        return;
    }
    // 獲取文件大小
    NSError *error = nil;
    CGFloat fileSize = [[fileManager attributesOfItemAtPath:[fileurl path] error:&error] fileSize]/1024.0f;
    if (error) {
        NSLog(@"獲取日志失敗");
        return;
    }
    if (fileSize == 0) {
        NSLog(@"獲取日志失敗,文件不存在");
    }else {
        self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:fileurl];
        self.interactionController.delegate = self;
        UIViewController *vc = [UIApplication sharedApplication].keyWindow.rootViewController;
        while (vc.presentedViewController) {
            vc = vc.presentedViewController;
        }
        if (vc!=nil) {
            [self.interactionController presentOptionsMenuFromRect:vc.view.bounds inView:vc.view animated:YES];
        }
    }
}

但是當(dāng)我要分享籍琳,使用AirDrop 分享發(fā)送時菲宴,發(fā)送失敗報下面的錯誤贷祈,第三方發(fā)送也是如此,備忘錄里面也是喝峦,顯示文件為空势誊。
后來查閱資料發(fā)現(xiàn),沒有強(qiáng)引用

 self.interactionController = [UIDocumentInteractionController interactionControllerWithURL:fileurl];
分享日志文件失敗

出現(xiàn)以上的原因是因為沒有全局引用 UIDocumentInteractionController

 UIDocumentInteractionController *interactionController = [UIDocumentInteractionController interactionControllerWithURL:fileurl];

導(dǎo)致在分享文件的時候找不到UIDocumentInteractionController谣蠢,獲取文件失敗粟耻。

好了,就說這么多吧眉踱!可以下載demo 自己試一試挤忙,或者放到自己的項目里面試試,當(dāng)你看到日志了是不是感覺感覺很爽谈喳,不用聯(lián)機(jī)就可以分析數(shù)據(jù)册烈,這樣如此好的工具,豈不是和后臺婿禽,服務(wù)端撕逼的神器茄厘。被各種錯誤數(shù)據(jù)虐了千萬遍的客戶端同仁們,這下是不是長嘆一句谈宛,真爽。

當(dāng)測試美美來找你結(jié)束bug時胎署,直接看日志吆录,分析顯示,他媽的數(shù)據(jù)錯誤琼牧,數(shù)據(jù)格式不對恢筝,各種jj,bug 直接拋出去巨坊。當(dāng)然還是要和睦相處撬槽,共同為自家項目加油努力。

如有考慮不周和錯誤的地方歡迎你的指正趾撵,謝謝侄柔!

如果你感覺到,幫助了你占调,就star一個吧暂题!

最后是 demo地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市究珊,隨后出現(xiàn)的幾起案子薪者,更是在濱河造成了極大的恐慌,老刑警劉巖剿涮,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件言津,死亡現(xiàn)場離奇詭異攻人,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)悬槽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門怀吻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人陷谱,你說我怎么就攤上這事烙博。” “怎么了烟逊?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵渣窜,是天一觀的道長。 經(jīng)常有香客問我宪躯,道長乔宿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任访雪,我火速辦了婚禮详瑞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘臣缀。我一直安慰自己坝橡,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布精置。 她就那樣靜靜地躺著计寇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脂倦。 梳的紋絲不亂的頭發(fā)上番宁,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機(jī)與錄音赖阻,去河邊找鬼蝶押。 笑死,一個胖子當(dāng)著我的面吹牛火欧,可吹牛的內(nèi)容都是我干的棋电。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼布隔,長吁一口氣:“原來是場噩夢啊……” “哼离陶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衅檀,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤招刨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后哀军,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沉眶,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡打却,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谎倔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柳击。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖片习,靈堂內(nèi)的尸體忽然破棺而出捌肴,到底是詐尸還是另有隱情,我是刑警寧澤藕咏,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布状知,位于F島的核電站,受9級特大地震影響孽查,放射性物質(zhì)發(fā)生泄漏饥悴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一盲再、第九天 我趴在偏房一處隱蔽的房頂上張望西设。 院中可真熱鬧,春花似錦答朋、人聲如沸贷揽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擒滑。三九已至,卻和暖如春叉弦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藻糖。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工淹冰, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巨柒。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓樱拴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親洋满。 傳聞我的和親對象是個殘疾皇子晶乔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容