iOS IO 重定向(NSLog to UITextView)

5 Comments

情形描述:

在调试程序的时候,通过NSLog打印log,很方便的就可以在Xcode里面看到。但是程序一旦“离开XCode运行”, 比如将App交付给了公司的测试团队,怎样能够很随意看到NSLog打印的信息呢?通常在离开xcode之后,NSLog的信息会保存在Systemlog里面(这里有NSLog详细描述),你可以通过一定办法取出这个log。甚至可以写一套日志系统,然后将这些信息保存到日志中,然后导出或者上传自己的服务器。但是这些太麻烦了,简直是弱爆鸟。我们的目的是:在App里面能够直接像xCode console窗口那样显示NSLog的信息,准确的说是标准输出的信息。

关键技术:IO重定向

通过IO重定向,我们可以直接“截取” stdout,stderr等标准输出的信息(NSLog->stderr),然后再在自己的View上显示出来。这里只展示IO重定向相关代码。

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
@implement TestAppDelegate

- (void)redirectNotificationHandle:(NSNotification *)nf{
  NSData *data = [[nf userInfo] objectForKey:NSFileHandleNotificationDataItem];
  NSString *str = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];

  self.logTextView.text = [NSString stringWithFormat:@"%@\n%@",self.logTextView.text, str];
  NSRange range;
  range.location = [self.logTextView.text length] - 1;
  range.length = 0;
  [self.logTextView scrollRangeToVisible:range];

  [[nf object] readInBackgroundAndNotify];
}

- (void)redirectSTD:(int )fd{
  NSPipe * pipe = [NSPipe pipe] ;
  NSFileHandle *pipeReadHandle = [pipe fileHandleForReading] ;
  dup2([[pipe fileHandleForWriting] fileDescriptor], fd) ;

  [[NSNotificationCenter defaultCenter] addObserver:self
                                           selector:@selector(redirectNotificationHandle:)
                                               name:NSFileHandleReadCompletionNotification
                                             object:pipeReadHandle] ;
  [pipeReadHandle readInBackgroundAndNotify];
}
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOption{

  [self redirectSTD:STDOUT_FILENO];
  [self redirectSTD:STDERR_FILENO];

//YOUR CODE HERE...
}

//YOUR CODE HERE...

@end

主要代码就功能就是:

  1. 通过NSPipe创建一个管道(这里有详细讲NSPipe的文章),pipe有读端和写端。
  2. 通过dup2(这里有详细将dup2的文章)讲标准输入重定向到pipe的写端。
  3. 通过NSFileHandle监听pipe的读端,然后讲读出的信息显示在uitextview上。

通过上面三步,一旦通过printf或者NSLog写数据,因为重定向过,这些数据都会写到Pipe的写端。同时pipe会将这些数据从写端直接传送到读端。读端通过NSFileHandle的“监控”函数取出这些数据,并最终显示在uitextview上。

DONE!
在真实的项目中,你可以设置一个开关去开启或者关闭这个重定向。在调试测试程序的过程中,打开开关,快捷及时查看程序运行状况;产品发布的时候关掉开关就OK了,用户可以毫不知情。这样,也许会提高不少社会主义生产力。

下面展示一下效果截图:

乔布斯辞世

4 Comments

永远记住这一天 2011/10/5. 乔布斯离开了我们.

iPhone 4S:iPhone for Steve,Jobs在世留给我们最后的礼物。

[小工具:jtop] Convert JSON to Plist

1 Comment

JSON格式很好,但是可阅读性相对较差,而且在mac下也没有很好的专门针对JSON的编辑器,一般都用文本编辑器,所以看起来非常累。

恰好,mac下的plist文件格式如果用系统自带的properties list editor(现在整合到Xcode4中了)打开阅读性非常好。于是自己写了一个小工具将JSON转换为plist文件输出。这样看起来就非常爽了~

两者阅读效果对比截图如下:

 

你需要做的就是:下载这个小app,然后安装到PATH中,mv …jtop /bin/jtop ,然后这样就可以直接转换并打开了:

 

点击下载jtop :)

查看iOS“加密”(Symbolicated)后的Crash Report

No Comments

程序真机上崩溃以后通常会留下一个.crash的日志文件,可以通过这个crash文件迅速查找到哪里崩溃了。但是这个文件中没有平时调试时候那样可以看到的函数名和函数具体调用行数。因为这里的这些信息都被转换成了16进制的地址,起到了一定的加密作用,别人拿到你的crash日志也不知道哪里崩溃了。

但是开发人员就不能一下子看出问题所在,需要利用你编译app的时候生成的dSYM文件然后讲这些信息反转化为可读模式。所以保留好你的dSYM文件,很有用的!

下面这写链接会让你知道具体怎么做:

 

你可以在上面文章中所提到的目录中找不到symbolicatecrash,因为它在新的SDK中被移到了这里:

/Developer/Platforms/iPhoneOS.platform/Developer/Library/PrivateFrameworks/

DTDeviceKit.framework/Versions/A/Resources/symbolicatecrash

 

这里还有一个用来代替symbolicatecrash的工具,叫symbolicator:

Objective-C Singleton template

2 Comments

原文:http://blog.mugunthkumar.com/coding/objective-c-singleton-template-for-xcode-4/

___FILEBASENAME___.h

1
2
3
4
5
6
7
8
9
10
#import <foundation /Foundation.h>

@interface ___FILEBASENAMEASIDENTIFIER___ : NSObject {

}

+ (___FILEBASENAMEASIDENTIFIER___*) sharedInstance;

@end
</foundation>

___FILEBASENAME___.m

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#import "___FILEBASENAME___.h"

static ___FILEBASENAMEASIDENTIFIER___ *_instance;
@implementation ___FILEBASENAMEASIDENTIFIER___

#pragma mark -
#pragma mark Singleton Methods

+ (___FILEBASENAMEASIDENTIFIER___*)sharedInstance
{
  @synchronized(self) {
   
        if (_instance == nil) {
     
            _instance = [[self alloc] init];
           
            // Allocate/initialize any member variables of the singleton class here
            // example
      //_instance.member = @"";
        }
    }
    return _instance;
}

+ (id)allocWithZone:(NSZone *)zone

{
    @synchronized(self) {
   
        if (_instance == nil) {
     
            _instance = [super allocWithZone:zone];    
            return _instance;  // assignment and return on first allocation
        }
    }
 
    return nil; //on subsequent allocation attempts return nil 
}


- (id)copyWithZone:(NSZone *)zone
{
    return self; 
}

- (id)retain
{
    return self; 
}

- (unsigned)retainCount
{
    return UINT_MAX;  //denotes an object that cannot be released
}

- (void)release
{
    //do nothing
}

- (id)autorelease
{
    return self; 
}

#pragma mark -
#pragma mark Custom Methods

// Add your custom methods here

@end

推荐Sqlite利器 SqliteManager

1 Comment

如题,这是是我目前用过的最好的Sqlite GUI管理程序,windows和mac版本都有,界面简洁时尚高效,作为调试利器,比命令行方便不少。

下面截图是展示的iBooks的sqlite数据库:

详情见(收费):http://www.sqlabs.com/sqlitemanager.php

这里也有Firfox的插件:https://addons.mozilla.org/zh-cn/firefox/addon/sqlite-manager/

悄悄送上序列号:http://www.oyksoft.com/soft/11945.html

iOS/Mac OS 调试信息输出(一)

No Comments

调试信息输出方法介绍

在Apple Tech Note TN2239:iOS Debugging Magic(http://developer.apple.com/library/ios/#technotes/tn2010/tn2239.html)中提到了程序开发中Debug output 方法:

  1. NSLog
  2. stderr
  3. system log

调试信息的输出主要有方式,一是通过输出到终端输出,二是输出到日志系统。下面讲介绍一下这几种输出调试信息的方式,首先从stderr说起。

1. stderr (引用自TN2239):

Many programs, and indeed many system frameworks, print debugging messages to stderr. The destination for this output is ultimately controlled by the program: it can redirect stderr to whatever destination it chooses. However, in most cases a program does not redirect stderr, so the output goes to the default destination inherited by the program from its launch environment. This is typically one of the following:

  • If you launch a GUI application as it would be launched by a normal user, the system redirects any messages printed on stderr to the system log. You can view these messages using the techniques described earlier.
  • If you run a program from within Xcode, you can see its stderr output in Xcode’s debugger Console window (choose the Console menu item from the Run menu to see this window).

Attaching to a running program (using Xcode’s Attach to Process menu, or the attach command in GDB) does not automatically connect the program’s stderr to your GDB window. You can do this from within GDB using the trick described in the “Seeing stdout and stderr After Attaching” section of Technical Note TN2030, ‘GDB for MacsBug Veterans’.

这样一段代码在真机上跑:

NSLog(@”This is message from NSLog”);

fprintf(stderr, “This is message from stderr\n”);

1)如果是通过Xcode调试加载运行这个程序,那么

在xcode的console中打印如下:

2011-03-12 18:52:26.948 Test86[7891:307] This is message from NSLog

This is message from stderr

在iPhone的system log中(通过Organizer的console查看)只打印

Sat Mar 12 18:52:26 unknown Test86[7891] <Warning>: This is message from NSLog

2)但是如果在iPhone上通过手指触摸启动这个程序,在iPhone的system log中会打印:

Sat Mar 12 18:53:38 unknown Test86[7900] <Warning>: This is message from NSLogSat Mar 12 18:53:38 unknown UIKitApplication:com.yourcompany.Test86[0x7d60][7900] <Notice>: This is message from stderr

说明确实stderr在user 自己launch的app中被重定向为system log,而且log的等级为Notice;NSLog的等级为Warning。

 

2. system log

其实system log是unix系统都有采用syslog协议的一个日志系统(RFC详细讲解了这种协议http://tools.ietf.org/html/rfc5424)。每条日志是有等级的,主要分为如下等级:

  • Level 0 – “Emergency”
  • Level 1 – “Alert”
  • Level 2 – “Critical”
  • Level 3 – “Error”
  • Level 4 – “Warning”
  • Level 5 – “Notice”
  • Level 6 – “Info”
  • Level 7 – “Debug”

在创建好日志之后,通过调用API发送日志信息给一个叫做syslogd的守护进程,然后syslogd根据自己的配置文件(位于/private/etc/syslog.conf, 具体怎么配置这篇文章说得很详细 http://study.chyangwa.com/IT/AIX/aixcmds5/syslogd.htm),最后讲日志保存早自己的系统日志“数据库”里面。有兴趣的可以去打卡这个syslog.conf文件看看,我Mac上的配置文件是这样的:

*.notice;authpriv,remoteauth,ftp,install,internal.none /var/log/system.log(此处省去若干字。。。)

其中” *.notice “ 指明了任何等级比notice高的等级都要录入到 /var/log/system.log 这个文件中去。

在mac os和ios那么怎样调用  API讲日志发送给系统日志呢?有两种API:

这里还有一些讲Syslog不错的文章,:

“Accesing the iOS system log” :  http://www.cocoanetics.com/2011/03/accessing-the-ios-system-log/

“Why ASL?”  http://boredzo.org/blog/archives/2008-01-20/why-asl

 

3. NSLog

NSLog应该是我们最熟悉的方式,其实也应该是每一个学习Objective C第一句会的语法,然后你对它真正了解多少?NSLog顾名思义,出去namespace NS 就是Log,其主要的功能就是为Cocoa程序编写人员提供一种简单的输入日志的方式。但是我们很多时候都讲其误认为是printf,而且也只是当printf用。如果是这样就太可惜了。

NSLog is a high-level API for logging which is used extensively by Objective-C code. The exact behaviour of NSLog is surprisingly complex, and has changed significantly over time, making it beyond the scope of this document. However, it’s sufficient to know that NSLog prints to stderr, or logs to the system log, or both. So, if you understand those two mechanisms, you can see anything logged via NSLog.[引用自TN2239]

看见这个surprisingly complex也许就知道NSLog也许没那么简单了,我讲在另外一篇文章中详细讲解NSLog, 《iOS/Mac OS 调试信息输出(二)之 NSLog没那么简单》

 

iOS/Mac OS 调试信息输出(二)

8 Comments

NSLog,没那么简单

在前一篇《iOS/Mac OS 调试信息输出(一)》中介绍了输出调试信息的方法。现在讲主角:NSLog。

1.NSLog工作流程

前面已经说过我们很多时候都只是讲NSLog当做是printf,比printf好就好在:1. 自动添加了换行符;2. 在信息头还添加了一些其他更易于阅读和标示的信息,如“2011-03-12 20:18:34.000 Test14[40871:903]”。但其实NSLog更重要的功能是Log功能。看了下面这幅图你也许久明白了NSLog的工作原理了。

NSLog工作流程图

所以从图中可以看出NSLog的工作主要是分为:1. 输出信息到终端,2. 输出信息到System Log中去。

一般开发的时候程序都是从Xcode中启动,所以这个时候NSLog就具有打印信息到终端的能力就像printf一样(说fprintf(stderr, ….) 更合适一点)。而且很自然也就认为NSLog只是用来打印这些调试信息用的。

但是当程序不是从xcode或者ternimal启动等时,因为stderr不是被定向到标准终端这个时候NSLog的功能就是把信息输入到System Log中去。如果这个时候你也用的stderr输入信息,这个时候stderr会被定向到System Log中去。

2. Log结构

我之前说过Log信息是有level之分的,完整的Log是包含很多key-value形式的其他信息的,下面我们看一个通过NSLog输入到System Log的Log实例:
{
ASLMessageID = 3827200;
“CFLog Local Time” = “2011-03-12 16:31:02.592″;
“CFLog Thread” = 903;
Facility = “com.apple.console”;
GID = 20;
Host = “Xu-Benyangs-MacBook-Pro”;
Level = 4;
Message = “Hello World!”;
PID = 27692;
ReadUID = 501;
Sender = Test14;
Time = 1299918662;
TimeNanoSec = 593334000;
UID = 501;
}

其中我们比较关注的有这几项:

  1. Facility:用一个反链接形式字符串标示发送Log的来源,比如NSLog内部就使用的是com.apple.console
  2. Level: 等级,NSLog的等级是4,也就是Warnning,根据之前我们多/private/etc/syslog.conf 查看,只要是在Notice(5)之上的Log都可以被记录到/private/var/system.log中去。所以你调用一个NSLog你就可以去看看你的system.log文件
  3. Message: 就是消息本省
  4. Sender:你的应用程序名称
所以从上面信息可以看出NSLog的Log是:Facility=“com.apple.console”; Level=4(每一条NSLog都当做是一个Warning Log)的信息。而顺便提一下stderr如果被定向到System Log,那么是:Facility=“user”, Level=5(每一条是当做一个Notice的Log)。

3. 查看Log的方法

那么NSLog既然主要作用是用来发送System Log的,那么如何可以方便查看这些Log呢?特别是哪些搞测试和调程序的同学来讲,看Log是做常见的事情,所以这里给出在Mac和iOS下在没有源代码或者脱离了Xcode运行环境怎么看Log的方法。

Mac

  1. 如果你的systemlog.conf配置正确,就可以直接去查看这个文件 /private/var/log/system.log
  2. 打开/Applications/Utilities/Console这个程序,在Log List中选Console Messages就可以查看所有程序的NSLog打印出来的信息了。如图:

如图所示,Console中也提供了查看system.log的快捷方式。这里可能对你造成困惑,为什么有了system.log还有一个Console Messages。其实说白了Console Messages只是讲system log 中facility为“com.apple.console”的都筛选出来了,所以你就姑且可以把Console Messages中的信息认识全部是NSLog产生的。但是其实如果你自己发送一个facility=“com.apple.console”的日志,同样会出现在Console Messages中。

iOS
在真机上调试程序的时候打开Xcode 中的Oganizer, 然后在你当前的device下选取Console就可以看到这个Device的System Log了,如图。


这里我用的是xcode 4,所以界面和你的也许会不一样。

4. 程序员使用NSLog注意事项

在了解了NSLog的工作原理之后,你还敢像只是用printf那样使用NSLog了吗?我可以很负责人的告诉你虽然苹果的设备硬件条件都很牛B,但是NSLog还是是一件非常expensive的事情,主要体现在这两点:
  1. NSLog在打印信息的同时要发送日志,效率低
  2. 程序一些调试信息通过NSLog发送到System Log之后,很容易被其他人查看到,对你程序的安全性造成了直接的威胁。除了通过xcode的oganizer可以查看console,在iPhone上有一个叫ConsoleLog的程序可以查看你的这些日志(http://itunes.apple.com/us/app/consolelog/id415116252?mt=8)。
所以综合这些因素,我们应该在程序release的时候尽量去掉NSLog,但是如果程序大了一个一个去删NSLog似乎也不现实。于是网上就有不少牛人提供了不少的解决方案。推荐一篇著名的Cocoa Is My Girlfriend的Dropping NSLog in Release Build” : http://www.cimgf.com/2009/01/24/dropping-nslog-in-release-builds/ .

最后,感谢CamScanner帮我把手绘直接扫描成电子版。

 

用NSZombieEnabled处理EXC_BAD_ACCESS

1 Comment

一直有件很头痛的事情就是在debug程序的时候经常出现程序crash然后在console就只是打印了EXC_BAD_ACCESS, 完全不知道问题具体出在哪里。这种情况一看就知道是对象指针出了问题,很大部分时候都是因为再次使用了一个已经完全dealloc的object。对于console的这种不负责任的报错,大家都表示纷纷不给力。其实只要你google一下EXC_BAD_ACCESS,就会得到很多很多的解决方案,这里我搜集了很多我认为讲得不错的文章和大家分享。

  1. CocoaDev,个人觉得讲Cocoa技术十分专业的网站之一,下面的链接详细讲了讲NSZombieEnable的原理。http://www.cocoadev.com/index.pl?NSZombieEnabled
  2. 苹果官方的Mac OS X Debugging Magic, 详细讲述了最为一个高级苹果程序员应该具备的调试技巧 http://developer.apple.com/library/mac/#technotes/tn2004/tn2124.html
  3. 其实还可以在Instruments中开启NSZombie选项,这样就可以在Instruments中直接查看crash时候的call stack了:http://www.markj.net/iphone-memory-debug-nszombie/

最后提醒NSZombieEnabled只能在调试的时候使用,千万不要忘记在产品发布的时候去掉,因为NSZombieEnabled不会真正去释放dealloc对象的内存,一直开启后果可想而知,自重!

NSString 最美的创建方式也是最毒

3 Comments

今天睡觉前,看到一位学妹在淫淫网站上发了一篇关于Cocoa开发的技术文章,在这里http://blog.renren.com/blog/251384541/715015230,问为什么在代码中很少看到这样创建一个NSString对象的:

NSString *str = @”I LOVE TJAC”;

这是最简单最美丽地创建一个NSString对象的方法,估计也是列为开发人员最喜欢用的方式,而且应该很多人至今都还这样用。但是这样做其实是有很大的缺陷的。在官方String Programming Guide中是这样说的:

—————————————————————————————————————–

 

The simplest way to create a string object in source code is to use the Objective-C @”…” construct:

NSString *temp = @”/tmp/scratch”;

Note that, when creating a string constant in this fashion, you should avoid using anything but 7-bit ASCII characters. Such an object is created at compile time and exists throughout your program’s execution. The compiler makes such object constants unique on a per-module basis, and they’re never deallocated, though you can retain and release them as you do any other object. You can also send messages directly to a string constant as you do any other string:

BOOL same = [@"comparison" isEqualToString:myString];

—————————————————————————————————————–

 

所以如果你的程序中有这样的方式赶紧改过来吧,如果你觉得用类似与[NSString StringWith....]这样函数很麻烦,那么你可以些这样一个宏来简化你的工作:

#define OC(str) [NSString stringWithCString:(str) encoding:NSUTF8StringEncoding]

于是就可以方便使用了:NSLog(@”%@”, OC(“Hello World!”) );

Older Entries

  • RSS
  • Twitter
  • Buzz
  • LinkedIn
  • Flickr