開篇作一下更新說明,當你看完這篇文章的時候,若是你以爲文章裏面的實現方案與需求不是那麼合拍,請不要懷疑本身,由於我也這麼以爲。但其實主要目的仍是爲了介紹一下runtime中一個不太經常使用的知識點,實現需求只是順帶,感謝盒子大佬的講解,學到了不少東西。bash
iOS開發作了好幾年,一直想寫點東西卻沒有動手,一個是由於懶,還有一個就是我一個同事說的,我寫啥?想寫的別人都寫過了。網絡
事實上也確實是這樣,不管你想知道什麼知識點,幾乎都能在網上找到答案,實在是沒那個必要在後面跟風,關鍵是你尚未人家講得好。async
可是凡事也有利弊,資料太多了不免選擇恐懼症,隨便搜一個組件化流程都能找到十幾個不一樣版本的方案,最可怕的是我還以爲他們說的都對!這就很尷尬了。ide
更可悲的是,看了這麼多的組件化教程,被安利了各類庫以後,我依然沒有把組件化學會。組件化
就比如聽過了許多大道理,卻依舊過很差這一輩子。記住了許多理論,卻依然寫很差代碼。ui
這也是土系魔法講義的由來,這個系列的每一篇文章都會更接地氣一些,以一個具體需求爲起始,用一種特殊的方式將文章的中心點講述出來。spa
文章會以code和思路爲主,講解部分比較少,根據需求隨時變動。線程
若是有什麼地方說的不對,不用過來打我,我確定改。code
一個最基本的UIbutton的使用大概應該是這個樣子的:orm
- (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
方法裏面。
固然這個寫法實在是太...,除了萌新以外不多真的有人這麼寫了,可是其實上面那段代碼其實還有如下幾個變種:
[Util cameraAuth:^{
//乾點啥
} fail:^{
}];
複製代碼
好了,寫到這裏,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 Swizzling
和objc_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子類,你會寫嗎?