iOS土味兒講義(一)--一個Button引發的血案

Mr_Wei發表於2018-02-28

開篇做一下更新說明,當你看完這篇文章的時候,如果你覺得文章裡面的實現方案與需求不是那麼合拍,請不要懷疑自己,因為我也這麼覺得。但其實主要目的還是為了介紹一下runtime中一個不太常用的知識點,實現需求只是順帶,感謝盒子大佬的講解,學到了很多東西。

大地母親在忽悠護佑著你

iOS開發做了好幾年,一直想寫點東西卻沒有動手,一個是因為懶,還有一個就是我一個同事說的,我寫啥?想寫的別人都寫過了。

事實上也確實是這樣,無論你想知道什麼知識點,幾乎都能在網上找到答案,實在是沒那個必要在後面跟風,關鍵是你還沒有人家講得好。

但是凡事也有利弊,資料太多了難免選擇恐懼症,隨便搜一個元件化流程都能找到十幾個不同版本的方案,最可怕的是我還覺得他們說的都對!這就很尷尬了。

更可悲的是,看了這麼多的元件化教程,被安利了各種庫之後,我依然沒有把元件化學會。

就好比聽過了許多大道理,卻依舊過不好這一生。記住了許多理論,卻依然寫不好程式碼。

這也是土系魔法講義的由來,這個系列的每一篇文章都會更接地氣一些,以一個具體需求為起始,用一種特殊的方式將文章的中心點講述出來。

文章會以code和思路為主,講解部分比較少,根據需求隨時變更。

如果有什麼地方說的不對,不用過來打我,我肯定改。

從一個Button說開去

一個最基本的UIbutton的使用大概應該是這個樣子的:

- (void)viewDidLoad {
    [super viewDidLoad];
    UIButton *testButton = [UIButton buttonWithType:UIButtonTypeCustom];
    testButton.backgroundColor = [UIColor redColor];
    testButton.frame = CGRectMake(100, 100, 100, 100);
    [self.view addSubview:testButton];
    [testButton addTarget:self action:@selector(test:) forControlEvents:UIControlEventTouchUpInside];
}

- (void)test:(UIButton*)sender{
    NSLog(@"test");
}
複製程式碼

那麼問題來了,想一想,如果現在臨時加一個需求,button響應事件之前要先獲取相機視訊許可權,應該怎麼做?(舉個例子,同樣的需求還有獲取位置許可權,檢測網路連線,檢視登入狀態等等)

先不說button,許可權獲取的程式碼大概應該長這樣:

AVAuthorizationStatus authStatus =  [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied){
        //啥也不幹
    }else if(authStatus == AVAuthorizationStatusNotDetermined){
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            if(granted){
                dispatch_async(dispatch_get_main_queue(), ^{
                    //幹正事兒
                });
            }
        }];
    }else{
        //幹正事兒
    }
複製程式碼

需要注意的就一點,同意獲取許可權的回撥需要返回主執行緒。

還有你需要設定info.plist的Privacy。

說起來可能有些搞笑,很大一部分的工程中,上面那段程式碼就這堂而皇之的躺在- (void)test:(UIButton*)sender方法裡面。

當然這個寫法實在是太...,除了萌新之外很少真的有人這麼寫了,但是其實上面那段程式碼其實還有以下幾個變種:

  1. 知道將button原本的響應事件單獨提出來,至少不用寫兩遍。
  2. 將獲取許可權的程式碼封裝起來,大概這樣:
    [Util cameraAuth:^{
       //乾點啥    
    } fail:^{
        
    }];
    複製程式碼
  3. 以上兩種方法互相結合。

好了,寫到這裡,50%的開發者已經躺槍了。

“沒錯我們就是這麼寫的!”

這時候有人知道我想要說什麼嗎?對!萬惡的產品經理又來了。

“我需要你在這個button事件裡,再加上照片許可權獲取,位置許可權獲取,音訊許可權獲取!”

然後程式碼就變成了:

[Util cameraAuth:^{
    [Util audioAuth:^{
        [Util photoAuth:^{
           [Util locationAuth:^{
           
    } fail:^{
        
    }];    
    } fail:^{
        
    }];      
    } fail:^{
        
    }];    
    } fail:^{
        
    }];
複製程式碼

就問你怕不怕?

彆著急,飯一口一口吃,我們現在先來拯救一下這50%的小夥伴。

其實很簡單,你需要的是一個UIButton的子類。(什麼玩意兒?褲子都脫了你就給我看這個??)

是的就是這樣,一個UIbutton的子類,需要實現的方法大概如下:

-(void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event{
    AVAuthorizationStatus authStatus =  [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied){
        
    }else if(authStatus == AVAuthorizationStatusNotDetermined){
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            if(granted){
                dispatch_async(dispatch_get_main_queue(), ^{
                    [super sendAction:action to:target forEvent:event];
                });
            }
        }];
    }else{
         [super sendAction:action to:target forEvent:event];
    }
    
}
複製程式碼

這樣的話,你就可以肆無忌憚的和原生的UIbutton一樣去使用它了,並不需要在原本的action中新增任何的程式碼。

當然有必要的話還應該對event做一下區分,以免影響到這個button的其他功能互動。

有沒有人這麼做?我想肯定有,而且不算少,初步估計應該有個10%左右吧。

如果你真的是這麼做的,恭喜你掉坑了,這種寫法有個弊端就是使用button子類替換很不方便,工作量大,而且降低了可讀性,總感覺哪裡不太對。

沒關係,至少路子走對了,如果想要繼續完善這個思路,下面的這個變種瞭解一下。

你知道安利runtime嗎?

如果你對runtime的瞭解和使用僅限於Method Swizzlingobjc_setAssociatedObject的話,往下看一定會有收穫。

新建一個UIbutton的類別,假設之前的button子類為SKButton,則新增方法如下:

- (void)setNeedsCameraPermission{
    object_setClass(self, [SKButton class]]);
}
複製程式碼

是的你沒有看錯,只需要一句話,就可以把一個UIbutton,變成他的子類,不需要#import,不需要改類名,屠龍寶刀點選就送,是不是很方便?

but...

你以為完了嗎?怎麼可能。

上面的寫法和直接替換一個UIbutton的子類一樣,有一個共同的弊端,就是當工程內部使用的button控制元件本身就已經是一個寫好的輪子了,也是UIbutton的子類,那你怎麼辦?

SKButton的父類由UIbutton改為當前子類?呸!不要臉!

這個思路對嗎?當然對!

但是作為一個輪子,別人拿去之後還沒使用就要先補胎,你好意思嗎?

所以在runtime中,不僅可以動態變更類,還可以動態建立類,你知道嗎?

一個動態建立的支援獲取相機許可權的button的程式碼大概長這樣:

- (void)setNeedsCameraPermission{
    NSString *className = [NSString stringWithFormat:@"CameraPermission_%@",self.class];
    Class kclass = objc_getClass([className UTF8String]);
    if (!kclass)
    {
        kclass = objc_allocateClassPair([self class], [className UTF8String], 0);
    }
    SEL setterSelector = NSSelectorFromString(@"sendAction:to:forEvent:");
    Method setterMethod = class_getInstanceMethod([self class], setterSelector);
    object_setClass(self, kclass);
    const char *types = method_getTypeEncoding(setterMethod);
    class_addMethod(kclass, setterSelector, (IMP)camerapermission_SendAction, types);
    objc_registerClassPair(kclass);
}

static void camerapermission_SendAction(id self, SEL _cmd, SEL action ,id target , UIEvent *event)
{
        struct objc_super superclass = {
        .receiver = self,
        .super_class = class_getSuperclass(object_getClass(self))
    };
    void (*objc_msgSendSuperCasted)(const void *, SEL, SEL, id, UIEvent*) = (void *)objc_msgSendSuper;
    AVAuthorizationStatus authStatus =  [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    if (authStatus == AVAuthorizationStatusRestricted || authStatus ==AVAuthorizationStatusDenied){
        
    }else if(authStatus == AVAuthorizationStatusNotDetermined){
        [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
            if(granted){
                dispatch_async(dispatch_get_main_queue(), ^{
                    objc_msgSendSuperCasted(&superclass, _cmd,action,target,event);
                });
            }
        }];
    }else{
        objc_msgSendSuperCasted(&superclass, _cmd,action,target,event);
    }
}
複製程式碼

這樣就動態建立並替換了一個叫做“CameraPermission_XXXXXX”的button子類,任何一個button,只需要呼叫setNeedsCameraPermission方法,就能夠為button新增許可權獲取功能了。

能夠寫到這裡的話,基本上就差不多了,不過真的有人有耐心把這麼爛的文章看完嗎?

如果你真的看到這的話,那一定是因為愛情了,你也一定發現了我似乎漏掉了什麼東西,我當然是故意的!

好了下面給你留一個作業,如果讓你動態建立一個可自由組合,同時獲取多個許可權的button子類,你會寫嗎?

相關文章