嗯。。怎么開篇呢
。
。
(一個小時。。搓腳毛苦思中。。)
。
呵呵,你以為博主真的不知道怎么開篇么,這里花了一個小時的時間其實是另有深意的好么,我的套路就是這么濕?。?,其實博主是為了闡述一個問題!就是如果只想了一個標題,內(nèi)容卻不知道怎么組織就會是這樣的慢性尷尬癥。就像我們做項目的時候經(jīng)常腦袋一熱,二話不說上來就擼代碼。然后就發(fā)現(xiàn)框架不行,不夠靈活無法擴展,功能缺失!然后在你準備調(diào)整架構(gòu)的時候,產(chǎn)品經(jīng)理就跳出來補上一刀——改需求。

這種絕望,我們都經(jīng)歷過!
產(chǎn)品經(jīng)理常用必殺:
『用戶反應(yīng),按鈕雙擊會有錯誤,所以你把整個項目有交互的控件都設(shè)置為不能雙擊吧,干巴爹』
——『這個輸入框怎么能輸入??????這個呢,把所有的輸入框都禁止輸入亂七八糟的東西吧,么么噠』
——這個是emoji,并沒有這么亂七八糟。。
『哦之前忘了定義,給所有的輸入框都限制大數(shù)吧、給所有的頁面都加上返回手勢吧,給所有的……』
『線上有幾個頁面暫時不需要了,能屏蔽掉么』
。。。
我能和產(chǎn)品探討一下引力波的探測與廣義相對論的必然聯(lián)系么?老子弄死你丫的
我想項目新人幾乎都遇到過這些坑吧,產(chǎn)品經(jīng)理不專業(yè)在一般的公司里是常態(tài),現(xiàn)在的互聯(lián)網(wǎng),一言不合就改需求,也是個常態(tài)。
但是強大的猿類們,決不能屈服于這種常態(tài),變態(tài)起來!!
只要努力微笑,命運也會懼怕我的獠牙。
回到這次的主題——『打補丁』
什么是打補丁呢,打補丁是使用針線在織物上輔以破布以縫補上,是民間偉大的傳統(tǒng)手工藝之一。該技藝嚴謹精密,講究施針,針法所達百余種,常見的有滾、鋪、蓋、戳等等,針腳整齊、摻色輕柔、虛實合度、變化豐富。一千多年來,逐步形成。。。誒,這老毛病就是改不了,總是喜歡一本正經(jīng)的扯犢子~~
博主要說的『打補丁』必然不是針線活!再次聲明這里是技術(shù)博客,并非傳統(tǒng)技藝授受中心!
我們給一個東西打補丁,原因就兩個字!破。
所以我們給項目打補丁也是因為項目破了,就像遇到上面的整改需求,功能不完善了,功能缺失了我們就有了打補丁的必要了。
在iOS中打補丁,我以修補時機為主分為兩種打補丁的方式,
- 開發(fā)中的打補丁
- 線上的打補丁
開發(fā)中修補
早知今日,何必當初。何出此感慨?假如開始項目的時候框架設(shè)計好一點,今天還會淪落到打補丁么??但是耍流氓的敏捷開發(fā)、坑爹的開發(fā)周期、逆天的用戶需求之下何來優(yōu)秀的框架搭設(shè)???
看著產(chǎn)品方案,我顫抖的小嘴剛要張開說『一個禮拜框架搭設(shè),兩個禮拜編碼,應(yīng)該…』然而老板拍拍你的肩膀『小伙子 這個周末弄出來,我以前也是做開發(fā)的,時間很充足哦,不許騙我喔~』,老板你確定你不是以前做PPT的。
這個時候的心情就跟剛看完《小時代》一樣憋屈。所以開發(fā)中需要打補丁的狀況太多了,改結(jié)構(gòu),重寫,時間不夠,所以只能打補丁了!
AOP
當初學習JavaEE的時候接觸了該理念,反正文鄒鄒的概念博主也不貼出來了,AOP就是面向切面編程的簡稱,說白了就是一個打補丁的編程方式!不侵入式地給一個方法添加代碼。冠名之『 潤物細無聲の技能』,嘿嘿,有個片假名的標題,你們都興奮了起來呢~~
至于AOP的基本理念、適用場景等,各位看官就自行Wiki吧。什么竟然說博主其實也不懂什么是AOP?。?!知道什么是學霸么!就是舉手投足高分拿下、信手拈來理論來辯、回眸一笑全是敗將!不要懷疑!這就是博主,真學霸!

說了這么多,到底怎么用AOP方式給項目打補丁呢?
我們來打個栗子吧!
『只允許所有的控件的單擊』
一個項目中少說成百上千的控件,即使有些控件復用,項目中控件的數(shù)量也會幾十上百的UI控件無法復用,那怎么把這幾十上百的控件都禁止雙擊呢?
我們知道UIView有個屬性
@property(nonatomic,getter=isExclusiveTouch) BOOL exclusiveTouch __TVOS_PROHIBITED; // default is NO
如果一個View設(shè)置exclusiveTouch
為YES的話,那么該View就會獨占事件,就是當點擊自己的時候,其他所有的View的事件都會被Block,并且當前的View也只能單次點擊。利用這個特性我們就能把所有的控件的這個屬性都設(shè)置為YES不就行了嘛。
然后吭吃吭吃地給幾十上百個控件都設(shè)置了該屬性,看到都累,這樣的方式打補丁,那萬一產(chǎn)品又來了說不要禁止雙擊呢?

你這不是在給項目打補丁,是在打自己。
我們有下面這樣投機的方式:
@implementation MyView //繼承自UIView
+ (void)load{ //load方法是所有繼承NSObject類都擁有的類方法,可以直接理解為這個方法加載的灰常早灰常的早!!
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
//把原來的方法換掉
SEL originalSelector = @selector(willMoveToSuperview:);//View被加到父View的時候的回調(diào)
SEL swizzledSelector = @selector(ddwillMoveToSuperview:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod =class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
- (void)ddwillMoveToSuperview:(UIView *)newSuperview{
[self ddwillMoveToSuperview:newSuperview];//這個地方 可自行資料為何用self
[self setExclusiveTouch:YES];
}
@end
其實這就就是OC的Runtime的 Method Swizzling
,很輕易地實現(xiàn)了AOP打補丁,這樣我們的UIView都只能單擊啦,哈哈,擊潰產(chǎn)品+1。
更多的資料任意門:
Method Swizzling 和 AOP 實踐{:target="_blank"}
當然很多優(yōu)秀的開源項目都是潤物細無聲的老司機,他們的庫不需要添加任何代碼就能跑起來,其實這個方式就是AOP,就是使用了load方法和Runtime!
比如給鍵盤打補丁老司機的 IQKeyboardManager{:target="_blank"}
fuckingd。。 噢不 是forking dog團隊的給返回手勢打補丁的UITableView-FDTemplateLayoutCell{:target="_blank"} 這個團隊還是非常棒的!他們的開源項目質(zhì)量都很高!值得學習!
Category
Category可以給任意一個繼承自NSObject的類添加方法,重寫方法! 其作用就是為了輕繼承的,所以利用Category同樣可以給項目打補丁!
同樣的問題
『只允許所有的控件的單擊』
我們可以給UIView寫一個Category
#import "UIView+SingleTap.h"
@implementation UIView(SingleTap)
//該方法會直接覆蓋原View的方法
-(BOOL)isExclusiveTouch{
return YES;
}
@end
不足之處就是在使用的時候必須引用該Category的頭文件
當然如果你確定要干掉所有控件的雙擊,也可以在Pch預編譯頭文件中引入該Category,這樣整個項目的每個文件默認都會引入這個Category,一勞永逸了。
Notification
利用通知也能給項目修修補補。
個人認為作為一個iOS開發(fā)者首先都要有一定的YY能力!怎么說?因為我們幾乎不可能看到應(yīng)用層框架源碼,所以很多實現(xiàn)機制只能靠猜!也因為這種狀況,我覺得iOSer都應(yīng)該養(yǎng)成一個癖好——對蘋果暴露的方法和屬性列表要近乎狂熱地感興趣,比如學習一個框架的時候頭文件中所有東西都不要放過!也應(yīng)該學會掃描方法列表和成員變量的技能,比如有好事者把iOS Runtime的所有私有接口都掃面了出來 iOS-Runtime-Headers{:target="_blank"} 這個東西真TM太贊了??!哈哈
現(xiàn)在我們有這樣的一個需求
『讓所有的UITextField不允許輸入emoji表情』
如果在每個使用了UITextField的地方使用代理方法
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string;
然后一個一個字符的檢測,如果是emoji的話就刪除,想想全局有幾十上百的UITextField,就菊花一緊,這么多改起來蛋疼,以后維護起來還會更蛋碎,所以這種方法是絕不可行的!!
當然就想想AOP、和Category的方式了,當然這些方式必然能做到的,但是我們這里要用別的方法!
二話不說擼出UITextfieldDelegate.h
我們可以看到有幾個String常量!看到Notification
關(guān)鍵字就絕逼是注冊接受通知用的了!可以猜到UITextField在?各種狀態(tài)回調(diào)時會發(fā)出好幾個通知:
UIKIT_EXTERN NSString *const UITextFieldTextDidBeginEditingNotification;
UIKIT_EXTERN NSString *const UITextFieldTextDidEndEditingNotification;
UIKIT_EXTERN NSString *const UITextFieldTextDidChangeNotification;
所以我們可以利用著幾個通知這么做
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
/**
* 監(jiān)聽全局的textview和textfield的EndEidt
*/
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(enhanceGlobalInputs:) name:UITextFieldTextDidEndEditingNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(enhanceGlobalInputs:) name:UITextViewTextDidEndEditingNotification object:nil];
return YES;
}
- (void)enhanceGlobalInputs:(NSNotification*)notification{
//移除emoji表情 stringByRemovingEmoji是我給NSString寫的一個擴展,用于移除Emoji
if ([notification.name isEqualToString:@"UITextFieldTextDidEndEditingNotification"]) {
((UITextField *)notification.object).text = [((UITextField*)notification.object).text stringByRemovingEmoji];
}else if([notification.name isEqualToString:@"UITextViewTextDidEndEditingNotification"]){
((UITextView *)notification.object).text = [((UITextView*)notification.object).text stringByRemovingEmoji];
}
//限制大數(shù) 只允許輸入10位長度的數(shù)字 isPositiveFloat是一個判斷字符串中的數(shù)字是否是合法數(shù)字的方法,簡單的正則匹配
NSString*(^limitBigNum)(NSString* num) = ^(NSString* num){
if ([Tools isPositiveFloat:num]) {
if (num.length>10) {
return [num substringWithRange:NSMakeRange(0, 10)];
}else{
return num;
}
}else{
return num;
}
};
if ([notification.name isEqualToString:@"UITextFieldTextDidChangeNotification"]) {
((UITextField *)notification.object).text = limitBigNum(((UITextField *)notification.object).text);
}else if([notification.name isEqualToString:@"UITextViewTextDidChangeNotification"]){
((UITextView *)notification.object).text = limitBigNum(((UITextView *)notification.object).text);
}
}
NSString+Emoji.m
#import "NSString+Emoji.h"
#include <unicode/utf8.h>
@implementation NSString(Emoji)
- (NSString *)stringByRemovingEmoji {
NSData *d = [self dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO];//有損轉(zhuǎn)換
if(!d){
return nil;
}
const char *buf = (char*)d.bytes;
NSUInteger len = [d length];
char *str = (char *)malloc(len);//分配char*len大小的內(nèi)存
unsigned int inputIndex = 0, outpuIndex = 0;
int uc;//當前unicode字符的編碼 十進制表示
while (inputIndex < len) {
U8_NEXT_UNSAFE(buf, inputIndex, uc);//一個一個字符遍歷
if(0x2100 <= uc && uc <= 0x26ff) continue;//是emoji就放棄本輪循環(huán)
if(0x1d000 <= uc && uc <= 0x1f77f) continue;//是emoji就放棄本輪循環(huán)
U8_APPEND_UNSAFE(str, outpuIndex, uc);//不是emoji表情,添加到str中
}
return [[NSString alloc] initWithBytesNoCopy:str length:outpuIndex encoding:NSUTF8StringEncoding freeWhenDone:YES];
}
@end
宏替換
最后這種方法也許很多人都知道怎么用了,iOS的編譯機制是這樣的:對于擁有相同方法簽名的方法,后編譯的會覆蓋較早編譯的方法。
#pragma mark - 重寫NSLog,Debug模式下打印日志和當前行數(shù)
#if DEBUG
#define NSLog(FORMAT, ...) fprintf(stderr,"\nfunction:%s line:%d content:%s\n", __FUNCTION__, __LINE__, [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);
#else
#define NSLog(FORMAT, ...) nil
#endif
iOS框架的NSLog會比較早編譯,但是最后會被我們應(yīng)用中的覆蓋掉。
這樣就等于給全局的NSLog給打上一個補丁了或者說給NSLog增強了!這個就不贅述了。
線上的修補
因為蘋果一個多禮拜審核周期的尿性,給一個線上的項目打補丁還是很有意義的。但是線上的打補丁方式條件就要苛刻許多了!一般是在項目中先植入一個引擎類的東西,然后移動端去服務(wù)端獲取修補的指令(Lua、JavaScript等腳本,至于用什么語言和這個修補引擎的設(shè)計有關(guān)),然后這個引擎會將指令通過一定的映射規(guī)則生成本地的的可執(zhí)行指令,比如OC中可以使用Runtime新增類或者修改類,然后達到打補丁的效果,這也稱為熱更新
技術(shù)!下面的都是成熟的熱更新引擎,可以學習一下
wax
使用Wax給你的應(yīng)用程序打補丁{:target="_blank"}
JSPatch
JSPatch{:target="_blank"}
不過如果你的項目支持了熱更新,那么產(chǎn)品就更加肆無忌憚了,因為你可以給線上的項目打補丁了,所以你懂得~~
『這樣要改一下』。。被嚇得都質(zhì)壁分離了!
總結(jié)
這篇文主要是分享了本人在正式項目中遇到時間緊迫但是急需變更需求的時候的一些解決方法與思路,都是拙見,都是野路子,但是我就是喜歡這樣,哈哈 (自帶BGM我就是愛音樂別叫我停下來~)
但是,預見性的架構(gòu)設(shè)計思想可以讓你避免掉很多的野路子,一份代碼的優(yōu)雅以及可靠都是在一些規(guī)范的設(shè)計原則上建立起來的,所以哦,像一些基本的設(shè)計原則比如Don't repeat yourself
原則;封裝成類,或者在基類中的封裝;眾多設(shè)計模式有良好的擴展和靈活特性的指導;又或者利用其他編程范式如函數(shù)式、響應(yīng)式來寫出更加健壯靈活的代碼,可以讓你的項目更加健壯、靈活、、高效、優(yōu)雅。

散了!回家抄黨章避避邪去了,又要改需求。。。。