说明
clang
- 市面上的语言越来越多, 总的趋势是
使用越来越简单
, 伴随着的却是程序员越来越不是程序员
, 因为不用在关心编译机制, 不再考虑最核心的内存管理, 不用关心系统调用机制, 不用关心程序的平台系统适配性等等, 所以衍生出的衡量出一个国家的it水平的综合指数是会不会用, 而不是怎么实现原理
, 当然国内还是有很大部分牛逼的人隐藏在幕后, 这里就不说了, 总之编程的领域要往下学
, 这是本人一惯主张的宗旨, 这个路是漫长且孤独难熬的... - 这里主要探讨的是在
OC环境下实现函数参数类型自动推导机制
, 这有点像cpp下的函数模板,是的没错, 就是实现在书写oc的过程中, 完全在编译期指定出函数参数的类型
. 这中间会简略的介绍下c++中的函数模板, 需要借助于苹果自身的编译器clang
的方言C(非标准C
)相关的语法, 但并不能完全实现出cpp函数模板
的效果, 但对程序员来说, 特别是对泛型感兴趣的同学来说, 在一门完全动态的语言中实现出这一种特性将是一种不可思义
, 也可以说是思维上的一种跳跃
cpp
泛型
- 这里就不再述说与
java, swift
中泛型的区别, 2者本质上不是一个东西 - cpp中的泛型
- 严格
- 严谨
- 复杂
- 奇异
即在书写c++代码时,
一定要写对
,编译器会强制你写对
, 但是写对的这个过程很难
, 这个不作解释了, 写过c++的都能体会到
类型转换
C语言中
- 从所周知, 在C语言中, 可以随意的对
任意值做类型强转
int a = 0;
void* p = a; // __code_1
__code_1
并没有错(clang
), 只是报了警告, 但从代码上看, 将一个数字转成void*
从逻辑上来看, 是不合理的(除非它的确是一个有效的整数的内存地址)
, 从这一点看, C语言的语法真的是不合理松散
struct T{
int a;
};
void print(void* data){
struct T* t = data; // __code_2
printf("%d", t->a);
}
int main(int args, char** argv){
struct T t = {20};
print(&t); // __code_1
}
__code_1
伴随着类型转换(struct T* 到 void*)
, 编译不会报错, 因为在标准中, 任何类型的指针都可以有效转成void*
, 反之也是没有问题的(__code_2
), 但这只能保证编译期不会报错
, 不能保证运行时没有问题
cpp
- 上面C语言中的第2个案例中, 在cpp中也是没有问题的, 但cpp有更好的处理机制
struct T{
int a;
};
template<typename T>
void print(T* data){
printf("%d", data->a); // __code
}
int main(int argc, const char * argv[]) {
// insert code here...
struct T t = {20};
const char* name = "hello";
print<T>(&t); // __code_1
print(&t); // __code_2
print(name); // __code_3
return 0;
}
__code_1 和 __code_2
不会报错, 因为事实上调用时产生的函数体是真的 struct T*指针, 函数的参数真的有 a这个成员变量
, 但__code_3
在调用时就报错了, 因为生成的函数体接收的参数是字符串, 它并没有成员变量a
可以明显的看出cpp的函数模板, 已经在编译期杜绝了运行时可能发生的错误
cpp泛型的一点小瑕疵
template<typename T>
void print(const T& data){
std::cout << data.length() << std::endl; // __code
}
int main(int argc, const char * argv[]) {
print(std::string("hello")); // __code_0
return 0;
}
整个代码没有问题, 但
唯一有一点不足的是: 在__code 处书写代码时, "data.length()"没有代码提示, 但在VS中好像有, 这完全取决于编译器的友好程度
, 解决这种问题的一个方案是给出一个模板特化
// __func_v_0
template<typename T>
void print(const T& data){
}
// 特化了一个模板 __func_v_1
template<>
void print(const std::string& data){
std::cout << data.length() << std::endl;
}
int main(int argc, const char * argv[]) {
// insert code here...
print(std::string("hello")); // 直接调用到 __func_v_1
return 0;
}
这样在
__func_v_1
中书写时, 就有正确友好的代码提示, 但这样的设计明显是多余的, 因为泛化的版本__func_v_0
和特化的版本 __func_v_1
它们实现的功能是一样的, 为了单独对字符串调用一个.length()
的方法, 显得多余了
iOS中的id类型
- 在ios中有一个类型是
id
, 它是一个万能的OC类型
- Class
NSObject*
block
- ...
- 它的特性如同
void*
,所有的OC对象都可以强转成id
, 同时编译器也赋于了id调用任何oc方法的权利(不能保证运行时的正确性)
, 但不能点语法
- 一般会在项目中,自定义一个
macro
, 来做类型的方便转换
#define GETT(_type, _val) ((_type)(_val))
// 使用
id hello = @"hello";
[id length]; // 只能以标准的方式调用
GETT(NSStrng*, hello).length; // 调用宏后, 经过强转, 可以直接调用getter, 相对来说是比较友好的, 但要保证自己转化的类型是正确的
设计对象调用getter实现类型转换
- 用协议和分类的语法来实现
TraitsType.h
, 声明项目中使用频度较高的类
#if __OBJC__
@class MyBaseLayout;
@class YYLabel;
@class NSMImageView;
@class NSMButton;
@class LBTextField;
@class YYImage;
@class LBVC;
@class LBBaseNav;
@class LBTabbarVC;
@class MOSTabbarVC;
#define $Traits_property(_) \
_(NSString* , traits_str) \
_(NSMutableString* , traits_mstr) \
_(NSAttributedString* , traits_att) \
_(NSMutableAttributedString* , traits_matt) \
_(NSNumber* , traits_num) \
_(NSDecimalNumber* , traits_dnum) \
_(NSArray* , traits_arr) \
_(NSMutableArray* , traits_marr) \
_(NSDictionary* , traits_dic) \
_(NSMutableDictionary* , traits_mdic) \
_(NSSet* , traits_set) \
_(NSMutableSet* , traits_mset) \
_(NSData* , tratis_data) \
_(NSMutableData* , traits_mdata) \
_(UIView* , traits_view) \
_(MyBaseLayout* , traits_layout) \
_(UILabel* , traits_lab) \
_(UIButton* , traits_btn) \
_(UIImageView* , traits_iv) \
_(UITextField* , traits_tf) \
_(UITextView* , traits_tv) \
_(UIScrollView* , traits_scroll) \
_(UITableView* , traits_table) \
_(YYLabel* , traits_yylab) \
_(NSMImageView* , traits_nsmiv) \
_(NSMButton* , traits_nsmbtn) \
_(LBTextField* , traits_lbtf) \
_(UIColor* , traits_color) \
_(UIImage* , traits_icon) \
_(YYImage* , traits_yyicon) \
_(UIFont* , traits_font) \
_(UIViewController* , traits_vc) \
_(UINavigationController* , traits_nav) \
_(UITabBarController* , traits_tab) \
_(LBVC* , traits_lbvc) \
_(LBBaseNav* , traits_labnav) \
_(LBTabbarVC* , traits_lbtab) \
_(MOSTabbarVC* , traits_protab) \
#define $Traits_make_property(_type, _name) \
@property(nonatomic, strong, nullable, readonly) _type _name;
@protocol Type_Traits<NSObject>
// 生成的这些属性, 是方便 快捷转变 项目中 经常用到的类型
$Traits_property($Traits_make_property)
/** 配合 tratis宏, 实现 self.tratis(_var, _type) 的类型转换 */
@property (nonatomic,strong,readonly) NSObject* (^traits)(void);
/** 所有的类对象(编译时)也可以调用这个进行转换 暂时无用*/
@property (nonatomic,strong,readonly,class) NSObject* (^traits)(void);
#define traits(_var,_type) traits(); ((_type)(_var)) // 极其精巧的设计, 从这一点可以在ios中实现出, 函数式编程中的 curing 机制, 后面有时间的话, 会写一篇
@end
@interface NSObject(Traits)<Type_Traits>
@end
#endif
TraitType.m
#import "TraitsType.h"
#define $Traits_impl_property(_type, _name) \
- (_type)_name{ return (_type)self;}
@implementation NSObject(Traits)
- (NSObject *(^)(void))traits{
return ^NSObject*(void){
return self;
};
}
+ (NSObject *(^)(void))traits{
return ^NSObject*(void){
return self;
};
}
$Traits_property($Traits_impl_property)
@end
定义这些东西的目的并不是
@"hello".traits_str.length; // 脑残调用
目的是配合后面的
block
函数式编程
简单的函数范式数组
- 函数式编程的概念不说了, 这里只给出
NSArray的map等几个函数范式方法
#define Object NSObject*
@interface NSObject(Functor)
@property (nonatomic,strong,readonly) Object _Nullable(^map)(Object _Nullable(^cbk)(Object _Nonnull self));
@end
/** 数组简单封装的 函数式基本范类方法 */
@interface NSArray(Functor)
@property (nonatomic,strong,readonly) NSArray<Object>* _Nullable(^ _Nonnull map)(Object _Nullable(^ _Nullable cbk)(Object _Nonnull item, NSInteger idx));
@property (nonatomic,strong,readonly) Object _Nullable(^reduce)(Object _Nonnull _init_obj, Object _Nullable(^cbk)(Object pre_val, Object _Nonnull item, NSInteger idx));
@property (nonatomic,strong,readonly) NSArray<Object>* _Nullable(^filter)(bool (^cbk)(Object _Nonnull item, NSInteger idx));
@end
上面这是声明, 全部用
Object-->NSObject*
当作参数, 原因就是配合上面声明TraitsType
, 使用对象的点语法,来实现类型转换
, 下面是.m相关的实现
@implementation NSObject(Functor)
- (id _Nullable (^)(id _Nullable(^)(id _Nonnull)))map{
return ^id _Nullable (id _Nullable(^cbk)(id _Nonnull obj)){
if(cbk == nil){
return nil;
}
return cbk(self);
};
}
@end
@implementation NSArray(Functor)
- (NSArray<id> * _Nullable (^)(id _Nullable (^)(id _Nonnull, NSInteger)))map{
return ^NSArray<id>* _Nullable (id _Nullable(^cbk)(id _Nonnull, NSInteger)){
if(cbk == nil)
return nil;
if(self.count == 0)
return nil;
NSMutableArray* result = @[].mutableCopy;
for(NSInteger i = -1, j = self.count; ++i < j;){
__auto_type item = self[i];
__auto_type new_item = cbk(item, i);
if(new_item == nil)
continue;
[result addObject:new_item];
}
return result;
};
}
- (id _Nonnull (^)(id _Nonnull, id _Nullable (^)(id pre_val, id _Nonnull, NSInteger)))reduce{
return ^id _Nullable (id _Nonnull _init_obj ,id _Nullable(^cbk)(id pre_val, id _Nonnull, NSInteger)){
if(_init_obj == nil)
return nil;
if(cbk == nil)
return _init_obj;
if(self.count == 0)
return _init_obj;
for(NSInteger i = -1, j = self.count; ++i < j;){
__auto_type item = self[i];
__auto_type new_item = cbk(_init_obj,item, i);
_init_obj = new_item;
}
return _init_obj;
};
}
- (NSArray<id> * _Nullable (^)(bool (^)(id _Nonnull, NSInteger)))filter{
return ^NSArray<id>* _Nullable (bool(^cbk)(id _Nonnull, NSInteger)){
if(cbk == nil)
return nil;
if(self.count == 0)
return nil;
NSMutableArray* result = @[].mutableCopy;
for(NSInteger i = -1, j = self.count; ++i < j;){
__auto_type item = self[i];
__auto_type judge = cbk(item, i);
judge ? [result addObject:item] : 0;
}
return result;
};
}
@end
使用
@[@"hello", @"world"].map(^NSObject * _Nullable(NSObject * _Nonnull item, NSInteger idx) {
// 用 traits 转换 item
item.traits(item,NSString*).length;
// 也可以用 简便的方法转换
item.traits_str.length;
return item.traits_str.lowwerstring;
});
因为
Array的map在声明时, 所有的入参都是NSObject*
, 所以在书写时, 利用编译器的补全功能, 用户接收到的类型再通过TraitsType
的转换, 可以说是投机取巧又完美
终级版本
- 上面最后的成型的调用
还有2点美中不足:
, 即在代码体中:- 不能
直接对 item
参数调用NSString*
相关的方法 返回值没有安全类型的警告提示
- 不能
@[@"hello", @"world"].map(^NSObject * _Nullable(NSObject * _Nonnull item, NSInteger idx) {
// 不足1, 不能直接调用 item.length, 因为item的类型是 NSObject*
item.traits(item,NSString*).length;
// 也可以用 简便的方法转换
item.traits_str.length;
// 这里返回的是字符串, 但接收的类型是NSObject*
// 所以只是要 OC对象, 编译器就不会有警告
return item.traits_str.lowwerstring;
});
针对上面的2点不足, 最终修改为如下版本
@interface NSObject(Functor)
@property(strong,nonatomic,readonly) id _Nullable (^map)(id _Nullable(^)(id, ...));
@end
@implementation NSObject(Functor)
- (id _Nullable (^)(id _Nullable (^)(id, ...)))map{
return ^id(id(^cbk)(id, ...)){
if(cbk == 0)
return nil;
return cbk(self);
};
}
@end
@interface NSArray(Functor)
@property(strong,nonatomic,readonly) id _Nullable (^map)(id _Nullable(^)(id, NSInteger));
@end
@implementation NSArray(Functor)
- (id _Nullable (^)(id _Nullable (^)(id,NSInteger)))map{
return ^id(id _Nullable(^cbk)(id item, NSInteger idx)){
NSMutableArray* arr = @[].mutableCopy;
for(NSInteger i = -1, j = self.count; ++i < j;){
id result = cbk(self[i],i);
if(result)
[arr addObject:result];
}
return arr;
};
}
@end
// 极其精巧的设计, 真正意义上的 类型推导, 类型绑定(都是在编译期确定)
// 第1个是 block的返回类型, 对应声明时的 id类型(id接收oc对象不会有问题)
// 后面的 args是参数个数, 在调用时由 用户传入, 由编译器绑定类型
// ps: 当是arry调用时, 会由编译器匹配到 app的声明, 并正确调用到array的map实现
#define map(_ret_type,args...) map(^ _ret_type (args)
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 第1个 NSString* 是返回类型, 实际由id接收, 没有问题
// 后面的参数 item对应array的map声明中的 id, 也没有问题(事实上数组中的元素也就是NSString*)
NSString* result = @[@"hello",@"world"].map(NSString*, NSString* item , NSInteger idx){
// 直接调用item的length方法, 不用再做什么转换
__unused __auto_type length = item.length;
NSLog(@"%@ %zd", item, idx);
// 若这里返回的不是 NSString, 是其他oc对象, 编译器会警告, 这也算是一种友好的提示了
return item.uppercaseString;
});
NSLog(@"%@",result);
// @[@"hello"].map(^NSArray<NSString*>* (id fir, ...){
// return ((void *)0);
// });
}
return 0;
}