15

Objective-C的元组实现(JDTuple)

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzUyMDAxMjQ3Ng%3D%3D&%3Bmid=2247494916&%3Bidx=1&%3Bsn=07bb0ccf2d6ab98a10a67121e2f7bcaf
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

引言

有没有觉得OC的数组和字典使用起来特别繁琐?我们在使用字典或数组时,总是需要判断数组越界,判断数据是否为空,判断数据类型。

有没有觉得定义多参数函数的时候特别杂乱无章?

有没有觉得Swift的元组使用起来特别顺心?

这些问题其实都源于OC是一门比较古老的语言,早期设计时缺少一个足够灵活,足够好用的数据封装工具。

试想一下,假如OC也有一个元组类,这个类可以封装任意数据,包装时也无须关注数据的类型。在解构数据时可以一次性的将数据展开,无需关注越界的问题。那势必能够提高不少开发的效率,让OC这门古老的语言也swift一下。

什么是元组?

元组(tuple)是一种数据结构,它本来是一种关系型数据库的概念,用来描述一张表中的一条数据。例如:

VbUBVfV.png!mobile

那么一行数据就是一个元组,即: tuple1 = (阿强,18,战士);tuple2 = (阿珍,28,法师)。这个概念后来也被使用在代码中,成为一种数据封装的工具。元组可以包含任意个数和任意类型的数据,具体元组中包含什么,取决于使用时的定义。同时,元组内的元素既可以有顺序也可以有键值,但一旦定义后不可增删元素,类似于不可变的数组+字典。那么我们就尝试根据元组的定义,为OC也增加一个元组的实现。

JDTuple元组的基本使用

首先我们先来看看成品的效果,以及功能。

0 1

元组构造

JDTuple通过 jd_tuple(...) 方法来构造一个元组,如下代码所示我们构造了一个包含三个元素的元组:

  NSString *name = @"阿珍";
NSInteger age = 28;
NSArray *arr = @[@"a", @"b", @"c"];
JDTuple *someTuple = jd_tuple(name, age, arr);

这一个方法其实包含了三个步骤:

  • 给元素定义键值:name、age、arr。

  • 给元素定义下标:0,1,2。

  • 给元素赋值:阿珍,28,["a", "b", "c"]。

当然也可以定义匿名元素的元组,直接将值传给构造方法,将默认为匿名的元素。如:

 JDTuple *someTuple = jd_tuple(@"阿强", 18, (CGRectMake(100, 200, 50.5, 7)), [NSObject new]);

0 2

元组解构

1.顺序解构

JDTuple通过 jd_tuple(tuple)^(...){...} 方法来进行顺序元素解构,顺序解构是根据构造元组时的元素顺序来取值,根据构造时元素的顺序和类型,定义相应的变量就能获取到元素的值,如:

  JDTuple *someTuple = jd_tuple(@"阿强", 18, (CGSizeMake(5, 18)), [NSObject new]);

jd_unpack(someTuple)^(NSString *name, int age, CGSize size, NSObject *objc) {
NSLog(@"name: %@", name);
NSLog(@"age: %d", age);
NSLog(@"width:%f, height:%f", size.width, size.height);
NSLog(@"objc: %@", objc);
//NSLog output
//name: 阿强
//age: 18
//width:5.000000, height:18.000000
//objc: <NSObject: 0x6000014279d0>
};

上述代码解构了someTuple元组,将元素在一个作用域内赋值给了name、age、size、objc变量。

当我们只需要部分元素时,我们只需要在相应的位置上定义变量就可以了,需要忽略的元素可以用 arg_ph 类型来占位,如:

JDTuple *someTuple = jd_tuple(@"阿强", 18, (CGSizeMake(5, 18)), [NSObject new]);

jd_unpack(someTuple)^(NSString *name, int age) {
NSLog(@"name: %@", name);
NSLog(@"age: %d", age);
//NSLog output
//name: 阿强
//age: 18
};


jd_unpack(someTuple)^(NSString *name, arg_ph one, arg_ph two, NSObject *objc) {
NSLog(@"name: %@", name);
NSLog(@"objc: %@", objc);
//NSLog output
//name: 阿强
//objc: <NSObject: 0x6000014279d0>
};

当相应位置的元素类型不匹配时,元素将不会被赋值,并在控制台输出提示, 如:

JDTuple *someTuple = jd_tuple(@"阿强", 18, (CGSizeMake(5, 18)), [NSObject new]);

jd_unpack(someTuple)^(NSInteger name, id age, CGSize size, NSObject *objc) {
NSLog(@"name: %ld", name);
NSLog(@"age: %@", age);
NSLog(@"width:%f, height:%f", size.size.width, size.size.height);
NSLog(@"objc: %@", objc);
//NSLog output
//JDTuple unpack params _ 0 _ type is not match. @ != q
//JDTuple unpack params _ 1 _ type is not match. i != @
//name: 0
//age: (null)
//width:5.000000, height:18.000000
//objc: <NSObject: 0x6000002439e0>
};

2.键值解构

如果元组在构造时包含键值,则可以使用 jd_unpackWithkey(...) 方法,通过对应的键值解构元组,而无需关心元素顺序或个数,如:

- (void)testFunctionOne {


NSString *name = @"阿珍";
NSInteger age = 28;
NSArray *arr = @[@"a", @"b", @"c"];
JDTuple *someTuple = jd_tuple(name, age, arr);

[self testFunctionTwo:someTuple];
}


- (void)testFunctionTwo:(JDTuple *)tuple {
jd_unpackWithkey(NSInteger age, NSArray *arr, NSString *name) = tuple;


NSLog(@"name: %@", name);
NSLog(@"age: %ld", age);
NSLog(@"array: %@", arr);
//NSLog output
//name: 阿珍
//age: 28
//array: (a,b,c)
}

上述代码在 testFunctionTwo 方法中解构了入参tuple, 在函数作用域内将元素直接赋值给了同名的参数name、age、arr。

如果尝试解构元组中不存在的键值,或者键值类型不匹配,则不会给予赋值,并在控制台输出提示,如:

- (void)testFunctionOne {


NSString *name = @"阿珍";
NSInteger age = 28;
NSArray *arr = @[@"a", @"b", @"c"];


JDTuple *someTuple = jd_tuple(name, age, arr);
[self testFunctionTwo:someTuple];
}


- (void)testFunctionTwo:(JDTuple *)tuple {
jd_unpackWithkey(NSString *name, id test1, CGFloat age) = tuple;


NSLog(@"name: %@", name);
NSLog(@"age: %f", age);
NSLog(@"test1: %@", test1);

//NSLog output
//JDTuple unpack params _ ” id test1 ” _ non-existent
//JDTuple unpack params _ “ CGFloat age ” _ type is not match. q != d
//name: 阿珍
//age: 0.000000
//test1: (null)
}

0 3

单个元素获取

可以从元组中根据下标或者键值来获取单个元素,类似数组或者字典的操作,得到的元素是被 NSValue 封装起来的,使用时要根据实际的类型进行值的获取, 如果下标或键值不正确,则会返回nil,如:

NSString *name = @"阿珍";
NSInteger age = 28;
NSArray *arr = @[@"a", @"b", @"c"];
JDTuple *someTuple = jd_tuple(name, age, arr);
NSString *nameWithIndex = [someTuple[0] nonretainedObjectValue]; //阿珍
NSInteger ageWithKey = [someTuple[@"age"] integerValue]; //28
NSValue *nonexistentWithIndex = someTuple[99999]; //(null)
NSValue *nonexistentWithKey = someTuple[@"existent"]; //(null)

0 4

元组定义

为了让开发者清楚的知道元组中有什么元素,我们可以使用 JD_TUPLE(...) 来定义一个元组,避免遇到一个元组时不清楚里面会包含什么元素的窘境,在使用顺序解构时,我们可以使用严格模式 jd_unpack_strict(...) 来完全匹配元组的定义。

//定义元组内包含:NSString *name, NSInteger age
JD_TUPLE(NSString *name, NSInteger age) tuple = jd_tuple(@"阿强", 18);
- (void)testFunction:( JD_TUPLE(NSString *name, NSInteger age) )tuple {
jd_unpack_strict(tuple)^(NSString *name, NSUInteger age) { //解构正确可以通过编译
...
};
jd_unpack_strict(tuple)^(UIView *name) { //解构错误编译不通过
...
};


jd_unpack(tuple)^(CGFloat f) { //解构正确可以通过编译,非严格模式
...
};
}

JDTuple元组实现原理

现在我们知道了JDTuple是什么,且要怎么使用JDTuple。接下来我们就一步步分析一下JDTuple是如何实现的。

01

数据封装

1.一次性构造

首先我们要解决数据如何方便的放到元组里,根据 jd_tuple(...) 方法的定义,我们可以将任意数据作为入参,并将入参保存在元组中。首先,任意入参这个需求,我们很自然的可以想到使用不定参数函数,它使用一块连续的内存来保存入参信息,并使用 va_start 来找到内存的首地址来获取入参。如:

void test(id one, ...) {
va_list list;
va_start(list, one);
id arg = nil;
do {
arg = va_arg(list, id);
//.../
} while (arg);
va_end(list);
};
test([NSObject new],[NSObject new],[NSObject new],[NSObject new], nil);

如上述例子,不定参数函数的标准用法中会要求使用一个 nil 来作为参数的结尾,原因就是 va_list 并不知道入参的个数和类型,需要被调用者预先跟调用者约定如何传参,所以规定一个 nil 值来规范双方,所以 nil 结尾只是一个约定,并不是一定要遵守的。实际上 NSLog(...) 就没有使用 nil 结尾,而是采用了特殊字符来让被调用者知道调用者的入参个数和类型。由于构造元组时,存在入参是nil的可能,所以我们也不能使用nil来作为结尾。我们需要使用另一种约定,来实现参数的解析。所以我们采用OC的类型编码来作为入参的类型解析,类型和值需成对传入,根据类型获取后面的值,最后使用一个全局字符串常量的指针地址作为结束符号。由此设计了方法- (JDTuple *(^)(const char *OCType, ...))addArg

使用如下方法:

//use
JDTuple *tuple = [JDTuple new];
id objc = [NSObject new];
NSInteger iNumber = 100;
CGFloat fNumber = 50.55;
CGReact frame = CGReactMake(10,20,300,400);
tuple.addArg("@", objc,
"q", iNumber,
"d", fNumber,
"{CGRect={CGPoint=dd}{CGSize=dd}}", frame,
jd_tuple_end);
//implementation
static const char *jd_tuple_end = "JD_TUPLE_END"; //结束标识符
@interface JDTuple : NSObject
- (JDTuple *(^)(const char *OCType, ...))addArg;
@end
@implementation JDTuple
- (JDTuple *(^)(const char *OCType, ...))addArg {
return ^(const char *OCType, ...) {
NSInteger iIndex = 1;
va_list valist;
va_start(valist, OCType);
while (iIndex != -1) {
if (iIndex % 2 == 0) { //双数位置上的入参为类型编码
OCType = va_arg(valist, const char*);
if (OCType == NULL || &OCType[0] == &jd_tuple_end[0]) {//识别到结束符,停止循环
iIndex = -1;
}else {
iIndex ++;
}
continue;
}else { //单数位置上的入参为值
iIndex ++;
}
//根据类型编码从valist中取出相应的值,保存在NSValue中
NSValue *newValue = nil;
if (strcmp(OCType, "@") == 0) {
__unsafe_unretained id<NSObject> p = va_arg(valist, id);
newValue = [[NSValue alloc]initWithBytes:&p objCType:OCType];
}else if (strcmp(OCType, "q") == 0) {
NSInteger p = va_arg(valist, NSInteger);
newValue = [[NSValue alloc]initWithBytes:&p objCType:OCType];
}else if ...
}
...
}
}
@end

关于OC类型编码的知识可以去官网查一下:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

2.类型自动识别

有了 addArg 方法,我们就已经实现了对元素的封装,但是这个方法使用起来还非常不便捷,我们既要输入值还要输入值的类型编码,依然很影响开发体验,这时我们需要借助 @encode()typeof() 两个关键字就可以实现自动识别变量的类型编码。 typeof(): 可以转译出变量的类型。 @code(): 可以根据类型转译出类型编码。如:

id objc = [NSObject new];
NSInteger i = 66;


@encode(typeof(objc)) //@
@encode(typeof(i)) //q

这样我们就不用自己手动去输入类型的编码了,可以由变量自动转换:

JDTuple *tuple = [JDTuple new];
id objc = [NSObject new];
NSInteger iNumber = 100;
CGFloat fNumber = 50.55;
CGReact frame = CGReactMake(10,20,300,400);
tuple.addArg(@encode(typeof(objc)), objc,
@encode(typeof(iNumber)), iNumber,
@encode(typeof(fNumber)), fNumber,
@encode(typeof(frame)), frame,
jd_tuple_end);

3.宏定义封装

最后我们再利用一个宏定义,将入参操作简化为一个参数,利用宏的预编译能力来自动填写类型编码。宏定义实现较为冗长,这里只展示解析2个参数的部分代码,如:

//use
JDTuple *tuple = [JDTuple new];
id objc = [NSObject new];
NSInteger iNumber = 100;
tuple.addArg(___tuple_add(objc, iNumber));
//implementation
//填写入参
static const char *jd_tuple_end = "JD_TUPLE_END";
#define jd_tuple_add_end jd_tuple_end
#define jd_tuple_add_1(param) @encode(typeof(param)), param, jd_tuple_add_end
#define jd_tuple_add_2(param, ...) @encode(typeof(param)), param,jd_tuple_add_1(__VA_ARGS__)
//计算参数个数
#define FL_INTERNAL_ARG_COUNT_PRIVATE(\
_0, _1, _2, N, ...) N
#define EXTAND_ARGS(args) args
#define jd_tuple_arg_count(...) EXTAND_ARGS(FL_INTERNAL_ARG_COUNT_PRIVATE(0, __VA_ARGS__, jd_tuple_add_2, jd_tuple_add_1, 0))
//添加变量
#define ___tuple_add(...) jd_tuple_arg_count(__VA_ARGS__)(__VA_ARGS__)

解决入参个数和类型的问题,再利用宏的特性,给入参元素一个键值。首先定义一个键值输入方法,入参为一个键值字符串集合,以逗号作为分隔符:

- (void)tupleInputKeys:(const char *)keys;
[tuple tupleInputKeys:"name, age, height"];

再利用宏定义的#号(使用#可以把宏参数变为一个字符串),将入参转化为字符串传入 tupleInputKeys 方法,生成入参的键值。最终得到构造宏定义方法 jd_tuple(...) , 如:

#define jd_tuple(...) ({JDTuple *tuple = [JDTuple new]; [tuple tupleInputKeys:#__VA_ARGS__]; tuple.addArg(___tuple_add(__VA_ARGS__)); tuple;})

以上就完成了入参数据封装的工作。

0 2

数据存储

解决了入参的问题,将入参保存下来的工作就容易多了。这里我们定义两个属性,分别保存元素和键值:

//数组用于顺序保存元素
@property (nonatomic, strong) NSMutableArray<__kindof NSValue *> *arrParams;
//字典用于映射键值和数组的下标
@property (nonatomic, strong) NSMutableDictionary<NSString *, NSNumber *> *dicKeys;

NSArray 只支持存储对象,顾所有元素都会使用 NSValue 来进行封装,保存在数组中,使用 NSValue 的好处是,他不仅能够存储数据本身,同时还会存储数据的类型编码,类型编码对于解构数据时的校验起到至关重要的作用,同时也更适用于ARC的编译环境。

0 3

数据解构

解决了存数据的问题,接下来我们就要考虑如何把元素从元组中取出来,也就是解构元组。

1.单个数据解构

JDTuple是用数组和字典来存储数据的,那么它就天然具备数组和字典的特性,可以使用下标和键值取值。想要具有中括号取值的能力其实也很简单,只要实现以下两个方法,就能让编译器识别。

- (objectType)objectAtIndexedSubscript:(NSUInteger)idx; 
- (objectType)objectForKeyedSubscript:(NSString *)key;

实现 objectForKeyedSubscript 方法,可以让编译器认为你具有键值取值的能力。

NSValue *value1 = someTuple[1]; 
NSValue *value2 = someTuple[@"name"];

2.批量键值解构

有了单个数据的获取方式,我们当然希望有批量的操作来提高我们的生产效率,这也是元组的精髓所在,下面我们来看看如何实现通过键值批量结构元组。通过键值从元组中取出的数据是封装在 NSValue 中的,我们使用时还需要从 NSValue 中把真正的元素取出,那我们能不能通过一个方法来获取 NSValue 中的值,并转化为目标值的类型呢?这时,我们需要借助C语言函数重载来实现。如:

__attribute__((overloadable)) int add(int num){
NSLog(@"Add Int %i",num);
return num;
}
__attribute__((overloadable)) NSString add(NSString * num){
NSLog(@"Add NSString %@",num);
return num;
}
__attribute__((overloadable)) NSNumber add(NSNumber * num){
NSLog(@"Add NSNumber %@",num);
return num;
}


int i = add(123); // Add Int 123
NSString *s = add(@"asd"); // Add NSString asd
NSNumber *n = add(@(444)); // Add NSNumber 444

如上例子,通过 __attribute__((overloadable)) 修饰的 add 函数可以使用同一个函数名来接收不同类型的入参,且定义不同的返回值。我们可以借助一组重载的函数来直接获取 NSValue 中的值。如:

// implementation
__attribute__((overloadable)) id tmp_tuple_unpack_c(id tuple, const char *key, id v){...};
__attribute__((overloadable)) NSInteger tmp_tuple_unpack_c(id tuple, const char *key, NSInteger v){...};
__attribute__((overloadable)) CGFloat tmp_tuple_unpack_c(id tuple, const char *key, CGFloat v){...};
//use
id valueObjc = tmp_tuple_unpack_c(tuple, "name", valueObjc);
NSInteger valueInt = tmp_tuple_unpack_c(tuple, "age", valueInt);
CGFloat valueFloat = tmp_tuple_unpack_c(tuple, "height", valueFloat);

接下来我们只需要借助一个宏,来将 tmp_tuple_unpack_c 方法的调用压缩成一个宏调用就能实现键值的批量结构了,为了让宏能够正确调用不同返回值类型的 tmp_tuple_unpack_c 函数,我需要将 tmp_tuple_unpack_c 函数的最后一个入参改造一下,如:

// implementation
__attribute__((overloadable)) id tmp_tuple_unpack_c(id tuple, const char *key, void(^v)(id)){...};
__attribute__((overloadable)) NSInteger tmp_tuple_unpack_c(id tuple, const char *key,void(^v)(NSInteger)){...};
__attribute__((overloadable)) CGFloat tmp_tuple_unpack_c(id tuple, const char *key, void(^v)(CGFloat)){...};


#define help_tuple_unpack_set(tuple, value) value = tmp_tuple_unpack_c(tuple, #value ,^(value){});
//use
help_tuple_unpack_set(tuple, id valueObjc);
// id valueObjc = tmp_tuple_unpack_c(tuple, "id valueObjc", ^(id valueObjc){});
help_tuple_unpack_set(tuple, NSInteger valueInt);
// NSInteger valueInt = tmp_tuple_unpack_c(tuple, "NSInteger valueInt", ^(NSInteger valueInt){});
help_tuple_unpack_set(tuple, CGFloat valueFloat);
// CGFloat valueFloat = tmp_tuple_unpack_c(tuple, "CGFloat valueFloat", ^(CGFloat valueFloat){});

最后再加工一下,用宏封装成赋值形式的等号写法,就有了 unpackWithkey(...) 方法,其中参考了ReactCocoa中RACTuple的解构写法,这个宏的封装描述起来比较复杂,主要原理是利用宏遍历宏的参数,再用宏参数调用 help_tuple_unpack_set 宏,感兴趣的话可以去看看源码。("Don’t try to understand it. Feel it." ——Tenet) 

#define help_tuple_unpack_set1(tuple, value) value = tmp_tuple_unpack_c(tuple, #value ,^(value){});
#define help_tuple_unpack_set2(tuple, value, ...) value = tmp_tuple_unpack_c(tuple, #value ,^(value){}); help_tuple_unpack_set1(tuple, __VA_ARGS__);
...


#define jd_tuple_arg_count_key(...) EXTAND_ARGS(FL_INTERNAL_ARG_COUNT_PRIVATE(0, __VA_ARGS__, ..., help_tuple_unpack_set2, help_tuple_unpack_set1, 0))


#define __unpackWithkey(name, ...) \
id tmp__tupleUnpack_##name = nil;\
JDTTupleUnpack_after_##name:;\
jd_tuple_arg_count_key(__VA_ARGS__)(tmp__tupleUnpack_##name, __VA_ARGS__)\
if (tmp__tupleUnpack_##name != nil)tmp__tupleUnpack_##name = @"";\
while (![ tmp__tupleUnpack_##name isKindOfClass:[NSString class]])\
if (tmp__tupleUnpack_##name) {if ([ tmp__tupleUnpack_##name isKindOfClass:[JDTuple class]]) {\
goto JDTTupleUnpack_after_##name;}else {break;}}else if ( tmp__tupleUnpack_##name == nil) tmp__tupleUnpack_##name\

3.批量顺序解构

顺序解构,我们采用Block回调的方式来实现,在 JDTuple 中定义一个 unpack 属性,属性类型为 id ,利用这个属性的 set 方法来接受一个任意类型的block变量,在 set 方法中利用 NSINvocation 动态调用这个block,将元组中的元素作为block的入参顺序传入,就实现了元组的解构。其中利用Block的signature和NSValue的objCType做比较,可以很好的比对类型是否一致,避免崩溃。下面是部分关键代码:

#define jd_unpack(tuple) tuple.unpack =


@interface JDTuple<__contravariant objectType, deconstructType> : NSObject
@property (nonatomic, copy) id unpack;
@property (nonatomic, copy) deconstructType unpackStrict;


...


- (void)setUnpack:(id)block {
...
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:methodSignature];
[blockInvocation retainArguments];
for (NSInteger idx = 0; idx < methodSignature.numberOfArguments - 1; idx++) {
NSValue *param = self.arrParams[idx];
__unsafe_unretained id p;
[param getValue:&p];
[blockInvocation setArgument:&p atIndex:idx + 1];
...
}
[blockInvocation setTarget:block];
[blockInvocation invoke];
}
// use
jd_unpack(tuple)^(NSString *name, NSInteger age, CGFloat height) {
....
};

MNjEBfi.png!mobile

分享一些坑点

  • OC中的 BOOL 类型是一个 char 型的数据, _Bool 才是0或1。

  • 在解构数据类型时,发现CGFloat类型在不同的CPU架构下使用的类型是不一样的,在ARM64架构下的 CGFloat 实际是 double 型的,而在ARM32架构下才是 float 型的。同理, NSInteger 也会在不同架构下表现为 longint 。如果没注意这些细节,可能在平时的开发中会出现精度丢失的问题。

JDTuple元组实际应用

01

数据的混合封装

元组的最直接使用好处就是方便的存取,无须在意元组中元素的个数和类型,很适合多参数传递的场景,实际使用中可以提升不少开发的效率。

- (void)test {
NSObject *name = [NSObject new];
NSInteger age = 28;
NSArray *arr = @[@"a", @"b", @"c"];
CGRect frame = CGRectMake(1, 2, 3, 4);
CGFloat f = 55.334;
JDTuple *someTuple = jd_tuple(name, age, arr, frame, f);

[self testFunction:someTuple];
}


- (void)testFunction:(JD_TUPLE(NSString *, NSInteger, NSArray *, CGRect, CGFloat))tuple {
jd_unpack_strict(tuple)^(NSString *a, NSInteger b, NSArray *c, CGRect d, CGFloat e) {

};
}

02

不定参数函数、不定返回值函数

我们可以利用Tuple的灵活性来做一些很有趣的事情。

1.返回多个返回值

- (JD_TUPLE(NSString*test, NSInteger i, CGFloat f))test{
NSString *test = @"test";
NSInteger i = 667;
CGFloat f = 334.5;
return jd_tuple(test, i, f);
}


- (void)viewDidLoad {
[super viewDidLoad];
jd_unpackWithkey(NSString *test, NSInteger i, CGFloat f) = [self test];
}

2.一个更万能的performSelector方法

- (JDTuple *)performSelector_tuple:(JDTuple *)tuple {
jd_unpackWithkey(id __self, NSString *selector) = tuple;
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];


...
[invocation invoke];
...


id returnValue;
[invocation getReturnValue:&returnValue];
return jd_tuple(returnValue);
}


JDTuple *returnTuple = [self performSelector_tuple:jd_tuple(self, @"someFunc", @"args1", 234, [NSObject new])];
JDTuple *returnTuple2 = [self performSelector_tuple:jd_tuple(self, @"someFunc2", 1,2,3,4,5,6,7,8,9)];

03

json数据解析

可以利用JDTuple的能力,实现一个json字符串转元组的方法,然后直接使用tuple来获取数据,省去了一遍又一遍写model的繁琐工作。

JDTuple *json_to_tuple(NSString *jsonString) {
...
return tuple;
}


JDTuple *tuple = json_to_tuple(@"{ \
a : \"abc\"\
b : [\"hello\", \"world\", \"!!\"]\
c : {\
cc : \"ccc\"\
}\
}");


jd_unpackWithkey(NSString *a, NSArray *b, JDTuple *c) = tuple;
jd_unpackWithkeyMore(2, NSString *cc) = c;

04

Swift元组桥接

利用JDTuple对Swift使用了元组的方法进行简单封装,使得OC也可以调用使用了元组的Swift 方法。更多的使用场景就留给大家去发散了。

后 记

JDTuple还处于持续开发阶段,有很多细节的工作还没有完善,例如类型的解析,目前还不支持自定义结构体的存取。源码已开源在gitee上供大家使用, 有兴趣的朋友可以到上面提提意见。

Gitee: https://gitee.com/jd-platform-opensource/jdtuple

7BVFF3j.gif!mobile

参考文献

7BVFF3j.gif!mobile

  • Type Encodings (https://nshipster.cn/type-encodings/)

  • ReactCocoa (https://reactivecocoa.io/)

  • iOS中的attribute和宏 (http://fighting300.com/2016/06/12/iOS-attribute/)

  • 计算可变参数宏 VA_ARGS 的参数个数(https://blog.csdn.net/10km/article/details/80760533)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK