開篇做一下更新說明,當你看完這篇文章的時候,如果你覺得文章裡面的實現方案與需求不是那麼合拍,請不要懷疑自己,因為我也這麼覺得。但其實主要目的還是為了介紹一下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
方法裡面。
當然這個寫法實在是太...,除了萌新之外很少真的有人這麼寫了,但是其實上面那段程式碼其實還有以下幾個變種:
- 知道將button原本的響應事件單獨提出來,至少不用寫兩遍。
- 將獲取許可權的程式碼封裝起來,大概這樣:
[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子類,你會寫嗎?