參考答案:
iOS Socket編程-Objective-C原生API版
iOS Socket編程-Objective-C基於CocoaAsyncSocket版
iOS Socket編程-Swift原生API版
iOS Socket編程-Swift基於AsyncSocket版
參考答案:
關於圖片的壓縮處理,在ios中常用的方法是先處理像素再處理尺寸。
參考答案:
最直接的辦法就是使用UIImageView的contentMode,讓其自動適應,但是這樣會消耗一定的性能。
如果要優化,可以通過手動將原圖處理成UIImageView大小的圖,再給它呈現。
參考答案:
通常耗時的操作都會放在子線程裡處理,然後再回到主線程來顯示。下面舉幾個例子:
我們要從數據庫提取數據還要將數據分組後顯示,那麼就會開個子線程來處理,處理完成後才去刷新UI顯示。拍照後,會在子線程處理圖片,完成後才回到主線程來顯示圖片。拍照出來的圖片太大了,因此要做處理。音頻、視頻處理會在子線程來操作文件較大時,文件操作會在子線程中處理做客戶端與服務端數據同步時,會在後台閒時自動同步參考答案:
將build setting中的Optimization Level設置為Fastest, Smallest [-Os],在發布模式下,默認就是這樣設置的將build setting 中的Strip Debug Symbols During Copy設置為YES,在發布模式下,默認就是這樣設置的資源文件查找出所有未使用的,去掉這些永遠不會使用的資源文件對嵌入App的音頻進行壓縮處理就想到這些了,大家再補充吧~
參考答案:
版本迭代一定要注意兼容老版本,比如新增了字段或者去掉了某些不再使用的字段,不能引起應用閃退。我們這裡只談程序代碼兼容新老版本問題,不考慮業務。因為業務是要求後台來兼容的,通常接口會有版本號控制,用於兼容不同版本的客戶端。
對於任何一個App,當可以升級的時候,不會是所有用戶就立刻去升級,通常會有很大一部分的用戶是不願意立刻升級的。原因會有很多種,比如我這種的就不會頻繁升級,因為對於我來說,這個App並不是天天用,沒有必要升級。
那麼,我們在iOS開發時,如何去兼容老版本的,保證新版本的增加或者刪減不會影響到老版本呢?其實這個問題似乎並不是說有沒有新、老版本問題,更重要的是程序的健壯性問題。
對於我們做前端的,永遠不要相信後台一定會按照原先約定返回我們想要的數據結構以及所有字段。
假設接口返回來的數據是這樣的,我們需要通過類型判斷,確保不會因為接口變化返回無效數據而引起閃退。當然,當接口返回的數據結構與我們原先約定的不一樣時,通常是因為後台出錯了,因此為了程序更健壯,我們應該要容得下後台的錯誤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 AFHTTPRequestOperation *op = [self PostRequestWithUrl:url params:params completion:^(id responseObject) { BOOL isSuccess = NO; if ([responseObject isKindOfClass:[NSDictionary class]]) { NSDictionary *response = responseObject[@"response"]; if ([response isKindOfClass:[NSDictionary class]]) { NSArray *resultList = response[@"resultList"]; if ([resultList isKindOfClass:[NSArray class]]) { NSArray *listModels = [HYBCosmesisModel objectArrayWithKeyValuesArray:resultList]; isSuccess = YES; completion(listModels); } } } if (!isSuccess) { // 表示出錯 completion(nil); } } errorBlock:^(NSError *error) { errorBlock(error); }];而我們在使用的時候,對於字符串、數組、字典都應該要做一下類型判斷和空處理。比如:
1 2 3 4 5 6 7 8 9 10 11 12 13 if ([response isKindOfClass:[NSDictionary class]]) { NSString *value = response[@"blogName"]; if (!kIsEmptyString(value)) { // Do my job } NSArray *array = response[@"array"]; if ([array isKindOfClass:[NSArray class]]) { // Do my job } }對於業務方面的話,由後台來做版本控制,通過在接口做添加公共參數,[emailprotected]?要調用的是哪個版本的接口。
參考答案:
這個問題非常簡單,但是對於新手就不太清楚了。在很多小公司裡,前端好像什麼都不需要管,只等後台給你一個接口及參數說明就可以了,根本不清楚後端為什麼要這麼設計這個接口。
那筆者也來聊聊如何與後端服務器交互:
在需求確定,定下了開發的周期後,就需要准備開發了。iOS、安卓及後端各端主要負責人員在了解完需求後,開始分析本期需求需要哪些接口,是否需要新的接口,是否需要改動原有的接口等。在三端統一後,後端接口負責人確定哪天出接口文檔及接口假數據。為什麼要三端一起定接口呢?因為即使是後端接口負責人,也不一定對原有的業務和原有的接口全部都了解,任何一方的了解加起來才能確定是否可行。如果App還沒有開發過,首先開發一款新的App,那麼iOS、安卓端的架構師,或者主要開發負責人,需要與後端接口負責人共同分析需求,然後初步寫出第一版本接口文檔,然後各方再各自好好看看、分析分析接口是否合理,參數是否合理,結構是否合理等。這數據的結構會決定著整個網絡框架的搭建。好了,就扯談這些吧~
參考答案:
使用Dispatch Group追加block到Global Group Queue,這些block如果全部執行完畢,就會執行Main Dispatch Queue中的結束處理的block。
當放到group中的所有請求都完成時,才會回調dispatchgroupnotify的block:
1 2 3 4 5 6 7 8 9 10 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ /*加載圖片1 */ }); dispatch_group_async(group, queue, ^{ /*加載圖片2 */ }); dispatch_group_async(group, queue, ^{ /*加載圖片3 */ }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 合並圖片 });參考答案:
這個不太清楚想問什麼,筆者翻看了看GCD中dispatchgroupt裡,也沒有什麼可以設置同一個組內的任務依賴關系的,就看到dispatchgroupwait這個API:
1 2 3 4 long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);這個API是等待group中的所有任務都執行完畢才能繼續往下執行其它任務。它是同步地等待任務執行完畢。比如,A、B、C、D四個任務,要求A、B執行完畢後,C、D才能開始執行,那麼可以通過這樣做:
1 2 3 4 5 6 7 8 9 10 11 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ /* 任務A */ }); dispatch_group_async(group, queue, ^{ /* 任務B */ }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ dispatch_group_async(group, queue, ^{ /* 任務C */ }); dispatch_group_async(group, queue, ^{ /* 任務D */ }); });或者可以這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ /* 任務A */ }); dispatch_group_async(group, queue, ^{ /* 任務B */ }); // 同步等待A、B執行 dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); // 重新創建組 group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ /* 任務C */ }); dispatch_group_async(group, queue, ^{ /* 任務D */ }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // C、D執行完畢後,想干嘛就干嘛去吧 });不知道筆者對題目的理解是否到位,上面的代碼是隨手寫的,可能單詞會寫錯~~~
參考答案:
這個問題,筆者在另外一篇文章中寫到,然後也在微博上收集了很多朋友們的想法。請大家穩步去閱讀吧!
A. 可隨機訪問任何一個元素
B. 插入,刪除操作不需要移動元素
C. 無需事先估計存儲空間大小
D. 所欲存儲空間可以是不連續的
這道題是考大學時所學的鏈表知識,其實筆者也忘了很多了。因為在大學時,曾經自己寫過很多鏈表相關的代碼,再加上經常幫同學調試,以及幫助同學講解鏈表相關知識點,因此記憶較深。以下答案純屬個人認知,非百度而來,若有不對之處,請指出。
參考答案: (A)
鏈表不同於數組。鏈表之所有叫鏈表,就是像一條鏈一樣,要過到某個節點處,就得遍歷著找;而數組才具備隨機訪問任何一個元素的能力,數組可以通過索引直接訪問元素,時間復雜度為常量,效率非常高,因此在某些場合上,我們需要數組這樣的數據結構。
B. 鏈表的插入、刪除都不需要移動元素,只需要修改指針的指向就可以了,因為鏈表上的每個節點都是動態分配的,分配在堆上,通過指針來指向每個節點的內存區,要獲取某個節點的值,是需要遍歷一遍才能找到對應的節點的。
C. 因為鏈表上的每個節點是分配在堆上,需要開發人員手動申請內存空間的,因此不像數組在定義時就要指定存儲空間大小。對於鏈表,需要增加一個節點時,直接在堆上申請。當需要刪除某個節點時,可以直接將該節點的內存給釋放掉。
D. 因為鏈接中的節點都是存儲在堆上的,而每個節點之間都有一個指向前一個節點和後一個節點的指針,只要知道鏈表頭指針,就可以通過遍歷查找到任何一個節點。因此,鏈表不同於數組,數組是要連續的內存存儲空間,才能保證以常量時間復雜度快速訪問任意元素;而鏈表不要求每個節點是連接,在堆上申請的內存空間很難得到連續的,而且空間產生內存碎片。
A. 多進程裡,子進程可獲取父進程的所有堆和棧的數據;而線程會與同進程的其他線程共享數據,擁有自己的棧空間。
B. 線程因為有自己的獨立棧空間且共享數據,所有執行的開銷相對較大,同時不利於資源管理和保護。
C. 線程的通信速度更快,切換更快,因為他們在同一地址空間內。
D. 線程使用公共變量/內存時需要使用同步機制,因為他們在同一地址空間內。
參考答案:
這道題要求不創建新的實例,只有a、b兩個變量,要交換這兩個變量的值,通常的做法是使用臨時變量來臨時存儲,但是現在要求不使用新的實例,那麼有什麼辦法呢?
方法就是通過位運算來操作:
1 2 3 4 5 a = a ^ b; b = a ^ b; a = a ^ b;對於題目中的a = 19,也就是對應二進制00010011;而b=29,也就是對應二進制00011101
第一步:a = 00010011 ^ 00011101 => 00001110,將a、b的值都記錄下來了第二步:b = 00001110 ^ 00011101 => 00010011(值為19,也就是b得到了原來的a的值)第三步:a = 00001110 ^ 00010011 => 00011101 (值為29,也就是a得到了原來的b的值)注意,符號表示按位異或。所謂按位異或是指對應位置上的二進制數值相同為0,不同為1。
參考答案:
筆者之前寫過這篇文章講了講開發中常見的內存循環引用的案例:
參考答案:
筆者也不是很確定,iOS裡的序列化是指歸檔、JSON序列化嗎?實際上歸檔也就是將對象轉換成XML、JSON序列化也就是將對象轉換data。
將對象JSON序列化:
1 2 3 4 5 6 7 8 NSLog(@"%s", __FUNCTION__); NSDictionary *dict = @{@"key": @"value", @"key1" : @"value1", @"key2" : @"value2"}; NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil]; NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);將對象歸檔:需要遵守NSCoding協議,實現如下方法:
1 2 3 4 5 - (void)encodeWithCoder:(NSCoder *)aCoder { [aCoder encodeObject:self.title forKey:@"title"]; }如果所謂的序列化不是指這兩種,還請高人指點。
參考答案:
將耗時的計算和IO操作放在子線程去處理,然後到主線程更新UI。優點是采用預加載方式,將耗時操作提前處理。優點是可讓UI更流暢;缺點是內存會增多,控制加載邏輯比較復雜。采用延遲加載方式,將耗時操作而不立刻使用時,采用延遲加載。優點是界面可提高流暢度;缺點是在需要顯示時還需要加載才能顯示,需要稍稍等待。不知道說得合不合適,還請高人提出還有哪些方式?
參考答案:
若高度一定,直接使用rowHeight屬性而不是使用heightForRowAtIndexPath方法,以減少調用的消耗。若高度是不固定的,heightForRowAtIndexPath所計算的高度應該緩存起來,每次數據源發生變化時,比如刪除、插入、更新行都會重新請求所有的高度。若有100個行,就會有調用100次,因為將高度緩存起來是應該的。同理,heightForHeaderInSection、heightForFooterInSection也應該緩存起來。不要在tableView:cellForRowAtIndexPath:中做太多的計算和IO操作,比如可以將需要的計算提前計算好、IO操作也提前計算好。它應該直接調用來顯示就可以。將計算行高的時間提前到從服務器獲取數據的時候,計算完了高度一並寫回數據庫或者通過轉型為model,將高度放到模型中。但是,最好將高度緩存起來。若一個model的數據有不同的狀態,比如展開與收起狀態,應該也將高度都緩存起來。注意使用異步去計算,計算完成後再回到主線程顯示。在設置顯示圖片時,不要直接設置UIImageView的contentMode屬性自動適應,圖片變形會計算transform,壓縮時會乘以一個矩陣,消耗性能。對於要求性能較高的app,應該將得到的圖片經過處理成UIImageView大小後再呈現。不要將視圖的opaque屬性設置為NO,默認為YES,它表示不透明度。當opque為NO的時候,圖層的半透明取決於圖片和其本身合成的圖層為結果。layer添加圓角是比較耗時的,這樣會離屏渲染,需要犧牲更多的性能。比如,圖片顯示有圓角時,可以通過core graphics來生成帶圓角的圖片等。手動繪制cell。繪制cell不建議使用UIView,建議使用CALayer。 UIView的繪制是建立在CoreGraphic上的,其使用的是CPU。CALayer使用的是Core Animation,CPU、GPU都可以使用且由系統自動決定使用哪一個。UIView的繪制,使用的是自下向上的一層一層的繪制,而後渲染。Layer處理的是紋理,利用GPU的 Texture Cache和獨立的浮點數計算單元可以加速紋理的處理。重用cell。防止重復的繪制,減少渲染次數,可提高性能。減少subviews的數量。盡量放在同一層view上顯示。盡量少動態給cell添加子view。用addView給Cell動態添加View,可以初始化時就添加,然後通過hide來控制是否顯示。想要更深入,不防看看大牛的文章吧:iOS 保持界面流暢的技巧
參考答案:
設計API的基本准則是:
簡單易用易擴展單一功能注意事項:
盡可能不要依賴第三方庫(除非不使用第三庫需要非常大的工作量)。每個API的功能應該是單一的,只做一件事。注意性能、內存問題注意多個HUD顯示、關閉的切換問題樣式問題分析:
要想讓全工程使用起來非常方便,那最好的方式就是使用單例,比如SVProgressHUD就是通過單例的方式來操作的。將單例封閉在內部,外部並不知道是單例,而外部的調用全是通過類方法的形式來調用,代碼調用是非常簡化的。使用單例的優點是方便管理和調用。缺點就是一直占用內存而不釋放。
當然,我們也可以通過正常的對象創建,在哪裡使用就在哪裡創建一個對象,自己來管理。這樣的方式在使用的地方不是那麼方便,還需要再單獨進行一層封裝,以方便直接調用。但這種方式的好處就是在不需要使用的時候可以釋放掉;缺點就是如果同時創建了多個HUD來顯示時,需要調用者使用代碼邏輯來控制之前的顯示與隱藏或者切換文本等。
筆者覺得,使用單例方式更方便調用一些,外部也不用通過邏輯來管理多個HUD的顯示與隱藏問題,都封裝到內部,由封裝庫的人來維護。
假設設計一個HYBProgressHUB的小例子,隨手寫的,沒有真實寫完整:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 typedef NS_ENUM(NSUInteger, HYBProgressHUBMaskType) { kHYBProgressHUBClear, kHYBProgressHUBBlack, kHYBProgressHUBUserEnabled }; @interface HYBProgressHUD : NSObject + (void)setDefaultMaskType:(HYBProgressHUBMaskType)defaultMaskType; + (void)show; + (void)showWithText:(NSString *)text; + (void)showWithImage:(NSString *)image; + (void)showWithText:(NSString *)text image:(UIImage *)image; + (void)showWithText:(NSString *)text maskType:(HYBProgressHUBMaskType)maskType; + (void)showWithImage:(NSString *)imagemaskType:(HYBProgressHUBMaskType)maskType; + (void)showWithText:(NSString *)text image:(UIImage *)imagemaskType:(HYBProgressHUBMaskType)maskType; + (void)dismiss; + (void)dismissWithText:(NSString *)text; + (void)dismissWithImage:(UIImage *)image; + (void)dismissWithText:(NSString *)text image:(UIImage *)image; @end將單例放在實現文件中,並沒有暴露出來:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @implementation HYBProgressHUD + (instancetype)sharedInstance { __block HYBProgressHUD *singleTon = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ singleTon = [[[self class] alloc] init]; }); return singleTon; } @end因為根據不同的需求,不同的場景,show會有很多種形式,比如不可點擊、可點擊、是否半透明等,因此API也應該針對單一性原則提供對應的API。但是,一個App中大部分的HUD顯示的樣式、方式是相同的,因此,我們可以提供一個默認的全局的呈現樣式,比如調用show這個API要顯示的樣式就是默認的樣式,若不調用,內部也會給一個默認值。
對於dismiss也一樣,可以是直接隱藏,也可以在隱藏之前先提示點什麼,比如純文本提示、純圖片提示、文本和圖片組合提示等。
當然,我們還可以擴展API設置dismiss的默認顯示圖片和文字的樣式:
1 2 3 4 5 6 7 8 9 10 typedef NS_ENUM(NSUInteger, HYBProgressHUBDismissType) { kHYBProgressHUBDismissSuccess, kHYBProgressHUBDismissFailed, kHYBProgressHUBDismissWarnings, kHYBProgressHUBDismissTimeout }; + (void)dismissWithText:(NSString *)text type:(HYBProgressHUBDismissType)type;比如在所有的網絡請求的地方,就可以寫一個通用的API,在成功與失敗回調處,統一處理顯示不同的類型提示。
提示:
上面所描述內容為筆者純手工嘗試設計的,不代表足夠合理,寫出來的最主要目的是拋磚引玉,當然我更希望能夠有大神出來指出其中的不足或者加以補充,讓後來者少走彎路。