oc已全部翻譯完成視頻總4個(gè)視頻免費(fèi)下載
公司主營(yíng)業(yè)務(wù):網(wǎng)站建設(shè)、成都做網(wǎng)站、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。成都創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。成都創(chuàng)新互聯(lián)推出錫林郭勒盟免費(fèi)做網(wǎng)站回饋大家。
鏈接:
提取碼:64s9
oc已全部翻譯完成視頻 總4個(gè)視頻|OC教程04:速建+講解.mp4|OC教程03:如何給機(jī)器人添加紋理與著色.mp4|OC教程02:場(chǎng)景管理.mp4|OC教程01:涂鴉的投射原理與應(yīng)用.mp4
Key-Value Coding 俗稱"鍵值編碼",蘋(píng)果官方簡(jiǎn)稱這個(gè)模式為KVC編碼模式,也就是說(shuō)可以通過(guò)一個(gè)Key去訪問(wèn)某一個(gè)屬性,或者給對(duì)象去賦值,而不需要去明確存取方法,這樣就可以動(dòng)態(tài)的訪問(wèn)和修改對(duì)象的屬性,而不是在編譯的時(shí)候去確定,這也是iOS開(kāi)發(fā)中的一大便利,其實(shí)有很多的框架和功能是用KVC去實(shí)現(xiàn)的,這個(gè)技術(shù)存在已經(jīng)很長(zhǎng)時(shí)間了,在網(wǎng)上也有很多相關(guān)的教程去教童鞋們?nèi)绾稳ナ褂肒VC,在這里,我們就只是簡(jiǎn)單的介紹一下KVC的底層實(shí)現(xiàn)和使用方法。
從蘋(píng)果官方對(duì)KVC的解釋來(lái)看,其實(shí)KVC在Fundation框架中占有很高的地位,諸如Core-Data之類的框架都使用到了KVC技術(shù),我們?cè)陂_(kāi)發(fā)中可能常見(jiàn)的API有:
NSKeyValueCoding類別中還有其他的一些比較重要方法,如下:
說(shuō)起KVC的執(zhí)行流程,我們有很多初級(jí)工程師都不大清楚,只知道KVC是如何使用的,而不知道KVC是怎么Key的尋找策略的。下圖我們借鑒了MJ老師的兩幅PPT來(lái)解釋
上圖我們可以看到
簡(jiǎn)單說(shuō)KVC機(jī)制在設(shè)值的時(shí)候會(huì)按照 setKey: 》_setKey 》_key 》_isKey 》key 》 isKey 順序搜索成員并進(jìn)行賦值操作,但是如果開(kāi)發(fā)者重寫(xiě)了類方法+ (BOOL)accessInstanceVarialbesDirectly并且讓其返回NO,這樣在搜索的時(shí)候會(huì)直接從步驟 1跳轉(zhuǎn)到步驟5 。
舉一個(gè)例子,我們先創(chuàng)建一個(gè)Person類
然后用KVC賦值
最終在控制臺(tái)打印的結(jié)果是
當(dāng)調(diào)用valueForKey:方法時(shí),KVC對(duì)key的搜索順序有點(diǎn)不同于setValue:forKey:方法,大致步驟如下:
如
最終打印為
上述可以看出,當(dāng)Key查找不到值的時(shí)候會(huì)走 valueForUndefinedKey 方法中拋出異常
類的成員變量有可能是自定義類或其他復(fù)雜數(shù)據(jù)類型,對(duì)這種成員變量可以先用KVC獲取該屬性,然后再用KVC來(lái)獲取這個(gè)自定義類的屬性,這樣一層層去獲取,但這樣比較繁瑣。對(duì)此KVC提供一個(gè)解決方案,就是鍵路徑keyPath,顧名思義就是按照路徑尋找key。主要有兩個(gè)以下兩個(gè)方法:
在上述Person中我們創(chuàng)建一個(gè)Cat類
在Cat類中我們創(chuàng)建一個(gè)屬性 name
我們?nèi)绻枰肒VC對(duì)Person對(duì)象中Cat對(duì)象賦值的話,我們就必須用到KeyPath了
KVC對(duì)于keyPath的搜索機(jī)制第一步就是分離key,用小數(shù)點(diǎn).來(lái)分割key,然后再像普通key一樣按照上面介紹的順序搜索。
使用KVC過(guò)程中最常見(jiàn)的異常就是不小心使用了錯(cuò)誤的key,或者在設(shè)值中不小心傳了nil的值,KVC有專門(mén)的方法處理這些異常。
該方法返回一個(gè)可變有序數(shù)組。對(duì)于無(wú)序的容器,可以用以下方法:
該方法返回一個(gè)可變的無(wú)序集合。同時(shí)他們也有對(duì)應(yīng)的keyPath版本:
當(dāng)NSDictionary對(duì)象使用KVC時(shí),valueForKey:的表現(xiàn)行為和objectForKey:一樣,使用valueForKeyPath:可訪問(wèn)多層嵌套的字典會(huì)方便點(diǎn),在KVC中有兩個(gè)關(guān)于NSDictionary的方法:
當(dāng)開(kāi)發(fā)者需要驗(yàn)證能不能用KVC設(shè)定某個(gè)值時(shí),就需要在進(jìn)行KVC賦值前驗(yàn)證值value的有效性,API文檔里面提供下面的方法進(jìn)行判斷值的有效性。
該方法的工作原理:先找一下你的類中是否實(shí)現(xiàn)了方法-(BOOL)validateKey:error:,如果實(shí)現(xiàn)了就會(huì)根據(jù)實(shí)現(xiàn)方法里面的自定義邏輯返回NO或者YES,如果沒(méi)有實(shí)現(xiàn)這個(gè)方法,則系統(tǒng)默認(rèn)返回就是YES。
輸出結(jié)果為
這里首先調(diào)用方法 [self validateValue:value forKey:key error:error] ;,這里,由于我實(shí)現(xiàn)了方法- (BOOL)validatePersonName:(id *)value error:(out NSError * _Nullable __autoreleasing *)outError ,所以就在這里進(jìn)行值value有效性的判斷,這里 [name isEqualToString:@"小明"] 我就給返回YES,否則就返回NO。
KVC在iOS開(kāi)發(fā)中非常的靈活,提供了開(kāi)發(fā)者更多的賦值和取值操作的選擇,它的有點(diǎn)明顯,缺點(diǎn)也有,如果key只寫(xiě)錯(cuò),編寫(xiě)的時(shí)候不會(huì)報(bào)錯(cuò),但是運(yùn)行的時(shí)候會(huì)報(bào)錯(cuò),在實(shí)際開(kāi)發(fā)中需要開(kāi)發(fā)者時(shí)刻小心自己輸入的鍵值,也時(shí)刻提醒著開(kāi)發(fā)者一旦使用KVC就要做容錯(cuò)處理。
iOS開(kāi)發(fā)就是為裝有iOS系統(tǒng)的設(shè)備完成應(yīng)用軟件或游戲軟件的開(kāi)發(fā),ios開(kāi)發(fā)的設(shè)計(jì)模式有代理模式、觀察者模式、MVC模式、單例模式、策略模式和工廠模式。
先來(lái)看看官方的文檔,是這樣寫(xiě)的:In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.翻譯過(guò)來(lái)是:在多線程應(yīng)用中,Notification在哪個(gè)線程中post,就在哪個(gè)線程中被轉(zhuǎn)發(fā),而不一定是在注冊(cè)觀察者的那個(gè)線程中。也就是說(shuō),Notification的發(fā)送與接收處理都是在同一個(gè)線程中。為了說(shuō)明這一點(diǎn),我們先來(lái)看一個(gè)示例:代碼清單1:Notification的發(fā)送與處理@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];NSLog(@"current thread = %@", [NSThread currentThread]);[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil];});}- (void)handleNotification:(NSNotification *)notification{NSLog(@"current thread = %@", [NSThread currentThread]);NSLog(@"test notification");}@end其輸出結(jié)果如下:2015-03-11 22:05:12.856 test[865:45102] current thread = {number = 1, name = main}2015-03-11 22:05:12.857 test[865:45174] current thread = {number = 2, name = (null)}2015-03-11 22:05:12.857 test[865:45174] test notification可以看到,雖然我們?cè)谥骶€程中注冊(cè)了通知的觀察者,但在全局隊(duì)列中post的Notification,并不是在主線程處理的。所以,這時(shí)候就需要注意,如果我們想在回調(diào)中處理與UI相關(guān)的操作,需要確保是在主線程中執(zhí)行回調(diào)。這時(shí),就有一個(gè)問(wèn)題了,如果我們的Notification是在二級(jí)線程中post的,如何能在主線程中對(duì)這個(gè)Notification進(jìn)行處理呢?或者換個(gè)提法,如果我們希望一個(gè)Notification的post線程與轉(zhuǎn)發(fā)線程不是同一個(gè)線程,應(yīng)該怎么辦呢?我們看看官方文檔是怎么說(shuō)的:For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.這里講到了“重定向”,就是我們?cè)贜otification所在的默認(rèn)線程中捕獲這些分發(fā)的通知,然后將其重定向到指定的線程中。一種重定向的實(shí)現(xiàn)思路是自定義一個(gè)通知隊(duì)列(注意,不是NSNotificationQueue對(duì)象,而是一個(gè)數(shù)組),讓這個(gè)隊(duì)列去維護(hù)那些我們需要重定向的Notification。我們?nèi)匀皇窍衿匠R粯尤プ?cè)一個(gè)通知的觀察者,當(dāng)Notification來(lái)了時(shí),先看看post這個(gè)Notification的線程是不是我們所期望的線程,如果不是,則將這個(gè)Notification存儲(chǔ)到我們的隊(duì)列中,并發(fā)送一個(gè)信號(hào)(signal)到期望的線程中,來(lái)告訴這個(gè)線程需要處理一個(gè)Notification。指定的線程在收到信號(hào)后,將Notification從隊(duì)列中移除,并進(jìn)行處理。官方文檔已經(jīng)給出了示例代碼,在此借用一下,以測(cè)試實(shí)際結(jié)果:代碼清單2:在不同線程中post和轉(zhuǎn)發(fā)一個(gè)Notification@interface ViewController ()@property (nonatomic) NSMutableArray *notifications; // 通知隊(duì)列@property (nonatomic) NSThread *notificationThread; // 期望線程@property (nonatomic) NSLock *notificationLock; // 用于對(duì)通知隊(duì)列加鎖的鎖對(duì)象,避免線程沖突@property (nonatomic) NSMachPort *notificationPort; // 用于向期望線程發(fā)送信號(hào)的通信端口@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];NSLog(@"current thread = %@", [NSThread currentThread]);// 初始化self.notifications = [[NSMutableArray alloc] init];self.notificationLock = [[NSLock alloc] init];self.notificationThread = [NSThread currentThread];self.notificationPort = [[NSMachPort alloc] init];self.notificationPort.delegate = self;// 往當(dāng)前線程的run loop添加端口源// 當(dāng)Mach消息到達(dá)而接收線程的run loop沒(méi)有運(yùn)行時(shí),則內(nèi)核會(huì)保存這條消息,直到下一次進(jìn)入run loop[[NSRunLoop currentRunLoop] addPort:self.notificationPortforMode:(__bridge NSString *)kCFRunLoopCommonModes];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"TestNotification" object:nil];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil];});}- (void)handleMachMessage:(void *)msg {[self.notificationLock lock];while ([self.notifications count]) {NSNotification *notification = [self.notifications objectAtIndex:0];[self.notifications removeObjectAtIndex:0];[self.notificationLock unlock];[self processNotification:notification];[self.notificationLock lock];};[self.notificationLock unlock];}- (void)processNotification:(NSNotification *)notification {if ([NSThread currentThread] != _notificationThread) {// Forward the notification to the correct thread.[self.notificationLock lock];[self.notifications addObject:notification];[self.notificationLock unlock];[self.notificationPort sendBeforeDate:[NSDate date]components:nilfrom:nilreserved:0];}else {// Process the notification here;NSLog(@"current thread = %@", [NSThread currentThread]);NSLog(@"process notification");}}@end運(yùn)行后,其輸出如下:2015-03-11 23:38:31.637 test[1474:92483] current thread = {number = 1, name = main}2015-03-11 23:38:31.663 test[1474:92483] current thread = {number = 1, name = main}2015-03-11 23:38:31.663 test[1474:92483] process notification可以看到,我們?cè)谌謉ispatch隊(duì)列中拋出的Notification,如愿地在主線程中接收到了。這種實(shí)現(xiàn)方式的具體解析及其局限性大家可以參考官方文檔Delivering Notifications To Particular Threads,在此不多做解釋。當(dāng)然,更好的方法可能是我們自己去子類化一個(gè)NSNotificationCenter,或者單獨(dú)寫(xiě)一個(gè)類來(lái)處理這種轉(zhuǎn)發(fā)。NSNotificationCenter的線程安全性蘋(píng)果之所以采取通知中心在同一個(gè)線程中post和轉(zhuǎn)發(fā)同一消息這一策略,應(yīng)該是出于線程安全的角度來(lái)考量的。官方文檔告訴我們,NSNotificationCenter是一個(gè)線程安全類,我們可以在多線程環(huán)境下使用同一個(gè)NSNotificationCenter對(duì)象而不需要加鎖。原文在Threading Programming Guide中,具體如下:The following classes and functions are generally considered to be thread-safe. You can use the same instance from multiple threads without first acquiring a lock.NSArray...NSNotificationNSNotificationCenter我們可以在任何線程中添加/刪除通知的觀察者,也可以在任何線程中post一個(gè)通知。NSNotificationCenter在線程安全性方面已經(jīng)做了不少工作了,那是否意味著我們可以高枕無(wú)憂了呢?再回過(guò)頭來(lái)看看第一個(gè)例子,我們稍微改造一下,一點(diǎn)一點(diǎn)來(lái):代碼清單3:NSNotificationCenter的通用模式@interface Observer : NSObject@end@implementation Observer- (instancetype)init{self = [super init];if (self){_poster = [[Poster alloc] init];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil]}return self;}- (void)handleNotification:(NSNotification *)notification{NSLog(@"handle notification ");}- (void)dealloc{[[NSNotificationCenter defaultCenter] removeObserver:self];}@end// 其它地方[[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];上面的代碼就是我們通常所做的事情:添加一個(gè)通知監(jiān)聽(tīng)者,定義一個(gè)回調(diào),并在所屬對(duì)象釋放時(shí)移除監(jiān)聽(tīng)者;然后在程序的某個(gè)地方post一個(gè)通知。簡(jiǎn)單明了,如果這一切都是發(fā)生在一個(gè)線程里面,或者至少dealloc方法是在-postNotificationName:的線程中運(yùn)行的(注意:NSNotification的post和轉(zhuǎn)發(fā)是同步的),那么都OK,沒(méi)有線程安全問(wèn)題。但如果dealloc方法和-postNotificationName:方法不在同一個(gè)線程中運(yùn)行時(shí),會(huì)出現(xiàn)什么問(wèn)題呢?我們?cè)俑脑煲幌律厦娴拇a:代碼清單4:NSNotificationCenter引發(fā)的線程安全問(wèn)題#pragma mark - Poster@interface Poster : NSObject@end@implementation Poster- (instancetype)init{self = [super init];if (self){[self performSelectorInBackground:@selector(postNotification) withObject:nil];}return self;}- (void)postNotification{[[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil];}@end#pragma mark - Observer@interface Observer : NSObject{Poster *_poster;}@property (nonatomic, assign) NSInteger i;@end@implementation Observer- (instancetype)init{self = [super init];if (self){_poster = [[Poster alloc] init];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil];}return self;}- (void)handleNotification:(NSNotification *)notification{NSLog(@"handle notification begin");sleep(1);NSLog(@"handle notification end");self.i = 10;}- (void)dealloc{[[NSNotificationCenter defaultCenter] removeObserver:self];NSLog(@"Observer dealloc");}@end#pragma mark - ViewController@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];__autoreleasing Observer *observer = [[Observer alloc] init];}@end這段代碼是在主線程添加了一個(gè)TEST_NOTIFICATION通知的監(jiān)聽(tīng)者,并在主線程中將其移除,而我們的NSNotification是在后臺(tái)線程中post的。在通知處理函數(shù)中,我們讓回調(diào)所在的線程睡眠1秒鐘,然后再去設(shè)置屬性i值。這時(shí)會(huì)發(fā)生什么呢?我們先來(lái)看看輸出結(jié)果:2015-03-14 00:31:41.286 SKTest[932:88791] handle notification begin2015-03-14 00:31:41.291 SKTest[932:88713] Observer dealloc2015-03-14 00:31:42.361 SKTest[932:88791] handle notification end(lldb)// 程序在self.i = 10處拋出了"Thread 6: EXC_BAD_ACCESS(code=EXC_I386_GPFLT)"經(jīng)典的內(nèi)存錯(cuò)誤,程序崩潰了。其實(shí)從輸出結(jié)果中,我們就可以看到到底是發(fā)生了什么事。我們簡(jiǎn)要描述一下:當(dāng)我們注冊(cè)一個(gè)觀察者是,通知中心會(huì)持有觀察者的一個(gè)弱引用,來(lái)確保觀察者是可用的。主線程調(diào)用dealloc操作會(huì)讓Observer對(duì)象的引用計(jì)數(shù)減為0,這時(shí)對(duì)象會(huì)被釋放掉。后臺(tái)線程發(fā)送一個(gè)通知,如果此時(shí)Observer還未被釋放,則會(huì)向其轉(zhuǎn)發(fā)消息,并執(zhí)行回調(diào)方法。而如果在回調(diào)執(zhí)行的過(guò)程中對(duì)象被釋放了,就會(huì)出現(xiàn)上面的問(wèn)題。當(dāng)然,上面這個(gè)例子是故意而為之,但不排除在實(shí)際編碼中會(huì)遇到類似的問(wèn)題。雖然NSNotificationCenter是線程安全的,但并不意味著我們?cè)谑褂脮r(shí)就可以保證線程安全的,如果稍不注意,還是會(huì)出現(xiàn)線程問(wèn)題。那我們?cè)撛趺醋瞿?這里有一些好的建議:盡量在一個(gè)線程中處理通知相關(guān)的操作,大部分情況下,這樣做都能確保通知的正常工作。不過(guò),我們無(wú)法確定到底會(huì)在哪個(gè)線程中調(diào)用dealloc方法,所以這一點(diǎn)還是比較困難。注冊(cè)監(jiān)聽(tīng)都時(shí),使用基于block的API。這樣我們?cè)赽lock還要繼續(xù)調(diào)用self的屬性或方法,就可以通過(guò)weak-strong的方式來(lái)處理。具體大家可以改造下上面的代碼試試是什么效果。使用帶有安全生命周期的對(duì)象,這一點(diǎn)對(duì)象單例對(duì)象來(lái)說(shuō)再合適不過(guò)了,在應(yīng)用的整個(gè)生命周期都不會(huì)被釋放。使用代理。
加上Android生態(tài)中紛繁復(fù)雜的各種奇葩尺寸,現(xiàn)在APP設(shè)計(jì)開(kāi)發(fā)必須考慮適配大、中、小三種屏幕。所以如何做到交付一套設(shè)計(jì)稿解決適配大中小三屏的問(wèn)題?設(shè)計(jì)和開(kāi)發(fā)之間采用什么協(xié)作模式?一個(gè)基本思路是:
1、選擇一種尺寸作為設(shè)計(jì)和開(kāi)發(fā)基準(zhǔn);
2、定義一套適配規(guī)則,自動(dòng)適配剩下兩種尺寸;
3、特殊適配效果給出設(shè)計(jì)效果。
手機(jī)淘寶的iPhone 6/iPhone 6 Plus適配版本即將提交App store審核。先曬一下我們采用的協(xié)作模式,再慢慢說(shuō)明原委。
第一步,視覺(jué)設(shè)計(jì)階段,設(shè)計(jì)師按寬度750px(iPhone 6)做設(shè)計(jì)稿,除圖片外所有設(shè)計(jì)元素用矢量路徑來(lái)做。設(shè)計(jì)定稿后在750px的設(shè)計(jì)稿上做標(biāo)注,輸出標(biāo)注圖。同時(shí)等比放大1.5倍生成寬度1125px的設(shè)計(jì)稿,在1125px的稿子里切圖。
第二步,輸出兩個(gè)交付物給開(kāi)發(fā)工程師:一個(gè)是程序用到的@3x切圖資源,另一個(gè)是寬度750px的設(shè)計(jì)標(biāo)注圖。
第三步,開(kāi)發(fā)工程師拿到750px標(biāo)注圖和@3x切圖資源,完成iPhone 6(375pt)的界面開(kāi)發(fā)。此階段不能用固定寬度的方式開(kāi)發(fā)界面,得用自動(dòng)布局(auto layout),方便后續(xù)適配到其它尺寸。
第四步,適配調(diào)試階段,基于iPhone 6的界面效果,分別向上向下調(diào)試iPhone 6 plus(414pt)和iPhone 5S及以下(320pt)的界面效果。由此完成大中小三屏適配。
為什么選擇iPhone 6作為基準(zhǔn)尺寸?
當(dāng)面對(duì)大中小三種屏幕需要適配的時(shí)候,很容易想到先做好一種屏幕,再去適配剩下兩種屏幕。第一個(gè)決定是到底以哪種屏幕作為設(shè)計(jì)和開(kāi)發(fā)的基準(zhǔn)尺寸。我們選擇中間尺寸的iPhone 6(750px/375pt)作為基準(zhǔn),基于幾個(gè)原因:
1、從中間尺寸向上和向下適配的時(shí)候界面調(diào)整的幅度最小。375pt下的設(shè)計(jì)效果適配到414pt和320pt偏差不會(huì)太大。假設(shè)以414pt為基準(zhǔn)做出很優(yōu)雅的設(shè)計(jì),到320pt可能元素之間比例就不是那么回事了,比如圖片和文字之間視覺(jué)比例可能失調(diào)。
2、iPhone 6 plus有兩種顯示模式,標(biāo)準(zhǔn)模式分辨率為1242x2208,放大模式分辨率為1125x2001(即iPhone 6的1.5倍)??梢?jiàn)官方系統(tǒng)里iPhone 6和iPhone 6 plus分辨率之間就存在1.5倍的倍率關(guān)系。很多情況下這兩種尺寸可以用1.5倍直接等比適配。
3、1242x2208這個(gè)奇葩的數(shù)值是蘋(píng)果官方都不愿意公開(kāi)宣傳的一個(gè)分辨率,不便于記憶和計(jì)算柵格。640x1136雖然是廣泛應(yīng)用的一個(gè)分辨率,但是大屏?xí)r代依然以小尺寸為設(shè)計(jì)基準(zhǔn)顯然不合時(shí)宜,設(shè)計(jì)師會(huì)停留在小屏的視角做設(shè)計(jì)。
所以,iPhone6的750x1334是最適合基準(zhǔn)尺寸。
只交付一套設(shè)計(jì)稿,默認(rèn)用什么規(guī)則來(lái)適配?
前文提到適配策略是先選擇iPhone 6作為基準(zhǔn)設(shè)計(jì)尺寸,然后通過(guò)一套適配規(guī)則自動(dòng)適配到另外兩種尺寸。這套適配規(guī)則總結(jié)起來(lái)就一句話:文字流式,控件彈性,圖片等比縮放。
控件彈性指的是,navigation、cell、bar等適配過(guò)程中垂直方向上高度不變;水平方向?qū)挾茸兓瘯r(shí),通過(guò)調(diào)整元素間距或元素右對(duì)齊的方式實(shí)現(xiàn)自適應(yīng)。這樣屏幕越大,在垂直方向上可以顯示更多內(nèi)容,發(fā)揮大屏幕的優(yōu)勢(shì)。
按照上述默認(rèn)適配規(guī)則,大中小三種屏幕顯示效果均相同。有時(shí)候想在大屏幕顯示更多內(nèi)容,需要設(shè)計(jì)出特殊適配效果。比如App store首頁(yè)焦點(diǎn)圖,從iPhone 6適配到iPhone 6 plus時(shí)焦點(diǎn)圖尺寸和排版做了特殊處理。底下應(yīng)用列表也從一排3+個(gè)變成一排4+個(gè),真正實(shí)現(xiàn)了大屏幕顯示更多內(nèi)容的理念。這些就需要設(shè)計(jì)師給出相應(yīng)設(shè)計(jì)稿。
1.什么是藍(lán)牙4.0,藍(lán)牙其它標(biāo)準(zhǔn)又是什么?
詳細(xì)描述:低功耗藍(lán)牙(Low Energy; LE),又視為Bluetooth Smart或藍(lán)牙核心規(guī)格4.0版本。其特點(diǎn)具備節(jié)能、便于采用,是藍(lán)牙技術(shù)專為物聯(lián)網(wǎng)(Internet of Things; IOT)開(kāi)發(fā)的技術(shù)版本。所以它最主要的特點(diǎn)是低功耗,普及率高?,F(xiàn)在所說(shuō)的藍(lán)牙設(shè)備,大部分都是在說(shuō)4.0設(shè)備,ble也特指4.0設(shè)備。 在4.0之前重要的版本有 2.1版本-基本速率/增強(qiáng)數(shù)據(jù)率(BR/EDR) 和 3.0 高速藍(lán)牙 版本,這些統(tǒng)稱為經(jīng)典藍(lán)牙。4.0還有4.1和4.2的小版本,其中4.2版本對(duì)傳輸速率做了進(jìn)一步他提升,提高了2.5倍,蘋(píng)果從iphone6開(kāi)始使用4.2,最新的藍(lán)牙標(biāo)準(zhǔn)為藍(lán)牙5.0,其中最大的特點(diǎn)連接范圍擴(kuò)大了4倍,速度又提高了2倍,無(wú)連接數(shù)據(jù)廣播能力提高了8倍,增加了藍(lán)牙組網(wǎng)的能力。
2.藍(lán)牙開(kāi)發(fā)必須知道的概念。
2.1.1 central和peripheral:
藍(lán)牙應(yīng)用開(kāi)發(fā)中,存在兩種角色,分別是central和peripheral(p?’r?f?r?l) ,中文就是中心和外設(shè)。比如手機(jī)去連接智能設(shè)備,那手機(jī)就是central,智能設(shè)備就是peripheral。大多時(shí)候都是central去連接peripheral的場(chǎng)景。
2.1.2 廣播和連接:
peripheral會(huì)發(fā)出廣播,central掃描到廣播后,可以對(duì)設(shè)備進(jìn)行連接,發(fā)出connect請(qǐng)求,peripheral接收到請(qǐng)求后,同意連接后,central和peripheral就建立了連接。
2.1.3?連接后的操作:
write,read,notify,indecate, response or not …
indecate和notify的區(qū)別就在于,indecate是一定會(huì)收到數(shù)據(jù),notify有可能會(huì)丟失數(shù)據(jù)(不會(huì)有central收到數(shù)據(jù)的回應(yīng)),write也分為response和noresponse,如果是response,那么write成功回收到peripheral的確認(rèn)消息,但是會(huì)降低寫(xiě)入的速率。
2.1.4 協(xié)議:
每個(gè)具體的智能設(shè)備,都約定了一組數(shù)據(jù)格式,這個(gè)就是數(shù)據(jù)協(xié)議,例如手環(huán)中獲取到數(shù)據(jù)0X001023,其中第2位到第5位表示步數(shù),那么就2310就是步數(shù)的16進(jìn)制的數(shù)據(jù),轉(zhuǎn)換成10進(jìn)制就是8976步,需要注意的是,設(shè)備端都是小端模式,所以取4位時(shí)候,高字節(jié)在前低字節(jié)在后。
3. iOS藍(lán)牙應(yīng)用的一般開(kāi)發(fā)流程。
4. 藍(lán)牙的數(shù)據(jù)交互。
write,read,notify,indecate, response or not … 都是容易理解的,indecate和notify對(duì)應(yīng)的是長(zhǎng)連接,建立indecate后,peripheral可以隨時(shí)往central發(fā)送數(shù)據(jù)。
indecate和notify的區(qū)別就在于,indecate是一定會(huì)收到數(shù)據(jù),notify有可能會(huì)丟失數(shù)據(jù)(不會(huì)有central收到數(shù)據(jù)的回應(yīng)),write也分為response和noresponse,如果是response,那么write成功回收到peripheral的確認(rèn)消息,但是會(huì)降低寫(xiě)入的速率。
對(duì)于一個(gè)charateristic,他的讀寫(xiě)訂閱的權(quán)限是peripheral決定的,熟悉可以被同時(shí)設(shè)置,一般會(huì)根據(jù)外設(shè)的功能來(lái)決定。
5.藍(lán)牙ota DFU。
藍(lán)牙ota,DFU(Device Firmware Update)指的是藍(lán)牙設(shè)備的固件升級(jí),其實(shí)是一整套流程,不同的藍(lán)牙芯片,ota的流程有不同之處,我這里用ti的芯片舉例。步驟為:切系統(tǒng)(bootloader mode),重啟,傳輸數(shù)據(jù),驗(yàn)證數(shù)據(jù),切系統(tǒng),重啟,完成。
其中數(shù)據(jù)傳輸也會(huì)分成很多節(jié)去發(fā)送,沒(méi)法送一段數(shù)據(jù),做一次數(shù)據(jù)校驗(yàn)。
6.ota存在的問(wèn)題。
每個(gè)智能設(shè)備的速率,功耗,存儲(chǔ)都會(huì)有很多限制,導(dǎo)致很多設(shè)備會(huì)自己去實(shí)現(xiàn)ota的功能,自定義流程和數(shù)據(jù)傳輸方式,導(dǎo)致許多設(shè)備都是有自己私有的ota模式和協(xié)議,所以在做開(kāi)發(fā)的時(shí)候,要仔細(xì)閱讀設(shè)備協(xié)議中對(duì)ota的描述。
7.如何做自動(dòng)重連。
只需要在設(shè)備斷開(kāi)連接的委托方法中,重新調(diào)用gatt.connet或者是centralManager.connet方法就可以了,無(wú)論當(dāng)時(shí)設(shè)備是否有點(diǎn),是否在周圍,當(dāng)設(shè)備再次開(kāi)會(huì)或者連接到可連接范圍內(nèi),都會(huì)自動(dòng)被連上。
8.連接失敗處理。
分兩個(gè)平臺(tái)來(lái)說(shuō),iOS端也有連接失敗的委托,但是好像幾乎不會(huì)發(fā)生這種情況,而對(duì)于同款設(shè)備,android常常會(huì)出現(xiàn)連接失敗的情況,status != BluetoothGatt.GATT_SUCCESS,android端開(kāi)發(fā)請(qǐng)不要把連接失敗和斷開(kāi)連接放在一塊處理,因?yàn)閿嚅_(kāi)連接可以直接嘗試重新連接,而連接失敗后嘗試重新連接,需要加一些延時(shí),并且需要gatt.close,清空一下?tīng)顟B(tài),否則會(huì)把gatt阻塞導(dǎo)致手機(jī)不重啟藍(lán)牙就再也無(wú)法連接任何設(shè)備的情況 。
9.后臺(tái)運(yùn)行。
iOS后來(lái)運(yùn)行,需要設(shè)備中info.Plist權(quán)限,key:Required background modes ,value: bluetooth-central(手機(jī)作為central) , bluetooth-peripheral。
10.同時(shí)連接多個(gè)設(shè)備。
使用同一個(gè)CBCentralManager,通過(guò)進(jìn)入委托的peripheral的identifier區(qū)分不同的設(shè)備,進(jìn)行不同的操作和處理。
11.掃描廣播包。
所有外設(shè),只有在發(fā)出廣播包的情況下,才能被central發(fā)現(xiàn),絕大多數(shù)情況下,外設(shè)被連接后就不會(huì)發(fā)出廣播(也有例外),很多人遇到無(wú)法找到設(shè)備的問(wèn)題,大多屬于這種情況。
12.提高藍(lán)牙連接速度。
無(wú)論是iOS,還是android,都可以通過(guò)已綁定的設(shè)備,在不開(kāi)啟掃描的情況下進(jìn)行快速連接,iOS需要的參數(shù)是peripheral的identifier,android需要mac地址。但android和iOS還是有一些區(qū)別的,比如iOS不能拿到已綁定的設(shè)備list,但是可以通過(guò)UUID去拿到peripheral的實(shí)例。而android可以拿到已綁定的設(shè)備list。android綁定過(guò)程需要手動(dòng)調(diào)用createBond的方法,而iOS在連接成功一次后會(huì)自動(dòng)綁定。 android在處理createBond時(shí),常常會(huì)應(yīng)為不同手機(jī)平臺(tái),不同設(shè)備,會(huì)產(chǎn)生兼容性的問(wèn)題,這點(diǎn)需要注意。
13.定向掃描。
在掃描時(shí)候可以傳入serviceUUID,這樣可以掃描到特定條件的設(shè)備,提高掃描的速度,排除干擾。
14.如何獲取mac地址。
而iOS出于蘋(píng)果的安全策略問(wèn)題,無(wú)法直接獲得mac地址,只能得到一個(gè)mac地址換算出來(lái)的identifier。