项目中需要区分iOS设备,但是苹果已经封了获取UDID和MAC的API,目前可行的方案是在钥匙串Keychain中存储自己产生的唯一标识,如GUID。
关于iOS的唯一标识技术解决方案比较参见:这里
苹果官方钥匙串开发指导参见Keychain Services Programming Guide。
Firemonkey目前并没有为我们封装好Security.framework,看了苹果官方的使用示例,发现自己Wrapper太麻烦。网上找了下,发现一个开源项目的代码比较简单明了,是OC编写的,拿来修改一下编译成静态库以供Firemonkey使用。
以下是封装调用静态库的代码:下载静态库文件
使用上直接调用StoreItemToKeychain存储一对Key-Value值到钥匙串;
调用GetItemValueFromKeychain获取指定Key的Value值;
调用DeleteItemFromKeychain删除指定Key。
静态库只封装了通用密码类型,所有Key-Value都将加密存储在钥匙串中。
unit TU2.iOS.KeychainHelper;
interface
//访问钥匙串
function StoreItemToKeychain(const AServiceName, AKey, AValue: string; const bOverride: Boolean=True): Boolean;
function GetItemValueFromKeychain(const AServiceName, AKey: string): string;
function DeleteItemFromKeychain(const AServiceName, AKey: string): Boolean;
implementation
uses iOSapi.Foundation, Macapi.ObjectiveC, iOSapi.CocoaTypes, Macapi.Helpers;
type
KeychainHelper = interface(NSObject)
['{591C260D-D80F-44C7-9400-20D49F4B87AA}']
end;
KeychainHelperClass = interface(NSObjectClass)
['{605B4A71-35B5-4307-845D-97F6933C2A1E}']
//+ (NSString *) getPasswordForUsername: (NSString *) username andServiceName: (NSString *) serviceName error: (NSInteger *) error;
//+ (NSInteger) storeUsername: (NSString *) username andPassword: (NSString *) password forServiceName: (NSString *) serviceName updateExisting: (BOOL) updateExisting;
//+ (NSInteger) deleteItemForUsername: (NSString *) username andServiceName: (NSString *) serviceName;
[MethodName('getPasswordForUsername:andServiceName:error:')]
function getPasswordForUsernameAndServiceName(username, serviceName: NSString; error: PNSInteger): NSString; cdecl;
[MethodName('storeUsername:andPassword:forServiceName:updateExisting:')]
function storeUsernameAndPasswordForServiceName(username, password, serviceName: NSString; updateExisting: Boolean): NSInteger; cdecl;
[MethodName('deleteItemForUsername:andServiceName:')]
function deleteItemForUsernameAndServiceName(username, serviceName: NSString): NSInteger; cdecl;
end;
TKeychainHelper = class(TOCGenericImport<KeyChainHelperClass, KeyChainHelper>);
{$IFDEF CPUARM}
{$O-}
function KeychainHelper_FakeLoader : KeychainHelper; cdecl; external 'libKeychainHelper_iOS.a' name 'OBJC_CLASS_$_KeychainHelper';
{$O+}
{$ENDIF}
function StoreItemToKeychain(const AServiceName, AKey, AValue: string;
const bOverride: Boolean=True): Boolean;
var
ARet: NSInteger;
begin
ARet := TKeychainHelper.OCClass.storeUsernameAndPasswordForServiceName(
StrToNSStr(AKey), StrToNSStr(AValue), StrToNSStr(AServiceName), bOverride);
Result := ARet>=0;
end;
function GetItemValueFromKeychain(const AServiceName, AKey: string): string;
var
AValue: NSString;
AError: NSInteger;
begin
Result := '';
AError := 0;
AValue := TKeychainHelper.OCClass.getPasswordForUsernameAndServiceName(
StrToNSStr(AKey), StrToNSStr(AServiceName), PNSInteger(@AError));
if AError=1 then
Result := NSStrToStr(AValue);
end;
function DeleteItemFromKeychain(const AServiceName, AKey: string): Boolean;
begin
Result := TKeychainHelper.OCClass.deleteItemForUsernameAndServiceName(
StrToNSStr(AKey), StrToNSStr(AServiceName))>=0;
end;
end.
1、Firemonkey引用iOS静态库*.a文件
无导出类型的静态库使用方法参见:这里,这里对有导出类型的静态库附加两点:
1)*.a文件只需放在工程能识别的目录下即可,不需也不能设置Deloyment到设备上,否则前期调试都没问题,发布到appstore会检测通不过(被坑过);
2)由于IDE更新很快,网上有些关于Wrapper静态库的示例提供的方式都已经不适用了。关于XXX_FakeLoader函数只需如上面代码所示申明一下即可,无需{$link XXX.a},也不需在单元初始段中调用XXX_FakeLoader。
2、如果按照网上XCode开发静态库教程编译出来的静态库在Delphi下编译发布版时报如下类似错误,是因为检测到静态库文件含有调试信息。(此问题高版本已经有解决选项)
在XCode的工程中如下设置,重新编译一个Release版本即可。
3、以上静态库使用方法目前只对iOS设备目标平台有效,对模拟器还没有找到使用方式,如有会的请不吝赐教。