前序
作为iOS开发人员一定了解过黑魔法,网络上关于黑魔法介绍的文章一抓一大把,在日常的开发工作的中也很少会运用到黑魔法,但是学会黑魔法在某些场景下面会提高开发效率。
开发场景
在APP中,或多或少会获取用户的一些权限,使用最多的就是获取用户的相机/相册权限。iOS权限的获取相比安卓要简单点,只需要在plist文件中配置相关的权限属性。在代码调用时系统会默认弹出权限申请框,但是权限申请框系统只会弹一次,那么产品希望在用户第一次不允许的情况下,再次使用相关功能时可以弹出自定义的弹层,指引用户去系统设置页开启相关权限。最常用的权限就是相机和相册的权限,随着版本的不断迭代,代码中存在多处的相机和相册的调用,而且调用的代码都是一样的,所以这时候统一处理权限问题变得十分的必要。
需求
1、第一次弹出系统权限申请弹框,点击好--使用权限; 点击不允许,弹出自定义的弹框。
2、如果用户第一次禁止权限,则弹出自定义弹框。
3、如果用户第一次允许权限,则正常使用功能。
实现思路
我们需要在弹出相册权限和相机界面之前就判断出用户是否有相应的权限,并且要知道需要申请的是相册还是相机权限,所以我拦截了赋值sourceType的方法。
+ (void)load {
// 通过class_getInstanceMethod()函数从当前对象中的method list获取method结构体,如果是类方法就使用class_getClassMethod()函数获取。
Method fromMethod = class_getInstanceMethod([self class], @selector(setSourceType:));
Method toMethod = class_getInstanceMethod([self class], @selector(swizzlingSetSourceType:));
/**
我们在这里使用class_addMethod()函数对Method Swizzling做了一层验证,如果self没有实现被交换的方法,会导致失败。
而且self没有交换的方法实现,但是父类有这个方法,这样就会调用父类的方法,结果就不是我们想要的结果了。
所以我们在这里通过class_addMethod()的验证,如果self实现了这个方法,class_addMethod()函数将会返回NO,我们就可以对其进行交换了。
*/
if (!class_addMethod([self class], @selector(swizzlingSetSourceType:), method_getImplementation(toMethod), method_getTypeEncoding(toMethod))) {
method_exchangeImplementations(fromMethod, toMethod);
}
}
为了实现需求1,添加了一个回调方法
@property (nonatomic, strong) void(^judgeJurisdiction)(BOOL granted)
为了实现需求3,添加了一个判断是否有权限的属性
@property (nonatomic, assign) BOOL hasJurisdiction
权限申请代码
- (void)swizzlingSetSourceType:(UIImagePickerControllerSourceType)sourceType {
if (sourceType == UIImagePickerControllerSourceTypePhotoLibrary) {
PHAuthorizationStatus status = [PHPhotoLibrary authorizationStatus];
if (status == PHAuthorizationStatusNotDetermined) {
__weak typeof(self) weakSelf = self;
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
dispatch_async(dispatch_get_main_queue(), ^{
if (status == PHAuthorizationStatusRestricted || status == PHAuthorizationStatusDenied) {
[weakSelf showAlertView:@"照片"];
}else{
if(weakSelf.judgeJurisdiction){
weakSelf.judgeJurisdiction(YES);
}
}
});
}];
}else if (status == PHAuthorizationStatusRestricted || status == PHAuthorizationStatusDenied) {
[self showAlertView:@"照片"];
}else{
[self setHasJurisdiction:YES];
}
} else {
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status == AVAuthorizationStatusNotDetermined) {
__weak typeof(self) weakSelf = self;
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!granted) {
[weakSelf showAlertView:@"相机"];
}else{
if(weakSelf.judgeJurisdiction){
weakSelf.judgeJurisdiction(YES);
}
}
});
}];
}else if (status == AVAuthorizationStatusRestricted || status == AVAuthorizationStatusDenied){
[self showAlertView:@"相机"];
}else{
[self setHasJurisdiction:YES];
}
}
[self swizzlingSetSourceType:sourceType];
}
弹框代码
- (void)showAlertView:(NSString *)str
{
[self setHasJurisdiction:NO];
if (!self.isShowAlertView) {
NSString *message = [NSString stringWithFormat:@"未获取您的%@权限,该功能无法正常使用。您可点击“设置”开启权限。", str];
__weak typeof(self) weakSelf = self;
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"" message:message cancelButtonItem:[RIButtonItem itemWithLabel:@"取消" action:^{
weakSelf.isShowAlertView = NO;
}] otherButtonItems:[RIButtonItem itemWithLabel:@"设置" action:^{
weakSelf.isShowAlertView = NO;
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
if ([[UIDevice currentDevice].systemVersion doubleValue] >= 10.0) {
[[UIApplication sharedApplication]openURL:url options:@{} completionHandler:nil];
}else{
[[UIApplication sharedApplication]openURL:url];
}
}
}], nil];
[alert show];
self.isShowAlertView = YES;
}
}
功能调用处代码修改
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc]init];
imagePickerController.delegate = self;
imagePickerController.allowsEditing = NO;
imagePickerController.sourceType = sourceType;
if (imagePickerController.hasJurisdiction) {
[self presentViewController:imagePickerController animated:YES completion:^{
[imagePickerController.navigationBar setTranslucent:NO];
}];
}else{
__block UIImagePickerController *strongImagePicker = imagePickerController;
imagePickerController.judgeJurisdiction = ^(BOOL granted) {
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:strongImagePicker animated:YES completion:nil];
});
}
};
}