Files
keyboard/keyBoard/Class/Manager/AppleSignInManager.m
2025-10-30 14:35:06 +08:00

156 lines
6.4 KiB
Objective-C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// AppleSignInManager.m
// 封装“用 Apple 登录”的实现与存储
#import "AppleSignInManager.h"
#import <UIKit/UIKit.h>
#import <Security/Security.h>
static NSString * const kKBAppleUserIdentifierKey = @"com.company.keyboard.apple.user"; // 钥匙串键名
@interface AppleSignInManager ()
@property (nonatomic, weak) UIViewController *presentingVC;
@property (nonatomic, copy) KBAppleSignInCompletion completion;
@end
@implementation AppleSignInManager
+ (instancetype)shared {
static AppleSignInManager *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ instance = [AppleSignInManager new]; });
return instance;
}
- (NSString *)storedUserIdentifier {
return [self.class keychainLoad:kKBAppleUserIdentifierKey];
}
- (void)signInFromViewController:(UIViewController *)presenting completion:(KBAppleSignInCompletion)completion {
if (@available(iOS 13.0, *)) {
self.presentingVC = presenting;
self.completion = completion;
ASAuthorizationAppleIDProvider *provider = [ASAuthorizationAppleIDProvider new];
ASAuthorizationAppleIDRequest *request = provider.createRequest;
request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
ASAuthorizationController *controller = [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
controller.delegate = self;
controller.presentationContextProvider = self;
[controller performRequests];
} else {
if (completion) {
NSError *err = [NSError errorWithDomain:@"AppleSignIn" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Apple 登录需要 iOS 13 及以上版本"}];
completion(nil, err);
}
}
}
- (void)checkCredentialStateWithCompletion:(void(^)(ASAuthorizationAppleIDProviderCredentialState state))completion {
if (!completion) return;
if (@available(iOS 13.0, *)) {
NSString *userID = self.storedUserIdentifier;
if (!userID) {
completion(ASAuthorizationAppleIDProviderCredentialNotFound);
return;
}
ASAuthorizationAppleIDProvider *provider = [ASAuthorizationAppleIDProvider new];
[provider getCredentialStateForUserID:userID completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{ completion(credentialState); });
}];
} else {
completion(ASAuthorizationAppleIDProviderCredentialNotFound);
}
}
// 使用指定的 userIdentifier 检查凭证状态(无需本地存储)
- (void)checkCredentialStateForUserID:(NSString *)userID completion:(void(^)(ASAuthorizationAppleIDProviderCredentialState state))completion {
if (!completion) return;
if (@available(iOS 13.0, *)) {
if (userID.length == 0) {
completion(ASAuthorizationAppleIDProviderCredentialNotFound);
return;
}
ASAuthorizationAppleIDProvider *provider = [ASAuthorizationAppleIDProvider new];
[provider getCredentialStateForUserID:userID completion:^(ASAuthorizationAppleIDProviderCredentialState credentialState, NSError * _Nullable error) {
dispatch_async(dispatch_get_main_queue(), ^{ completion(credentialState); });
}];
} else {
completion(ASAuthorizationAppleIDProviderCredentialNotFound);
}
}
#pragma mark - 授权回调 (ASAuthorizationControllerDelegate)
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
if (@available(iOS 13.0, *)) {
ASAuthorizationAppleIDCredential *credential = authorization.credential;
// 不在本地持久化 userIdentifier统一交给服务端处理
// NSString *userID = credential.user; // 如需本地缓存,请自行决定是否保存
if (self.completion) {
self.completion(credential, nil);
}
}
self.completion = nil;
self.presentingVC = nil;
}
- (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
if (self.completion) {
self.completion(nil, error);
}
self.completion = nil;
self.presentingVC = nil;
}
#pragma mark - 授权界面展示锚点 (ASAuthorizationControllerPresentationContextProviding)
- (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller API_AVAILABLE(ios(13.0)) {
return self.presentingVC.view.window ?: UIApplication.sharedApplication.keyWindow;
}
#pragma mark - Keychain 工具
// 本地登出:删除已存储的 userIdentifier使 App 重新要求登录
- (void)signOut {
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: kKBAppleUserIdentifierKey,
(__bridge id)kSecAttrAccount: kKBAppleUserIdentifierKey};
SecItemDelete((__bridge CFDictionaryRef)query);
}
+ (BOOL)keychainSave:(NSString *)key value:(NSString *)value {
if (!key) return NO;
NSData *data = [value dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: key,
(__bridge id)kSecAttrAccount: key};
SecItemDelete((__bridge CFDictionaryRef)query);
NSMutableDictionary *attributes = [query mutableCopy];
attributes[(__bridge id)kSecValueData] = data ?: [NSData data];
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, NULL);
return (status == errSecSuccess);
}
+ (NSString *)keychainLoad:(NSString *)key {
if (!key) return nil;
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService: key,
(__bridge id)kSecAttrAccount: key,
(__bridge id)kSecReturnData: @YES,
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne};
CFTypeRef dataRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef);
if (status != errSecSuccess || !dataRef) return nil;
NSData *data = (__bridge_transfer NSData *)dataRef;
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
@end