记录一些自己平常使用的另类编程方法,给繁复枯燥的代码平添一些乐趣。主要是从一些实际使用场景出发,探讨如何『偷懒』,让时间不那么浪费在重复修改代码上,即使修改也要让修改代码没那么麻烦。
目的:
1.关于ActionSheet点击响应不同事件的一个使用场景
比较常用的一个场景是一个界面中,我们需要弹出来一个ActionSheet,里面有n多个选项可供用户来选择。从产品设计的角度来说,对应的n种操作就是一个可怕的定时炸弹,不确定产品是不是会对这n种选项进行一个任性的排列;而从写代码的角度来说,对于这n种操作,写一个if-else或者switch-case是一个正确的选择,但是写下来你就会发现:冗余。再加上有可能会有排序的改动,那更加是毁灭性的代码。
对于这种类型的地方,没有必要花费太多精力来书写以及维护代码,需要的是简洁且易维护。
可以考虑使用数组将分支的操作进行存储,然后在点击选择时就可以直观的进行其对应的操作。可惜在 中,选择子@selector
既不是基础数据类型也不是对象,是不能够使用数组存储的,那么就只能使用C数组来存储了,写出来大概是这个样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| -(void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
SEL selectors[] = { @selector(insertRow), @selector(insertSection), @selector(deleteSection) };
if (buttonIndex < sizeof(selectors) / sizeof(SEL)) {
void(*imp)(id, SEL) = (typeof(imp))[self methodForSelector:selectors[buttonIndex]];
imp(self, selectors[buttonIndex]); } }
|
这样还是会带来两个问题,第一个是UIActionSheet的实现和方法是分开写的,第二个是即使这样还是需要维护两个数组,没有达到易维护的目的,但是好赖也比以前简单了许多。
第一个问题可以使用最新的UIAlertController
的block来解决,但是这无疑又是一个问题,要写好多样板代码,这时候就需要对UIAlertController进行一下封装,使其使用起来更加简洁。
2.对系统的UIAlertController进行封装
在iOS8.0之后系统就不推荐使用UIAlertView以及UIActionSheet,而使用了一个UIAlertController来替换他们两个。这样的做法对于混乱的视图展示层级来说确实是一个福音,并且可以使用block的方式来在一处处理对应的事件。但是,对于新的UIAlertController来说,要写好多的样板代码,一般来说,排版也不是很好看。
基于减少劳动力以及优化使用方式的目的,对系统的UIAlertController进行一层简单的封装。思路是使用协议的方式来约定是要显示的Alert以及ActionSheet类:
1 2 3 4 5 6 7 8
| @protocol HLLAlertActionSheetProtocol <NSObject> @optional #pragma mark - config
- (id<HLLAlertActionSheetProtocol>) title:(NSString *)titile; - (id<HLLAlertActionSheetProtocol>) message:(NSString *)message; ... @end
|
使用一个HLLAlertActionSheet
类来生成对应的alert或者actionSheet类:
1 2 3 4 5 6 7 8 9 10 11
| @interface HLLAlertActionSheet : NSObject + (id<HLLAlertActionSheetProtocol>) alert; + (id<HLLAlertActionSheetProtocol>) actionSheet; @end
@implementation HLLAlertActionSheet #pragma mark - config + (id<HLLAlertActionSheetProtocol>) alert{ return [HLLAlert alert]; } @end
|
而对于alert以及actionSheet类来说,内部使用一个私有类来存储通过实现协议传入的设置,然后在showIn:
方法中对UIalertContrller进行初始化、设置以及显示。一个alert类的部分内容如下:
1 2 3 4 5 6
| @interface HLLAlert : NSObject<HLLAlertActionSheetProtocol>
@property (nonatomic ,strong) HLLAlertActionSheetModel * aModel; #pragma mark - config + (HLLAlert *) alert; @end
|
其中实现协议方法时候把当前实例对象也返回出去:
1 2 3 4 5
| - (HLLAlert *) title:(NSString *)titile{ self.aModel.title = titile; return self; }
|
这样在使用的时候可以这样:
1 2 3 4 5 6 7 8 9
| [[[[[[[HLLAlertActionSheet alert] title:@"举报这个人"] message:@"确定举报当前主播直播的内容违规"] buttons:@[@"取消",@"举报"]] style:UIAlertActionStyleCancel index:0] showIn:self] fetchClick:^(NSInteger index) { if (index = 1) {// 举报 } }]
|
对于传入的用于可选操作的内容,可以使用数组也可以使用类似于UIAlertView初始化的时候不定个数的字符串类型:
1 2 3 4
| - (id<HLLAlertActionSheetProtocol>) buttons:(NSArray *)buttons; /** Explain some as above .这个采用多字符串的方式传递buttons,以应对button-fetch不对应的问题*/ - (id<HLLAlertActionSheetProtocol>) buttonTitles:(NSString *)buttonTitles, ... NS_REQUIRES_NIL_TERMINATION
|
目前,没有做可以添加UITextField的封装,后续有时间再做。
3.关于一个变量的实例化方式
其实这是小括号内联符合表达式写法–A compound statement enclosed in parentheses.
想象一下,在对一个类的对象变量进行实例化的时候,往往需要写大量的关于这个对象变量的设置属性代码。
这种做法的最大的意义是将代码整理分块,将同一个逻辑的代码抱在一起;同时对于一个不需要复用的小段逻辑,免去了重量级函数的调用:
1 2 3 4 5
| self.loginButton = ({ UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; ... button; })
|
4.使用位域进行代理方法实现与否的判断
在使用代理的时候需要判断代理代理是否实现了协议方法,一般的方法就是使用if-else进行书写,但是这样的切砖代码比较枯燥,也没有一点儿技术性可言,所以按照EOC中的方法可以使用以下方法进行书写,
使用结构体进行位的标记位:
1 2 3 4 5 6 7
| @interface KKGestureLockView (){ struct { unsigned int didBeginWithPasscode :1; unsigned int didEndWithPasscode : 1; unsigned int didCanceled : 1; } _delegateFlags; }
|
在设置代理的时候进行判断代理是否实现了相关协议方法,然后设置标记位:
1 2 3 4 5 6 7
| - (void)setDelegate:(id<KKGestureLockViewDelegate>)delegate{
_delegate = delegate;
_delegateFlags.didBeginWithPasscode = [delegate respondsToSelector:@selector(gestureLockView:didBeginWithPasscode:)]; _delegateFlags.didEndWithPasscode = 。。。 }
|
使用标记位判断是否实现协议方法:
1 2 3 4 5
| if (_delegateFlags.didBeginWithPasscode) {
[self.delegate gestureLockView:self didBeginWithPasscode:[NSString stringWithFormat:@"%zd",touchedButton.tag]];
}
|
5.改进属性字符串
在使用属性字符串的时候,不论生成还是对已知字符串进行某些字段设置属性,都是一件很麻烦的事情,要编写大段的样板代码实在是浪费时间。对生成配置属性字符串的操作进行简化,但是不要放弃系统提供的设置方法,因为系统的都会用,改进之后会好上手。
改进生成属性字符串的方式,简单需求比如一个个的拼接:
1 2 3 4 5 6 7 8 9 10
| + (instancetype) builder;
+ (instancetype) builderWithDefaultStyle:(NSDictionary *)defaultStyle;
- (HLLAttributedBuilder *) appendString:(NSString *)string; - (HLLAttributedBuilder *(^)(NSString *str)) appendString;
- (HLLAttributedBuilder *) appendString:(NSString *)string forStyle:(NSDictionary *)style; - (HLLAttributedBuilder *(^)(NSString *str ,NSDictionary *style)) appendStringAndStyle;
|
更常见的需求比如,查找特定的字符串然后进行属性设置。一种情形是,已有一个字符串,需要对其中的某一些字符串进行属性字符串设置,支持正则表达式匹配:
1 2 3 4 5
| + (instancetype) builderWithString:(NSString *)originalString; + (instancetype) builderWithString:(NSString *)originalString defaultStyle:(NSDictionary *)defaultStyle
- (HLLAttributedBuilder *) configString:(NSString *)string forStyle:(NSDictionary *)style - (HLLAttributedBuilder *(^)(NSString *str ,NSDictionary *style)) configStringAndStyle
|
根据上面设置的属性然后调用- (NSAttributedString *) attributedString;
方法就可以生成对应的属性字符串。
使用例子
1 2 3 4 5 6 7 8 9 10 11 12
| NSString * display = @"hello = nihao = Hello = 你好 = nihao"; attachment 是NSTextAttachment类的实例 [[[[[[[HLLAttributedBuilder builderWithString:display] configString:@"hello" forStyle:@{NSUnderlineColorAttributeName:[UIColor redColor], NSUnderlineStyleAttributeName:@1, NSForegroundColorAttributeName:[UIColor orangeColor]}] configString:@"nihao" forStyle:@{NSStrokeColorAttributeName:[UIColor redColor], NSStrokeWidthAttributeName:@1}] configString:@"H" forStyle:@{NSBackgroundColorAttributeName:[UIColor greenColor]}] appendAttachment:attachment] appendString:@"娃大喜"] attributedString];
|
内部主要是使用私有类来记录每次的属性设置,用attributedString属性来记录,然后使用数组来保存这个私有实例。对于-append...
方式,每次将记录的对象放入数组,而对于-configString...
方式,则是数组中一直只有一个对象。
6.简化NSUserDefaults的使用
NSUserDefaults一般用来保存一些简单的数据到本地,但是写起来会写很多的样板代码,没有什么技术含量,稍微不注意还会出错,这里将NSUserDefaults添加一个分类,简化他的使用。
结合宏和block的特性,可以很优雅的使用点语法调用。
1 2 3 4 5 6 7 8 9 10
| #define MM_UserDefaults [NSUserDefaults standardUserDefaults]
@interface NSUserDefaults (MM_Tools)
- (NSUserDefaults *(^)(NSString *key,BOOL value))mm_addBool; - (BOOL(^)(NSString *))mm_boolValue; ... - (NSUserDefaults *(^)(NSString *key,id value))mm_addObject; - (id(^)(NSString *))mm_objectValue; @end
|
对应的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| - (NSUserDefaults *(^)(NSString *,BOOL))mm_addBool{ return ^NSUserDefaults *(NSString *key,BOOL value){ return self.mm_addObject(key,@(value)); }; } - (BOOL(^)(NSString *))mm_boolValue{ return ^BOOL(NSString *key){ return [self.mm_objectValue(key) boolValue]; }; } ... - (NSUserDefaults *(^)(NSString *,id))mm_addObject{ return ^NSUserDefaults *(NSString *key,id value){ [self setValue:value forKey:key]; [self synchronize]; return self; }; } - (id(^)(NSString *))mm_objectValue{ return ^id(NSString *key){ return [self valueForKey:key]; }; }
|
使用的时候可以这样
1 2
| MM_UserDefaults.mm_addBool(@"some_bool_key",YES); MM_UserDefaults.mm_boolValue(@"some_bool_key");
|
7.优雅的为分类添加属性
难保会遇到要在某一个分类中添加好多的属性,结合runtime可以实现,但是要写好多的重复代码,对于这些重复代码,使用宏来统一替换是一个很好的方法。
借由下面文章中的方法,实现为一个单例添加一些属性,结合上面的MM_UserDefaults
对这些属性进行本地存储
1 2 3 4 5 6 7 8 9 10 11
| @interface MM_Manager (UserDefaults)
mm_property_basicDataType(BOOL, openDebugerToggle ... @end
@implementation MM_Manager (UserDefaults) mm_def_property_basicDataType(BOOL, openDebugerToggle)
@end
|
宏里面有两个对当前类的方法需要添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @implementation MM_Manager (MM_associated) - (id)mm_getAssociatedObjectForKey:(const char *)key { const char * propName = key; NSString * key_ = [NSString stringWithUTF8String:propName]; return MM_UserDefaults.mm_objectValue(key_); }
- (id)mm_setAssignAssociatedObject:(id)obj forKey:(const char *)key; { const char * propName = key; NSString * key_ = [NSString stringWithUTF8String:propName]; id oldValue = MM_UserDefaults.mm_objectValue(key_); MM_UserDefaults.mm_addObject(key_ ,obj); NSLog(@"userdefault:%@ for key:%@",obj,key_); return oldValue; } @end
|
要了解如何使用宏,请参考👇的文章
为分类添加试用宏添加属性
对ReactiveCocoa中宏的解释
8.合理的修改readonly属性以及便捷的进行copy操作
一个场景是一个类通过一个参数初始化,然后他也有一个属性和这个参数一样,但是是readonly的,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @interface Reminder : NSObject
@property (nonatomic, copy, readonly) NSString *title; - (instancetype)initWith:(NSString *)title;
@impeletation
- (instancetype)initWithTitle:(NSString *)title { self = [super init]; if (self) { _title = title; } return self; }
|
然后就没有办法修改这个title
属性了,虽然可以通过KVC,但是,想象一下有很多参数的时候,那就是比较麻烦的事情了。具体还有其他什么麻烦的情况,可以参考这个博文,里面也有应对麻烦场景下的解决办法,这些办法都不是很优雅,最后通过runtime特性做出了一个小巧的工具。
里面主要用到了runtime
来获取类的所有属性、消息传递、KVC
来对所有的属性进行赋值,主要涉及到两个方法
1 2 3
| - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel;
- (void)forwardInvocation:(NSInvocation *)invocation;
|
具体NSInvocation
的用法,一搜一大堆,这不是重点,根据以上两个方法可以将target对象进行KVC设置readonly属性,最后的工具方法有两个:
1 2 3 4
| @interface NSObject (AHKBuilder) - (instancetype)initWithBuilder_ahk:(void (^)(id))builderBlock; - (instancetype)copyWithBuilder_ahk:(void (^)(id))builderBlock; @end
|
有两种使用方法,第一种就是为readonly属性设置值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @protocol MM_testObjBuilder <NSObject> @property (nonatomic ,retain ,readwrite) NSString * title; @end
@interface MM_testObj : NSObject @property (nonatomic ,copy) NSString * detail; @property (nonatomic ,retain ,readonly) NSString * title; - (instancetype) initWith:(NSString *)title; @end
MM_testObj * obj = [[MM_testObj alloc] initWith:@"Name"]; obj.detail = @"Detail"; NSLog(@"%@/%@",obj.title,obj.detail);
MM_testObj * obj_1 = [[MM_testObj alloc] initWithBuilder_ahk:^(id<MM_testObjBuilder>builder) { builder.title = @"Name_1"; }]; NSLog(@"%@/%@",obj_1.title,obj_1.detail);
|
第二种是对一个已有对象进行拷贝,说是拷贝,其实就是挨个对属性进行赋值,所以也就不需要被拷贝的类遵守NSCopying
协议
1 2 3 4 5 6 7 8 9
| MM_testObj * obj_2 = [obj copyWithBuilder_ahk:^(id<MM_testObjBuilder>builder) {
}] NSLog(@"%@/%@",obj_2.title,obj_2.detail)
MM_testObj * obj_3 = [obj copyWithBuilder_ahk:^(id<MM_testObjBuilder>builder) { builder.title = @"Name_3" }] NSLog(@"%@/%@",obj_3.title,obj_3.detail)
|
在正常开发中是不太会用到这种操作的,只不过是对Builder模式和runtime、NSInvocation的一个小练习。当然,如果在逆向的时候需要hook一些类,那就是很实用的工具了。
参考文章
仓库地址
Continue update …