iOS項目中常用的“打補丁”技巧

嗯。。怎么開篇呢

。

(一個小時。。搓腳毛苦思中。。)

。

呵呵,你以為博主真的不知道怎么開篇么,這里花了一個小時的時間其實是另有深意的好么,我的套路就是這么濕?。?,其實博主是為了闡述一個問題!就是如果只想了一個標題,內(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)雅。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內(nèi)容