iOS上mxnet的一个演示App

mxnet是最近火的不行的一个深度学习的框架,支持好多好多语言,有好多好多大牛在写。

之前也有想过把同样很牛很牛的caffe跑到iOS上看看速度怎么样,但是caffe有一大堆文件,感觉做起来很麻烦。

最近看到mxnet居然有一个单文件的版本
就做了一个简单的图像识别的演示App。跑在6上速度还可以,大概4秒一张图。

代码在这里:

WhatsThis-iOS on Github

iOS逐帧处理录像-MPVideoProcessor

一般在iOS上做录像都可以直接使用UIImagePickerController。
但有时候难免需要做逐帧的处理,比如实时的滤镜之类的。

参照这个帖子: A (quasi-) real-time video processing on iOS 把使用AVFoundation做录像的代码,做了一个简单的封装。

Delegate可以得到逐帧的彩色或者灰度图,然后就可以加上自己需要的处理了。

代码放在Gihub上:MPVideoProcessor
具体使用请参见Github上的Readme.

iExplorer

玩iOS的好东西。

这个不是微软的IE,而是iPhone Explorer,应该是因为Apple的压力,名字改成了iExplorer,据说图标也小有改动。

不需要Jailbreak你的iOS设备,连上机器之后打开iExplorer就可以看到各个App下的内容和系统的一些文件。
一般来说,App中用户的数据都是放在App Name/Documents这个文件夹下面,比如游戏存档…
iExplorer可以自由地存取文件,所以可以干的事情就多了…

软件主页上提供的截屏:

[iOS]How to save and load a custom object?

Here we go.

How to write a custom object which can be archived to a text file?

Usually, we use NSKeyedArchiver to serialize an object and write it to a file.
Correspondingly, NSKeyedUnarchiver is used to get the object from the file.

The NSKeyedArchiver’s interface is like this:

+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;

NSKeyedArchiver is a NSCoder. The rootObject should conform to the protocol NSCoding.

@protocol NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;

@end

For example, now we have a customObject:

@interface CustomObject : NSObject  {
    NSString*   mStringValue;
    int         mIntValue;
    BOOL        mBOOLValue;
}

@property (non-atomic, retain) NSString *stringValue;
@property (nonatomic, assign) int       intValue;
@property (nonatomic, assign) BOOL      boolValue;

@end

It conforms to NSCoding, so we need to implement those two methods in the .m file

#define kEncodeKeyStringValue   @"kEncodeKeyStringValue" 
#define kEncodeKeyIntValue      @"kEncodeKeyIntValue" 
#define kEncodeKeyBOOLValue     @"kEncodeKeyBOOLValue" 

#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:self.stringValue   forKey:kEncodeKeyStringValue];
    [aCoder encodeInt:self.intValue         forKey:kEncodeKeyIntValue];
    [aCoder encodeBool:self.boolValue       forKey:kEncodeKeyBOOLValue];
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    if ((self = [super init]))
    {
        self.stringValue = [aDecoder decodeObjectForKey:kEncodeKeyStringValue];
        self.intValue    = [aDecoder decodeIntForKey:kEncodeKeyIntValue];
        self.boolValue   = [aDecoder decodeBoolForKey:kEncodeKeyBOOLValue];
    }
    return self;
}

Now, we can save and load a customObject like:

- (IBAction)saveObjectPlain:(id)sender {

    printf("==========================================\n");
    printf("saveObjectPlain===========================\n");
    printf("==========================================\n");

    //< Create and Save the Object
    {
        CustomObject *obj = [[[CustomObject alloc] init] autorelease];
        obj.stringValue = @"The String Value";
        obj.intValue    = 12345;
        obj.boolValue   = YES;
        [NSKeyedArchiver archiveRootObject:obj toFile:[self tempFilePath]];
        printf("Save: \n %s \n", [[obj description] cStringUsingEncoding:NSUTF8StringEncoding]);
    }
    
    //< Load and Print the Object
    {
        CustomObject *obj = [NSKeyedUnarchiver unarchiveObjectWithFile:[self tempFilePath]];
        printf("Load: \n %s \n", [[obj description] cStringUsingEncoding:NSUTF8StringEncoding]);
    }
    
    printf("==========================================\n");
}

Quite easy, right?

How about to save/load an array of our CustomObjects?
Since NSArray conforms to protocol NSCoding, it is quite straightforward.

- (IBAction)saveObjectsInArray:(id)sender {

    printf("==========================================\n");
    printf("saveObjectsInArray========================\n");
    printf("==========================================\n");
    
    //< Create Two Keys and Save the Object
    {
        CustomObject *obj1 = [[[CustomObject alloc] init] autorelease];
        obj1.stringValue = @"The String Value 1";
        obj1.intValue    = 12345;
        obj1.boolValue   = YES;
        
        CustomObject *obj2 = [[[CustomObject alloc] init] autorelease];
        obj2.stringValue = @"The String Value 2";
        obj2.intValue    = 54321;
        obj2.boolValue   = NO;
        
        NSArray *array = [NSArray arrayWithObjects:obj1, obj2, nil];
        [NSKeyedArchiver archiveRootObject:array toFile:[self tempFilePath]];

        printf("Save: \n %s \n ", [[array description] cStringUsingEncoding:NSUTF8StringEncoding]);
    }
    
    //< Load and Print the Object
    {
        NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:[self tempFilePath]];
        printf("Load: \n %s \n ", [[array description] cStringUsingEncoding:NSUTF8StringEncoding]);
    }
    
    printf("==========================================\n");
}

So, if there is a member variable of type NSArray in our CustomObject,
we need to make sure the array contains only objects conform to NSCoding.
Otherwise, an exception of unrecognized selector [.. encodeWithCoder:] will be raised.

One step further, how to save/load a NSDictionary has a CustomObject as the key and another CustomObject as the Object.

This is actually another problem. We need to make sure CustomObject can be used as a key in the NSDictionary.
According to the documentation, NSDictionary requires its key to conform to the protocol NSCopying.

@protocol NSCopying

- (id)copyWithZone:(NSZone *)zone;

@end

The key object needs to be copied to a specified piece of memory described by NSZone.
The implementation is like:

#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
    CustomObject *copy = [[CustomObject allocWithZone:zone] init];
    copy.stringValue = [[self.stringValue copy] autorelease];
    copy.intValue = self.intValue;
    copy.boolValue = self.boolValue;
    return copy;
}

We use allocWithZone to allocate a CustomObject on the indicated piece of memory.
Then copy member variables to the new instance.

The save/load procedure is like:

- (IBAction)saveObjectAsKey:(id)sender {

    printf("==========================================\n");
    printf("saveObjectAsKey===========================\n");
    printf("==========================================\n");
    
    //< Create Two Keys and Save the Object
    {
        CustomObject *keyObj = [[[CustomObject alloc] init] autorelease];
        keyObj.stringValue = @"The String Value Key";
        keyObj.intValue    = 12345;
        keyObj.boolValue   = YES;

        CustomObject *valObj = [[[CustomObject alloc] init] autorelease];
        valObj.stringValue = @"The String Value Value";
        valObj.intValue    = 54321;
        valObj.boolValue   = NO;
        
        NSDictionary *dict = [NSDictionary dictionaryWithObject:valObj forKey:keyObj];
        [NSKeyedArchiver archiveRootObject:dict toFile:[self tempFilePath]];

        printf("Save: \n %s \n", [[dict description] cStringUsingEncoding:NSUTF8StringEncoding]);
    }
    
    //< Load and Print the Object
    {
        NSDictionary *dict = [NSKeyedUnarchiver unarchiveObjectWithFile:[self tempFilePath]];
        printf("Load: \n %s \n", [[dict description] cStringUsingEncoding:NSUTF8StringEncoding]);
    }
    
    printf("==========================================\n");
}

The whole demo can be found here:
Demo CustomObject NSCoding

Hope it helps! :]

----------------------------------------------------
When we were learning new things, we searched for answers in Google, from Stackoverflow and followed well-written tutorials. When other one faces problems we met and solved, we are glad to answer their questions, post tutorial for the related topics. This is called the spirit of Community. I love it.

NSURL

码农Coding的时候有各种不好的习惯。
比如,不喜欢好好地看框架的文档,一旦找到某一个看起来简单易懂的接口,就一直用它。
如果需要之后的处理,往往简单粗暴。
我们以前管这种情况叫做“裸”。最近又不知不觉地写了比较“裸”的代码。
说实话,这个习惯得改,“裸”写的东西往往不健壮,不可读,效率还不高。成熟框架提供的直接可用的接口必须是第一选择。

NSURL是常用的类,用来描述一段URL的。
需要取得URL中不同部分的时候,我们应该用URL提供的接口,
而不是把它当做一个普通的字符串去手工分析。

比如:
http://www.testurl.com:8080/subpath/subsubpath?uid=123&gid=456

NSURL *url = [NSURL URLWithString:@"http://www.testurl.com:8080/subpath/subsubpath?uid=123&gid=456"];

下面是常用的几个接口,和它们的输出。接口意思都符合相关RFC里的定义。

[url scheme]
http

[url host]
www.testurl.com

[url port]
8080

[url path]
/subpath/subsubpath

[url lastPathComponent]
subsubpath

[url query]
uid=123&gid=456


NSURL 英文版 :]


NSURL is widely used, but sometimes we are not following the idiomatic usage.

When it comes to access part of the url, it is better to use the methods NSURL provide, instead of analysis its absoluteString manually.

Take this one for example:

http://www.testurl.com:8080/subpath/subsubpath?uid=123&gid=456

NSURL *url = [NSURL URLWithString:@"http://www.testurl.com:8080/subpath/subsubpath?uid=123&gid=456"];

Here listed some frequently-used accessors and results:

[url scheme]
http

[url host]
www.testurl.com

[url port]
8080

[url path]
/subpath/subsubpath

[url lastPathComponent]
subsubpath

[url query]
uid=123&gid=456

[iOS]关于IBOutlet

为了使用Interface Builder,我们需要在如XXViewController.h中增加IBOutlet标记。
IBOutlet对于编译器而言只是一个标记,也就是说,编译器会忽略这个关键字。
Interface Builder则是根据IBOutlet来寻找可以在Builder里操作的成员变量。

需要注意的是,任何一个被声明为IBOutlet并且在Interface Builder里被连接到一个UI组件的成员变量,会被额外retain一次。
常见的情形如

IBOutlet UILabel *label;

这个label在Interface Builder里被连接到一个UILabel。此时,这个label的retainCount为2。
所以,只要使用了IBOutlet变量,一定需要在dealloc或者viewDidUnload里release这个变量。

[cocoa demo]从图片中选取Frame的小工具

和设计相比,开发者的好处是遇到不好用的App就可以牛B哄哄的说:
“这个不给力啊,等哪天有空的时候自己做一个吧”。
等到真正要做的时候就愣了,满脑子的按钮不知道怎么摆….

在学校的时候,做的东西对UI的要求很低,不求美观,只要基本功能有就行了。
更不要谈什么用户体验,是不是User-Friendly之类的了。
工作的时候就大不一样了,做prototype的时候界面可能很粗糙。
但是等到Designer的东西(一般管这个叫mockup)出来,感觉就完全不一样了。
作为码农,常常觉得自己没有审美观….

我认为完全重现mockup的效果是很重要的,尤其是一些细节。
功能上开发者可以发挥,但是在自己不擅长的领域还是不要胡闹了。

好了,扯远了….

其实只是因为最近想学mac上的开发,然后因为有以上需求,
所以做了一个非常简单的工具作为练习。
真的很简单….不过如果你恰好有同样的需求,我觉得它还是有帮助的。
至少作为一个cocoa入门的demo,还是挺好的。

GetFrame

就是从mockup里框取一块区域,得到经过转换坐标的Frame。
左上的Button是用来打开图片文件的,左下的是用来切换显示的。

代码在Github上: GetFrame
App下载链接: GetFrame.zip

mac下的图标格式是icns
用这个网站转换图标比较方便,推荐一下: http://iconverticons.com/

[iOS]delegate和protocol

今天上班和同事讨论工程怎么组织的时候涉及到这个话题。
iOS开发上对delegate使用广泛。
记在这里,如果有新人Google到了,希望能有点帮助。

protocol和delegate完全不是一回事,放在一起说,只是因为我们经常在同一个头文件里看到这两个word。

protocol和java里interface的概念类似,是Objective-C语法的一部分。
定义protocol如下

@protocol ClassADelegate

- (void)methodA;
- (void)methodB;

@end

那么就是定义了一组函数,这组函数放在一起叫作一个protocol,也就是协议。

函数是需要被实现的,所以如果对于class如下

@interface ClassB  {
}
@end

就叫作ClassB conform to protocol ClassADelegate,也就是说ClassB实现了这个协议,
也就是实现了这一组函数。

有了上面这个头文件,我们就可以放心作调用

ClassB *b = [[ClassB alloc] init];
[b methodA];
[b methodB];

而不用担心出现unrecognized selector sent to instance这种错误了。

所以protocol就是一组函数定义,是从类声明中剥离出来的一组定义。

id b = ...;
[b methodA];

这种用法也常见,b是一个id类型,它知道ClassADelegate这组函数的实现。

那么delegate是什么?其实和protocol没有关系。Delegate本身应该称为一种设计模式。
是把一个类自己需要做的一部分事情,让另一个类(也可以就是自己本身)来完成。
比如ClassC

@interface ClassC {
    id delegate;
}
@end

那么ClassC的实现(.m文件)里就可以用delegate这个变量了。
当然这里完全可以用其它名字而不是delegate。

我们也可以这样写

@interface ClassC {
    ClassB *delegate;
}
@end

这样我们知道了delegate是一个ClassB,它就可以提供ClassB里的方法。
可以把一部分ClassC里的工作放在ClassB里去实现。
这样的写法看起来是不是有点奇怪?或者应该写成这样?

@interface ClassC {
    ClassB *classB;
}
@end

…..

delegate没有了…
所以说其实delegate只是一种模式,大家约定俗成,当把自己内部一部分实现暴露给另外一个类去做的时候,就叫实际做事的类为delegate。

为什么会需要把内部实现提出来给另一个类做呢?
最常见的目的就是为了在隐藏实现的前提下,提供一个自定义的机会。
比如Apple提供的iOS SDK里就有众多的delegate,比如最常用的UITableView,
我们没法知道Apple怎么重用UITableViewCell,怎么处理UITableView里Cell的增加、删减,因为我们没有源码。
但是我们可以通过实现Delegate的方法来控制一个UITableView的一些行为。
UITableViewDataSource其实和delegate是一样一样的,只是由于意义不同换了个名字罢了。

protocol在此扮演了什么角色呢?
protocol是一种语法,它提供了一个很方便的、实现delegate模式的机会。
比如写UITableView的时候,Apple这么干
UITableView.m

- (void)doSomething {
    [self blahblah];
    
    [self.delegate guruguru];
   
    [self blahblah];
 }

delegate是我们写的类,这个类如果可以被传给UITableView做为其delegate,那唯一要求,就是它实现了

- (void)guruguru;

这个方法。

如果我们把这个方法定义在一个protocol里

@protocol XXXProtocol

- (void)guruguru;

@end

就说明了,UITableView需要的delegate是一个conform to XXXProtocol的类。
这就正好是

id

表达的意思。
无论具体的类是什么,它还有其它什么方法,只要它conform to这个protocol,
就说明它可以被传给UITableView,作为它的delegate。
那么Apple为了让我们知道这个protocol是delegate需要conform的protocol,
它就把XXXProtocol改成了UITableViewDelegate

这样我们看到protocol的名字里有Delegate,就知道这个protocol里的函数是用来做自定义(Customization)的了。

代码最终还是给人看的,公司里尤其如此。
大家都希望对方把事情讲得清晰易懂,如果在再有两句俗语或者行话那大家就很开心了 :]