前言:
1 .為了統計和檢測應用的使用數據,幾乎每家公司都有獲取唯一標識的業務需求,在iOS5以前獲取唯一標識,可以獲取到系統提供的方法UDID(Unique Device Identifier),后來被出于用戶隱私的考慮被Apple官方禁止掉了。于是,大家開始在iOS6中使用 MAC 地址(Medium/Media Access Control) ,后來又被Apple官方在iOS7中禁止掉了。蘋果及其國外的IT公司都會比較注重用戶隱私,所以今后一但有比較靠譜的獲取唯一標示的方法放出,蘋果肯定會封堵。
2.** ** 在非越獄的手機上獲取某個硬件信息生成唯一標識,第一只能找到蘋果的漏洞,第二就是調用一些私有接口,顯然這兩條路都比較艱難,并不可持續發展,所以網上大部分的唯一標識都是從操作系統層面獲取的,在重置手機系統的時候都會被清除,在系統升級、卸載重裝、備份恢復都可以保留,現在本人尚未發現可以使用嚴格意義上的唯一標識。接下來我想跟大家探討的是如何通過“合法”的手段來盡量拿到不會輕易發生變化的“唯一標識”。
3.** **在2013年3月21日蘋果已經通知開發者,從2013年5月1日起,訪問UIDID的應用將不再能通過審核,替代的方案是開發者應該使用“在iOS 6中介紹的Vendor或Advertising標示符”。
4.MAC地址不能再用來設別設備
** **還有一個生成iOS設備唯一標示符的方法是使用iOS設備的Media Access Control(MAC)地址。一個MAC地址是一個唯一的號碼,它是物理網絡層級方面分配給網絡適配器的。這個地址蘋果還有其他的名字,比如說是硬件地址(Hardware Address)或是Wifi地址,都是指同樣的東西。
有很多工程和框架都使用這個方法來生成唯一的設備ID。比如說ODIN。然而,蘋果并不希望有人通過MAC地址來分辨用戶,所以如果你在iOS7系統上查詢MAC地址,它現在只會返回02:00:00:00:00:00。
5 ** ** 講真蘋果這傲嬌的小脾氣什么時候能能改改,不過這樣 對于用戶隱私的保護,確實起到很大作用,而且蘋果也沒有把路堵死,現在蘋果明確的表明你應該使用-[UIDevice identifierForVendor]或是-[ASIdentifierManager advertisingIdentifier]來作為你框架和應用的唯一標示符。坦白的來說,應對這些變化也不是那么的難,見以下代碼片段:
:
NSString *identifierForVendor = [[UIDevice currentDevice].identifierForVendor UUIDString];
NSString *identifierForAdvertising = [[ASIdentifierManager sharedManager].advertisingIdentifier UUIDString];
但是這樣用法有個缺點,就是程序每次被刪除以后,獲取到的都是新的uuid,并不能實現每個手機的唯一性.
所以下面要講的就是這個問題的解決辦法.
** **對于這個問題有一個可行的辦法,就是把獲取到的uuid保存在鑰匙串里面,這樣即使程序刪除重裝,獲取到的uuid一直是同一個,實現了手機的唯一標識的作用.
保存鑰匙串 我們需要用到keychain,除此之外,Code Signing Entitlements的創建方法也不夠嚴謹。下面教大家一種方法,簡單快速.
1.新建一個工程,看一下自己的Bundle Id.這個Bundle Id 要和你用真機測試時的證書上面的Bundle Id相匹配。
2.Target - Capabilities - Keychain Sharing - ON
這步主要目的是打開Keychain Sharing,將它由灰色狀態的OFF改為藍色狀態的ON。
左側的目錄會自動生成Entitlements文件,不需要自己創建了。
也就是說,Bundle Identifier、Keychain Sharing的Keychain Groups、Entitlements文件的Keychain Access Groups的第一個元素,它們要保持上圖所示的一致性。
設置好了以后可以運行下程序,沒問題可以進行下一步。
.傳說中的uuid類和keychain類來啦
既然蘋果的keychain方法會崩潰而且有些復雜,我們只保存一個uuid的話可以用下面的簡單方法:
KeyChainStore.h
#import <Foundation/Foundation.h>
@interface KeyChainStore : NSObject
+ (void)save:(NSString *)service data:(id)data;
+ (id)load:(NSString *)service;
+ (void)deleteKeyData:(NSString *)service;
@end
KeyChainStore.m
//
// KeyChainStore.m
// getUUID
//
// Created by ckl@pmm on 16/9/18.
// Copyright ? 2016年 CKLPronetway. All rights reserved.
//
#import "KeyChainStore.h"
@implementation KeyChainStore
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
return [NSMutableDictionary dictionaryWithObjectsAndKeys:
(id)kSecClassGenericPassword,(id)kSecClass,
service, (id)kSecAttrService,
service, (id)kSecAttrAccount,
(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,
nil];
}
+ (void)save:(NSString *)service data:(id)data {
//Get search dictionary
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Delete old item before add new item
SecItemDelete((CFDictionaryRef)keychainQuery);
//Add new object to search dictionary(Attention:the data format)
[keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
//Add item to keychain with the search dictionary
SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
+ (id)load:(NSString *)service {
id ret = nil;
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
//Configure the search setting
//Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue
[keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
[keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
CFDataRef keyData = NULL;
if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
@try {
ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
} @catch (NSException *e) {
NSLog(@"Unarchive of %@ failed: %@", service, e);
} @finally {
}
}
if (keyData)
CFRelease(keyData);
return ret;
}
+ (void)deleteKeyData:(NSString *)service {
NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
SecItemDelete((CFDictionaryRef)keychainQuery);
}
@end
getUUID.h
#import <Foundation/Foundation.h>
@interface getUUID : NSObject
+(NSString *)getUUID;
@end
getUUID.m
//
// getUUID.m
// getUUID
//
// Created by ckl@pmm on 16/9/18.
// Copyright ? 2016年 CKLPronetway. All rights reserved.
//
#import "getUUID.h"
#import "KeyChainStore.h"
#define KEY_USERNAME_PASSWORD @"com.company.app.usernamepassword"
#define KEY_USERNAME @"com.company.app.username"
#define KEY_PASSWORD @"com.company.app.password"
@implementation getUUID
+(NSString *)getUUID
{
NSString * strUUID = (NSString *)[KeyChainStore load:@"com.company.app.usernamepassword"];
//首次執行該方法時,uuid為空
if ([strUUID isEqualToString:@""] || !strUUID)
{
//生成一個uuid的方法
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
strUUID = (NSString *)CFBridgingRelease(CFUUIDCreateString (kCFAllocatorDefault,uuidRef));
//將該uuid保存到keychain
[KeyChainStore save:KEY_USERNAME_PASSWORD data:strUUID];
}
return strUUID;
}
@end
在Viewcontroller里面執行如下代碼
#import "ViewController.h"
#import "getUUID.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@" uuid is ---> %@",[getUUID getUUID]);
// Do any additional setup after loading the view, typically from a nib.
}
打印出來類似于以下的長串字符:
把程序卸載掉然后重新運行一次,獲取到的還是上次保存的uuid.
不知道手機越獄以后,會不會改變,因為樓主手機版本是最新的9.3.5,身邊還沒相關越獄設備,希望大家可以自行測試一下.并告知樓主.聯系方式 : QQ :576484150
參考地址:
:http://blog.sina.com.cn/s/blog_5971cdd00102vqgy.html
:http://blog.csdn.net/chy555chy/article/details/51628079
自己寫了一個demo,已經放在gitHub上了,有興趣的同學可以下載下來瞅瞅.
demo 地址 :https://github.com/chengkunlun/getUUID