KryoCocoa 是对优秀的 Kryo 序列化库(http://code.google.com/p/kryo/)进行 Objective-C 端的移植。它被设计成与 Kryo 和基本 Java 数据类型兼容。
Kryo *kryo = [Kryo new];
// ...
NSOutputStream *outputStream = [NSOutputStream outputStreamToFileAtPath:@"file.bin" append:NO];
KryoOutput *output = [[Kryo alloc] initWithStream:outputStream];
SomeClass *someObject = ...
[kryo writeObject:someObject to:output];
[output close];
// ...
NSInputStream *inputStream = [NSInputStream inputStreamWithFileAtPath:@"file.bin"];
KryoInput *input = [[KryoInput alloc] initWithInput:inputStream];
SomeClass *someObject = [kryo readObject:input ofClass:[SomeClass class]];
[input close];
KryoCocoa 是一个序列化框架。它不会强制执行模式或关心写入或读取哪些数据。这留给了序列化器自身。默认情况下,提供了序列化器来以各种方式读取和写入数据。如果这些不能满足特定的需求,可以用部分或全部替换。提供的序列化器可以读取和写入大多数对象,但在必要时编写新的序列化器也很容易。序列化器协议定义了将对象转换为字节以及从字节中获取对象的方法。
UIColorSerializer.h
#import <Foundation/Foundation.h>
#import "../Serializer.h"
@interface UIColorSerializer : NSObject<Serializer>
@end
UIColorSerializer.m
#import "UIColorSerializer.h"
#import "Kryo.h"
@implementation UIColorSerializer
- (void)write:(Kryo *)kryo value:(id)value to:(KryoOutput *)output
{
UIColor *colorValue = (UIColor *)value;
CGFloat red, green, blue, alpha;
[colorValue getRed:&red green:&green blue:&blue alpha:&alpha];
SInt32 rgbValue = ((SInt32)(red * 255) << 16)
| ((SInt32)(green * 255) << 8)
| (SInt32)(blue * 255);
[output writeInt:rgbValue optimizePositive:YES];
}
- (id)read:(Kryo *)kryo withClass:(Class)type from:(KryoInput *)input
{
SInt32 rgbValue = [input readIntOptimizePositive:YES];
return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0f
green:((rgbValue & 0xFF00) >> 8) / 255.0f
blue:((rgbValue & 0xFF) / 255.0f alpha:1.0];
}
@end
序列化器协议定义了两个必须实现的方法。方法 write:value:to 以字节的形式写入对象。方法 read:withClass:from 创建新实例,从输入端读取并填充它。Kryo 实例可以用来写入和读取嵌套对象。如果在 read:withClass:from 中使用 Kryo 来读取嵌套对象,则必须首先调用 [kryo reference:],前提是嵌套对象有可能引用父对象。如果嵌套对象不可能引用父对象,没有使用嵌套对象,或未使用引用,则不需要调用 [kryo reference:]。如果嵌套对象可以使用相同的序列化器,则序列化器必须是可重入的。代码不直接使用序列化器,而应该使用 Kryo 的读取和写入方法,这允许 Kryo 协调序列化和处理诸如引用和无对象之类的特性。
默认情况下,序列化器不需要处理为 nil 的对象。KryoCocoa 框架会根据需要写入字节,表示 nil 或非 nil。如果序列化器希望更高效,并自己处理 nil,它可以从 acceptsNull 返回 YES。这也可以用来避免在已知类型的所有实例永远不为 nil 的情况下写入表示 nil 的字节。
《KryoCocoa》框架目前基于《Kryo》v2.20,并且尽可能实现二进制兼容性。由于Objective-C语言不提供类似包、注解和泛型等特性,您必须以其他方式提供这些元信息。
《KryoCocoa》直接支持以下Java类型。
Java | Objective-C |
---|---|
bool | bool(不是BOOL,因为bool是一个内建类型,可以通过反射确定,而BOOL只是一个指向char类型的typedef) |
byte | char |
short | SInt16 |
int | SInt32 |
long | SInt64 |
float | float |
double | double |
char | unichar |
bool[] | JBooleanArray |
byte[] | NSData |
short[] | JShortArray |
int[] | JIntegerArray |
long[] | JLongArray |
float[] | JFloatArray |
double[] | JDoubleArray |
char[] | JCharacterArray |
Object[] | NSArray |
java.lang.Boolean | JBoolean |
java.lang.Byte | JByte |
java.lang Short | JShort |
java.lang.Integer | JInteger |
java.lang.Long | JLong |
java.lang.Float | JFloat |
java.lang.Double | JDouble |
java.lang.Character | JCharacter |
java.lang.String | NSString |
java.lang.StringBuilder | NSMutableString |
java.util.Date | NSDate |
java.util.List | NSArray |
java.util.Map | NSDictionary |
java.util.Locale | NSLocale |
由于objective-c不了解命名空间或包的概念,现在必须向KryoCocoa提供这些信息。您只需扩展您的类以使用SerializationAnnotation协议,并提供名为serializingAlias的方法,该方法必须返回全限定java类名(包括包),即可。这意味着,只要您通过serializingAlias提供了java类名,就不重要Objective-C类的名称是否与Java端相同。
SampleBean.java
package test;
public class SampleBean
{
private int key;
private String name;
public SampleBean()
{
}
public int getKey()
{
return key;
}
public void setKey(int key)
{
this.key = key;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
}
SampleBean.m
#import "SampleBean.h"
@implementation SampleBean
+ (NSString *)serializingAlias
{
return @"test.SampleBean";
}
SampleBean.h
#import <Foundation/Foundation.h>
#import "SerializationAnnotation.h"
@interface SampleBean : NSObject<SerializationAnnotation>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) SInt32 key;
@end
给定以下具有类型infoMap的Java实体,其中键类型为Integer,值类型为String。
OtherBean.java
package test;
import java.util.List;
import java.util.Map;
public class OtherBean
{
private Float price;
private Map<Integer, String> infoMap;
private List<SampleBean> elements;
public OtherBean()
{
}
public Float getPrice()
{
return price;
}
public void setPrice(Float price)
{
this.price = price;
}
public Map<Integer, String> getInfoMap()
{
return infoMap;
}
public void setInfoMap(Map<Integer, String> infoMap)
{
this.infoMap = infoMap;
}
public List<SampleBean> getElements()
{
return elements;
}
public void setElements(List<SampleBean> elements)
{
this.elements = elements;
}
}
为了通知KryoCocoa关于objective-c端的泛型类型,您必须提供一个名为<属性名>Generics的静态方法,该方法返回一个包含两个Class对象的NSArray,例如,在这种情况下为JInteger和NSString。
OtherBean.h
#import <Foundation/Foundation.h>
#import "SerializationAnnotation.h"
#import "JFloat.h"
@interface OtherBean : NSObject<SerializationAnnotation>
@property (nonatomic, strong) NSDictionary *infoMap;
@property (nonatomic, strong) JFloat *price;
@property (nonatomic, strong) NSArray *elements;
+ (NSArray *)infoMapGenerics;
@end
OtherBean.m
#import "OtherBean.h"
@implementation OtherBean
+ (NSArray *)infoMapGenerics
{
return [NSArray arrayWithObjects:[JInteger class], [NSString class], nil];
}
+ (NSString *)serializingAlias
{
return @"test.OtherBean";
}
@end
可以使用枚举作为属性类型。但在Objective-C端,这意味着您不能使用原生的C枚举,对应的类型必须实现为类。网上有一个非常细微的枚举实现(gandreas Blog),已被包含在KryoCocoa中。
SampleEnum.java
package test;
public enum SampleEnum
{
Sun, Earth, Moon
}
SampleEnum.h
#import <Foundation/Foundation.h>
#import "Enum.h"
#import "SerializationAnnotation.h"
@interface SampleEnum : Enum<SerializationAnnotation>
+ (SampleEnum *)SUN;
+ (SampleEnum *)EARTH;
+ (SampleEnum *)MOON;
@end
SampleEnum.m
#import "SampleEnum.h"
@implementation SampleEnum
ENUM_ELEMENT(SUN, 0, nil)
ENUM_ELEMENT(EARTH, 1, nil)
ENUM_ELEMENT(MOON, 2, nil)
+ (NSString *)serializingAlias
{
return @"test.SampleEnum";
}
@end
要创建与KryoCocoa兼容的枚举类,只需创建一个继承自Enum的新类(可选扩展 SerializationAnnotation 以注解全Java类名),为每个枚举常数添加一个静态方法,并在m文件中添加每个常量的ENUM_ELEMENT条目,其中ENUM_ELEMENT的第一个参数用于常量名称,第二个参数是顺序值,它必须与Java端的顺序值相对应,第三个参数是您的枚举常量的任何键/值对。有关更多信息,请参阅gandreas博客。重要的是,常量名称必须是大写,因为目前只能识别大写名称。
处理final类相对简单。如果您需要为类编写自己的序列化器,只需实现isFinal方法。如果您有一个简单的bean,不需要特别的序列化器,只需添加不带实现方法的FinalAnnotation协议。只要您的类实现了该协议,就足以将其标记为final。
如果您想将TaggedFieldSerializer用作默认序列化器,您的可序列化类必须遵循定义静态方法taggedProperties的TagAnnotation协议,该方法返回从属性名到标签值的字典。如果标签值为负,则视为已弃用。
TaggedBean.java
package test;
import com.esotericsoftware.kryo.serializers;
public class TaggedBean
{
@TaggedFieldSerializer.Tag(1)
private int value;
@TaggedFieldSerializer.Tag(2)
private String name;
@Deprecated
@TaggedFieldSerializer.Tag(3)
private int deprecatedField;
public TaggedBean()
{
}
public int getValue()
{
return value;
}
public void setValue(int value)
{
this.value = value;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getDeprecatedField()
{
return deprecatedField;
}
public void setDeprecatedField(int deprecatedField)
{
this.deprecatedField = deprecatedField;
}
}
TaggedBean.h
#import <Foundation/Foundation.h>
#import "SerializationAnnotation.h"
#import "TagAnnotation.h"
@interface TaggedBean : NSObject<SerializationAnnotation, TagAnnotation>
@property (nonatomic, assign) SInt32 value;
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) SInt32 deprecatedField;
@end
TaggedBean.m
#import "TaggedBean.h"
@implementation TaggedBean
+ (NSString *)serializingAlias
{
return @"test.TaggedBean";
}
+ (NSDictionary *)taggedProperties
{
return [NSDictionary dictionaryWithObjectsAndKeys:@1, @"value", @2, @"name", @-3, @"deprecatedField", nil];
}
@end
目前没有对克隆对象的支撑。在Java端,您可以为属性添加@NotNull注解,但当前尚未支持。
以下序列化器尚未移植,这不是技术问题,而是缺乏时间
JavaSerializer可能永远不会移植,因为Cocoa和Java的序列化API不同,但也许有人有好的点子。
由于NSDictionary要求密钥类型符合NSCopying协议,Java下可能的每个密钥类型都不一定能用于Objective-C。