前言:
1 .為了統(tǒng)計和檢測應(yīng)用的使用數(shù)據(jù)落萎,幾乎每家公司都有獲取唯一標(biāo)識的業(yè)務(wù)需求,在iOS5以前獲取唯一標(biāo)識翩肌,可以獲取到系統(tǒng)提供的方法UDID(Unique Device Identifier)模暗,后來被出于用戶隱私的考慮被Apple官方禁止掉了。于是念祭,大家開始在iOS6中使用 MAC 地址(Medium/Media Access Control) ,后來又被Apple官方在iOS7中禁止掉了碍侦。蘋果及其國外的IT公司都會比較注重用戶隱私粱坤,所以今后一但有比較靠譜的獲取唯一標(biāo)示的方法放出,蘋果肯定會封堵瓷产。
2.** ** 在非越獄的手機上獲取某個硬件信息生成唯一標(biāo)識站玄,第一只能找到蘋果的漏洞,第二就是調(diào)用一些私有接口濒旦,顯然這兩條路都比較艱難株旷,并不可持續(xù)發(fā)展,所以網(wǎng)上大部分的唯一標(biāo)識都是從操作系統(tǒng)層面獲取的尔邓,在重置手機系統(tǒng)的時候都會被清除晾剖,在系統(tǒng)升級、卸載重裝梯嗽、備份恢復(fù)都可以保留齿尽,現(xiàn)在本人尚未發(fā)現(xiàn)可以使用嚴(yán)格意義上的唯一標(biāo)識。接下來我想跟大家探討的是如何通過“合法”的手段來盡量拿到不會輕易發(fā)生變化的“唯一標(biāo)識”灯节。
3.** **在2013年3月21日蘋果已經(jīng)通知開發(fā)者循头,從2013年5月1日起绵估,訪問UIDID的應(yīng)用將不再能通過審核,替代的方案是開發(fā)者應(yīng)該使用“在iOS 6中介紹的Vendor或Advertising標(biāo)示符”卡骂。
4.MAC地址不能再用來設(shè)別設(shè)備
** **還有一個生成iOS設(shè)備唯一標(biāo)示符的方法是使用iOS設(shè)備的Media Access Control(MAC)地址国裳。一個MAC地址是一個唯一的號碼,它是物理網(wǎng)絡(luò)層級方面分配給網(wǎng)絡(luò)適配器的全跨。這個地址蘋果還有其他的名字躏救,比如說是硬件地址(Hardware Address)或是Wifi地址,都是指同樣的東西螟蒸。
有很多工程和框架都使用這個方法來生成唯一的設(shè)備ID盒使。比如說ODIN。然而七嫌,蘋果并不希望有人通過MAC地址來分辨用戶少办,所以如果你在iOS7系統(tǒng)上查詢MAC地址,它現(xiàn)在只會返回02:00:00:00:00:00诵原。
5 ** ** 講真蘋果這傲嬌的小脾氣什么時候能能改改,不過這樣 對于用戶隱私的保護,確實起到很大作用,而且蘋果也沒有把路堵死,現(xiàn)在蘋果明確的表明你應(yīng)該使用-[UIDevice identifierForVendor]或是-[ASIdentifierManager advertisingIdentifier]來作為你框架和應(yīng)用的唯一標(biāo)示符英妓。坦白的來說,應(yīng)對這些變化也不是那么的難绍赛,見以下代碼片段:
:
NSString *identifierForVendor = [[UIDevice currentDevice].identifierForVendor UUIDString];
NSString *identifierForAdvertising = [[ASIdentifierManager sharedManager].advertisingIdentifier UUIDString];
但是這樣用法有個缺點,就是程序每次被刪除以后,獲取到的都是新的uuid,并不能實現(xiàn)每個手機的唯一性.
所以下面要講的就是這個問題的解決辦法.
** **對于這個問題有一個可行的辦法,就是把獲取到的uuid保存在鑰匙串里面,這樣即使程序刪除重裝,獲取到的uuid一直是同一個,實現(xiàn)了手機的唯一標(biāo)識的作用.
保存鑰匙串 我們需要用到keychain,除此之外蔓纠,Code Signing Entitlements的創(chuàng)建方法也不夠嚴(yán)謹(jǐn)。下面教大家一種方法,簡單快速.
1.新建一個工程吗蚌,看一下自己的Bundle Id.這個Bundle Id 要和你用真機測試時的證書上面的Bundle Id相匹配腿倚。
2.Target - Capabilities - Keychain Sharing - ON
這步主要目的是打開Keychain Sharing,將它由灰色狀態(tài)的OFF改為藍色狀態(tài)的ON。
左側(cè)的目錄會自動生成Entitlements文件蚯妇,不需要自己創(chuàng)建了敷燎。
也就是說,Bundle Identifier箩言、Keychain Sharing的Keychain Groups硬贯、Entitlements文件的Keychain Access Groups的第一個元素,它們要保持上圖所示的一致性陨收。
設(shè)置好了以后可以運行下程序饭豹,沒問題可以進行下一步。
.傳說中的uuid類和keychain類來啦
既然蘋果的keychain方法會崩潰而且有些復(fù)雜务漩,我們只保存一個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"];
//首次執(zhí)行該方法時拄衰,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里面執(zhí)行如下代碼
#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,身邊還沒相關(guān)越獄設(shè)備,希望大家可以自行測試一下.并告知樓主.聯(lián)系方式 : QQ :576484150
參考地址:
:http://blog.sina.com.cn/s/blog_5971cdd00102vqgy.html
:http://blog.csdn.net/chy555chy/article/details/51628079
自己寫了一個demo,已經(jīng)放在gitHub上了,有興趣的同學(xué)可以下載下來瞅瞅.
demo 地址 :https://github.com/chengkunlun/getUUID