Category Archives: Coding

JSONKit :Illegal \u Unicode escape sequence

When JSON string contains unicodes between u0000 and u001f, JSONKit parser fails to work properly. and throws a error as “Illegal \u Unicode escape sequence”.

This is a known issue(link) but seems like the author thought it’s a fault caused by content provider and didn’t intend to have any fix on this issue.

In this particular case, these services are very clearly “in the wrong”. RFC 4627 is unambiguous that characters < 0x20are verboten. In cases like there, where something is clearly violating the standard, my default response is that “It’s the other persons (web service) problem.” The standard is the standard, and it is Right(tm), even its mistakes.

But in fact there are many JSON encoder may generates JSON string contains invalid characters < 0×20 including Python2.6, <not tested in Python2.7>, pre-rails3. We all love the clean codes but we need make things done first.

SOLUTION:

Edit JSONKit.m file:

 //GOTO Line 1462 or nearby
//      remove this line
//      if(JK_EXPECT_F(currentChar < 0x20UL)) { jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing; }
//      add following codes
        if(JK_EXPECT_F(currentChar < 0x20UL) && (parseState->parseOptionFlags & JKParseOptionLooseUnicode) == 0) {
            jk_error(parseState, @"Invalid character < 0x20 found in string: 0x%2.2x.", currentChar); stringState = JSONStringStateError; goto finishedParsing;
        }
        else {
            currentChar = 0xFFFDUL;
        }

Apple LLVM 4.0 带给ObjC的新语法

下载了Xcode4.4 Preview版本, 里面有段介绍:

Xcode now includes the Apple LLVM Compiler version 4.0, including the following newObjective-C language features:

  • Default @synthesize: automatically synthesizes an @property when unimplemented
  • Objective-C literals: create literals for NSArray, NSDictionary, and NSNumber, just the same as the literals for NSString
  • Objective-C container subscripting: use ‘[]‘ syntax to access containers such asNSArray and NSDictionary

一和三很好理解, 第二条没看懂. 搜索了下立马爽歪了. 这几个新特性真可以加快不少开发速度!

Objective-C literals

NSArray Literals

Previously:

array = [NSArray arrayWithObjects:a, b, c, nil];

Now:

array = @[ a, b, c ];

NSDictionary Literals

Previously:

dict = [NSDictionary dictionaryWithObjects:@[o1, o2, o3]
					forKeys:@[k1, k2, k3]];

Now:

dict = @{ k1 : o1, k2 : o2, k3 : o3 };

NSNumber Literals

Previously:

NSNumber *number;
number = [NSNumber numberWithChar:'X'];
number = [NSNumber numberWithInt:12345];
number = [NSNumber numberWithUnsignedLong:12345ul];
number = [NSNumber numberWithLongLong:12345ll];
number = [NSNumber numberWithFloat:123.45f];
number = [NSNumber numberWithDouble:123.45];
number = [NSNumber numberWithBool:YES];

Now:

NSNumber *number;
number = @'X';
number = @12345;
number = @12345ul;
number = @12345ll;
number = @123.45f;
number = @123.45;
number = @YES;

原文Link

OSX下自定义文件类型和QuickLook

研究了几个小时如何自定义文件类型和自定义的QuickLook, 怕以后忘记怎么操作了. 总结下流程和写下简单的教程.

如果
  • 你想让你的程序支持自定义的文件类型
  • 让自定义的文件支持苹果的 Quick Look (大致就是选中文件按下空格后的预览图)
请继续看 –>
测试的例子是 Single Window Application + Custom bundle file type
  1. 不只是文档程序,单窗口程序也能支持响应自定义的文件类型
  2. Bundle 简单的说就是个文件夹, OSX系统可以将bundle识别单独的文件: 比如你的app就是个bundle. bundle在OSX下有许多作用,甚至还能对现有的app提供插件支持.
我们的目标是让 <name>.xxx 文件夹(里面有个叫xxx.jpg的图片)
  • 被识别成bundle
  • 有个独立的图标
  • 能被 app 打开并在窗口显示 xxx.jpg
  • 能提供Preview并显示 xxx.jpg
马上开始,为了节约时间略了些细节和图片描述, 请自行补充,无需100%按部就班

Part I

创建自定义文件:  新建个 name.xxx 的文件夹并丢入一照片命名为 xxx.jpg

Xcode中新建一个App: 在MainMenu里的view中拖入一个ImageView 并绑定到 AppDelegate 的imageView属性.

给App加个ICON: 拖入图标后,在info.plist里的图标选项里补充上名字

App注册自定义文件: 在info.plist 里添加新字段 UTExportedTypeDeclarations (自动补全后应该是array内容) 编辑内容如下:

UTTypeConformsTo 是指我们自定义文件的类型,  identifier和filename-extension是至次自定义文件的标识和后缀.

App注册文档文件: 只有注册指定的UTI(上步骤)为documentType, 才能使用此App打开该类型的文件. plist里添加新key,如图操作

每个key内容就不解释了,自己查阅文档 :D

编译运行App,然后关闭.  你应该可以看到 name.xxx 文件夹已经显示为一个带着你指定图标的文件了. 当然还是可以通过点右键-> Show Package Contents 来查看包内的内容.

Part II

目前双击 name.xxx 已可以成功打开我们的App, 但是有个”无法打开***”的错误提示.

添加app文件打开处理: 在AppDelegate.m 里添加如下代码

- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
{
    NSString*path = [filename stringByAppendingPathComponent:@"xxx.jpg"];
    NSImage* img = [[NSImage alloc] initWithContentsOfFile:path];
    if (img) {
        _imageview.image = img;
        return YES;
    }else{
        return NO;
    }
}

现在已经可以成功处理打开自定义文件了 Wowowow! (测试中有个问题,返回NO也没出现 出错提示, 看来需要自己判断文件有效性 并弹出错误提示了 )  ,接下来就是非常好玩的 Quick Look 了!!!

Part III

选中 name.xxx , 按下空格后出现的放大的图标. 看起来还不错? NO, 必须得能够显示包内的图片才够完美啊.

PS. 其实让Package 文件支持Preview是非常容易的, 在包内新建 QuickLook 文件夹, 并放入 Preview.jpg 即可. 不过此方法不够灵活,  我就蛋疼的演示下写个自己的 Quick Look 插件.

Quick Look 插件是后缀为 .qlgenerator 的bundle, 有3处地方可以放置此插件

  • /Library/QuickLook       <系统级别的,安装iWork后里面就有个给iWork文档提供preview的插件>
  • ~/Library/QuickLook       <用户级别的>
  • <name>.app/content/Library/QuickLook   <App级别的>
我们这里选择方案3 <App级别> 开始:
App 工程新建Target: 选择 System PlugIns / QuickLook Plug-In
目录中生成了3个文件, 根据提示咱们不要改动main.c 文件, 将其他两个.c文件修改为.m , 由于对于CoreFoundation的函数实在没好感, 修改为.m 后方便继续使用高级别的API进行操作.
给此Target添加framework: cocoa.framework  (选中项目,右边的target, build parses / Linked With Libraries 里添加)
修改 GeneratePreviewForURL.m 里的同名方法如下 :
OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options)
{
    NSAutoreleasePool *pool;
    pool = [[NSAutoreleasePool alloc] init];
    NSURL*imgurl = [(NSURL*)url URLByAppendingPathComponent:@"oo.jpg"];
    NSLog(@"!xxx get path %@",imgurl);
    NSImage* img = [[NSImage alloc] initWithContentsOfURL:imgurl];
    if (img) {
        NSSize size = img.size;
        CGContextRef cgContext = QLPreviewRequestCreateContext(preview, *(CGSize *)&size, true, NULL);
        if (cgContext) {
            NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:(void *)cgContext flipped:YES];
            if (context) {
                [NSGraphicsContext saveGraphicsState];
                [NSGraphicsContext setCurrentContext:context];
                [img drawAtPoint:NSMakePoint(0, 0)
                        fromRect:NSMakeRect(0, 0, 0, 0)
                       operation:NSCompositeCopy
                        fraction:1];
                [NSGraphicsContext restoreGraphicsState];
                QLPreviewRequestFlushContext(preview, cgContext);
                CFRelease(cgContext);
            }
        }
        [img release];
    }
    [pool release];
    return noErr;
}

贴的代码有点多, 简单说下步骤

  • 根据传过来的url 生成 NSImage
  • 获取到 Preview的上下文, 并转换成 NS* 的上下文
  • 将图片绘制进当前的上下文
  • 然后刷新 Preview 显示内容
注意2点: 由于使用了foundation, 所以需要加入Autorelease pool.  由于plugin不好调试, 可以使用NSLog打入消息, 然后在 console 里 filter 来查看状况.
最后一步, 回到App的项目设置里, 新建一个 copy 的 Build Phases, 设置如下 : 

再次编译运行App后, 测试下是否成功!!!!!

DONE

ObjC中URL编码

NSString类已经中提供了现成的API:

/* Adds all percent escapes necessary to convert the receiver in to a legal URL string.
  Uses the given encoding to determine the correct percent escapes (returning nil if
the given encoding cannot encode a particular character).  See
CFURLCreateStringByAddingPercentEscapes in CFURL.h for more complex transformations
*/
- (NSString *)stringByAddingPercentEscapesUsingEncoding:(NSStringEncoding)enc;

但是非常重要的一点苹果没注明:该API并不会将”& + -”等需要转义的字符编码。
如若将需要传递的某个param的内容通过该API来转义就可能坑爹了!!你内容中的&=不会被转义,服务端就会把原来的内容根据&和=拆开成一片一片了!!

解决方法是使用介绍中提到的CFURLCreateStringByAddingPercentEscapes

@implementation NSString (URLEscaped)
- (NSString *)URLEscaped {
	CFStringRef escaped = CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)self, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]",
			 kCFStringEncodingUTF8);
	NSString *out = [NSString stringWithString:(NSString *)escaped];
	CFRelease(escaped);
	return [[out copy] autorelease];
}

需要注意的是不能将这个函数用来 encode 整个URL,而是params的内容


写这篇文章是因为自己也掉了这个大坑,而且半年后才注意到。惨吖,不知道收集的10多w条数据中有多少是内容破坏的 – -

LessLyrics 重启

原谅4月份时候的大放厥词 “LessLyrics歌词秀后续计划

今天写这文章时才发现居然又过去了5个月。这几个月里辞去了百度的工作,和朋友们开始了全新的创业之路,断断续续的也就选择性的忘记了这个升级计划。

当然还有2个原因,计划的0.8版本中无缝滚动动画效果依赖非常精确的iTunes播放时间,但是能取到最小的间隔是1秒,所以处理起一些细节会比较麻烦。但是呢从当时的预览视频可以看到其实当时完成度已经很高了,在我开心的向datou炫耀这个超级华丽无缝歌词滚动动画效果,结果datou研究了很久后,说了句。不好。

虽然不愿意承认这么帅气的效果被否决,但仔细想确实有很大的问题。无缝滚动效果会导致整个界面一直处于动画中,当前高亮播放中的歌词也一直在逐渐向上滚动,会导致使用者为了fo这行歌词一直让眼球向上扫描,接着目光聚集到下面的歌词,然后又继续向上扫。看久了就会觉得挺累。

昨天在我们的app内测qq群中有用户问要显示歌词的软件。我赶紧推荐了LessLyrics,结果人家居然说,居然说,说:“这个app早就知道了!!我想要iPhone版本的”

于是我爽到现在,于是决定必须立刻重新启动LessLyrics软件计划了!

新的0.8版本没增加太多的功能,因为迫不及待要发布新版本收集下反馈。这次完全重写了app的设计结构和渲染模块(现在看1年前刚学习mac开发的代码真的很戳),接下来的添加新功能就方便了。

不用期待,几小时内发布。

哈哈哈哈哈哈哈哈哈哈哈哈哈哈。

下载请移步至 项目页面

CrazyText – My First MacRuby App

Crazy Text screenshot

前不久听说MacRuby 0.10 发布,并支持了Mac App Store 的发布.

于是边学习Ruby边学习MacRuby边学习cocoa 写了这么一个东西.

A MacOSX app written in Macruby to create some funny  effects for your texts.

提交时候提示MacRuby.framework内的几个link位置不对. 忘记什么提示了,需要手动修复几个link文件的地址.

对了1 :源码请移步 github-> https://github.com/xhan/CrazyText

对了2:

可以在appstore下载哦

部分UI仍然使用 objc编写(有些类和方法实在不知道如何用ruby实现)

个人感觉macruby 的学习曲线还真蛮高的,会cocoa,会ruby,还得会MacRuby,非常坑人,而且调试也非常又难度.

UIViewController 的内存管理

在iOS3.0后,UIViewController多了一个叫做viewDidUnLoad的方法.不少人都不清楚这个方法的具体意义,苹果的文档也就一句”Called when the controller’s view is released from memory” 简单的解释了下,并要求你把IBOutlet绑定的视图给清空,为什么呢?

先看下UIViewController从创建view到展示的流程的几个函数

-init
-initWithNibName:bundle:

这两个方法都是初始化一个vc,但请注意view不是这时候载入的

-loadView
-viewDidLoad

当一个视图准备展现时,vc首先会判断view是否已经创建,否则便通过之前指定的xib文件来初始化view,以及绑定其他关系(若没有指定xib文件,则默认会搜索和vc同名的xib,比如myNameViewController就会搜索 myNameViewController.xib文件)

若是没有xib文件,你就可以在loadview中自己手动创建这个viewControoler需要的视图.
接下来就是调用到 -viewDidLoad,许多人喜欢在这里做些其他事情,比如做个http请求,建立个数组啥的, 这里若不处理正确, -viewDidUnload 激活时内存就容易泄露了,稍后提到.

-view()appear
-view()disappear

这几个方法就不解释了

-viewDidUnload

该方法在收到内存警告,同时该视图并不在当前界面显示时候会被调用,此时该controller的view已经被释放并赋值为nil.
接下来你要做的是

  1. 把实例变量的子视图释放(IBOulet的,以及自己添加的),.
  2. 其他实例变量,比如之前在-viewDidLoaded中实例的数据数组,http请求释放掉.

因为当该viewController再次被激活准备显示时(比如navigationControler返回到上一级),vc发现自己的view为空后会重复之前的流程直到把view给创建起来,若没将自己额外添加的子视图,各种类实例变量释放,这里便会重新再次创建.

于是,内存泄露了.

Read UTF8 code at specify position from an NSString

如何获取nsstring制定某个位置的utf8编码的字符呢?

我也不知道,所以在stackoverflow问了下.

很快就有人回复了解决方案.

在贴代码之前写介绍下几个编码格式:

UTF8 和 UTF16均为字符编码方式.

UTF-8使用一至四個位元組為每個字符編碼:

  1. 128個US-ASCII字符只需一個位元組編碼(Unicode範圍由U+0000至U+007F)。
  2. 帶有附加符号拉丁文希臘文西里爾字母亞美尼亞語希伯來文阿拉伯文敘利亞文它拿字母則需要二個位元組編碼(Unicode範圍由U+0080至U+07FF)。
  3. 其他基本多文種平面(BMP)中的字元(這包含了大部分常用字)使用三個位元組編碼。
  4. 其他極少使用的Unicode 輔助平面的字元使用四位元組編碼。

UTF-16Unicode的其中一個使用方式。UTF是Unicode/UCS Transformation Format,即把Unicode轉做某種格式的意思。

其編碼方法是:

1如果字符編碼U小於0×10000,也就是十進制的0到65535之內,則直接使用兩字節表示;

2如果字符編碼U大於0×10000,由於UNICODE編碼範圍最大為0x10FFFF,從0×10000到0x10FFFF之間 共有0xFFFFF個編碼,也就是需要20個bit就可以標示這些編碼。用U’表示從0-0xFFFFF之間的值,將其前 10 bit作為高位和16 bit的數值0xD800進行 邏輯or 操作,將後10 bit作為低位和0xDC00做 邏輯or 操作,這樣組成的 4個byte就構成了U的編碼。

而NSString使用的就是unicode存储的, 唯一一个获取unichar的方法叫做 -characterAtIndex: , 但是问题是 unichar 其实是个 unsigned short,也就是2个字节,所以它并不能展现所有的字符.

终极解决方法:

@interface NSString (UTF8)
- (NSRange) rangeOfUTFCodePoint:(NSUInteger)number;
@end
@implementation NSString (UTF8)
- (NSRange) rangeOfUTFCodePoint:(NSUInteger)number
{
    if (number >= [self length]) {
        return NSMakeRange(NSNotFound, NSNotFound);
    }
    NSUInteger codeUnit = 0;
    NSRange result;
    for(NSUInteger ix = 0; ix <= number; ix++)
    {
        result = [self rangeOfComposedCharacterSequenceAtIndex:codeUnit];
        codeUnit += result.length;
    }
    return result;
}
@end

关于豆瓣电台osx版

突发奇想为什么不做个豆瓣电台的osx版本呢

于是就有了这个开源的项目 http://ixhan.com/project/douban-fm-osx/

后发现Du Song同学的FanRadio: Free Music For Mac 已做的无懈可击,同时twitter上朋友告诉我dashboard已经支持后台的音乐播放了。

有点多此一举的感觉。= =

不过个人还是喜欢一个原生的播放器浮在桌面上的感觉拉。

原计划0.2版本中会提供解析当前歌手名,歌曲名并可以同步更新至adium的状态,以及歌词显示功能。

坏消息是今天发现从flash中获取到内容相当困难。

好消息是通过抓包发现了豆瓣电台的接口,相当简单。可以做不少好玩的东西去。

栽大了之Objc过度释放对象

事情是这样的,4个月前我写了份实现类似下拉框选择操作的界面.
一个月前,发现了这个View 在dealloc 会crash掉.
多次调试无果,上 devForum.apple.com 询问也无人问津.
今天决定再次调试下,还是没找到哪里出问题.
最后决定求助 cocoachina 上的现场观众
最后开始一行一行的注释代码做终极调试.

离谱的事情发现了,只要我创建一个名为 mainText 的 UILabel ,在dealloc 中程序就会crash ,改成其他名字无事.难道是apple的bug? 新写了个view测试了,还是没问题.

接着接着就发现了在dealloc方法中:
[mainText release] ,mainText = nil;
// NSLog(@"release %@",bgView);
[bgView release];
// NSLog(@"release %@",originView);
[originView release];
// NSLog(@"release %@",labelArray);
[labelArray release];
// NSLog(@"release %@",mainText);
[mainText release];

该死的,不知道当时那根神经错了,居然释放了两次,当然出错咯.问题也解决了.

所以在确认释放对象的情况下一定要写成:
[instance release],instance = nil ;

个人经验是,对于objective-c的内存管理都是得经过磨练才出来的,前期多犯错误是好事情.
当然我认识一个朋友 ,他不释放任何对象,因为反正关闭iPhone后系统会处理的.这种觉悟不是大部分程序员有的,大家还是稳步前进咯.