iOS 技術

1. 請問先後臺切換,會發生些什麼,系統哪些方法會被調用,viewcontroller哪些方法會被調用

在不考慮 APP 在後臺被 kill 的狀況: 進入後臺:html

方法 做用
applicationWillResignActive 點擊 Home 鍵,app開始準備進入後臺,這個時候會進入該回調,意味着 app 被掛起,進程即將失去活躍。通過不嚴謹的測試,大約有 10 分鐘左右的時間用來處理事務。
applicationDidEnterBackground 當 applicationWillResignActive回調方法徹底執行完畢後,會進入 applicationDidEnterBackground 。

進入前臺:ios

方法 做用
applicationWillEnterForeground 在 app 未被殺死的狀況下,點擊 icon再次進入 app,從新回到前臺以前會先進入 applicationWillEnterForeground 回調
applicationDidBecomeActive applicationWillEnterForeground執行完畢後,會進入 applicationDidBecomeActive 回調,正式迴歸活躍。

先後臺切換,主要的坑點在於:VC中並沒函數會調用,尤爲注意:VC 相關的 Appear 和 Disappear 函數並不會被調用。想在VC中監聽切換,只能監聽通知,每一個在appdelegate的生命代理方法都有對應的通知。git

若是考慮 APP 在後臺被 kill 的狀況:github

進入後臺後,若是沒有後臺運行權限及功能,可能在一段時間後被系統 kill 掉,再次進入app後,會從新進入啓動流程。objective-c

方法 做用
main() 函數: 這個階段通常是 可執行 .o 文件,動態庫加載,objc類註冊,category 類註冊,selector 惟一性檢查,+(void)load 方法,C++ 靜態全局變量的建立等。
didFinishLaunchingWithOptions 用戶點擊 icon 啓動 app,或者被 kill 後以任何方式進入 app,在 main() 執行後,會進入didFinishLaunchingWithOptions回調,處理首屏渲染,以及其餘業務相關的事件,例如監聽事件,配置文件讀寫或者 SDK 初始化等等。
applicationDidBecomeActive 在didFinishLaunchingWithOptions方法做用域結束後,會進入 applicationDidBecomeActive 回調,也正式意味着 app 已經處於活躍狀態。
rootViewController 的相關的 Appear 函數 注意:此時rootViewController 的相關的 Appear 函數會被調用。

參考連接:WWDC 2016 - Session 406-Optimizing App Startup Timeswift

2. 請問對無序的Array排序,有什麼好的方法,代碼越少,API越高級越好。有無原生方法能夠辦到。

蘋果爲咱們提供了不少 Array 的排序方法,但原理上能夠看到就是 Comparator (比較器) 和 Descriptor (描述器) 兩種,像是 Selector 和 Function ,最終也是使用 Comparator 在作排序,只是響應方法不一樣。 其中 Swift 也有方法: array.sort(),見參考連接:Apple Documentation-Swift-Array-sorted 先說說 Comparator ,若是數組中元素是 String 或 Number,首選 Comparator,能夠將 compare: 方法的返回值直接做爲 NSComparisonResult 返回值。實際的排序代碼三行就能夠搞定。固然用 Selector 和 Function,也是同樣的效果,但須要寫更多的代碼。 例子一:後端

NSArray *sortedArray = [array sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
               if ([obj1 compare:obj2] == NSOrderedAscending) {
                   return NSOrderedAscending;
               } else if ([obj1 compare:obj2] == NSOrderedDescending){
                   return NSOrderedDescending;
               }else {
                   return NSOrderedSame;
               }
           }];
複製代碼

例子二:api

- (void)arraySortUsingCompare {
   // 比較器 排序
   
   NSMutableArray *arr = [NSMutableArray array];
   for (int i = 0; i < 10; i ++) {
       int n = arc4random() % (10 - 0) + 1;
       [arr addObject:@(n)];
   }
   NSLog(@"排序前 ===== %@", arr);
   
   [arr sortUsingComparator:^NSComparisonResult(NSNumber *num1, NSNumber *num2) {
       //        return [num1 compare:num2];  // 正序
       return [num2 compare:num1]; // 倒序
       
   }];
   
   NSLog(@"排序後 %@", arr);
   
   
   arr = [NSMutableArray array];
   [arr addObject:@"Kobe Bryant"];
   [arr addObject:@"LeBorn James"];
   [arr addObject:@"Steve Nash"];
   [arr addObject:@"Stephen Curry"];
   [arr addObject:@"Monkey D Luffy"];
   [arr addObject:@"Roronoa Zoro"];
   
   NSLog(@"排序前 ==== %@", arr);
   
   [arr sortUsingComparator:^NSComparisonResult(NSString *str1, NSString *str2) {
       //        return [str1 compare:str2];  // 正序
       return [str2 compare:str1]; // 倒序
   }];
   
   NSLog(@"排序後 %@", arr);
}
複製代碼

參考連接:Objective-C中的排序及Compare陷阱數組

可是若是須要針對一個對象的幾個屬性做爲不一樣的維度去作排序,那選擇 Descriptor,由於不須要根據利用屬性對排序優先級寫一大堆的邏輯判斷。主要將全部參與比較的屬性都放入描述器中便可,若是想對球員的年齡和號碼(優先級分前後)進行排序,只須要依次加入描述器組,三行代碼就能夠完成。性能優化

- (void)arraySortUsingDescriptor {
   NSMutableArray *arr = [NSMutableArray array];
   
   Person *person = [[Person alloc] init];
   person.name = @"Ingram";
   person.age = 21;
   person.number = 14;
   
   [arr addObject:person];
   
   person = [[Person alloc] init];
   person.name = @"Ball";
   person.age = 21;
   person.number = 2;
   
   [arr addObject:person];
   
   person = [[Person alloc] init];
   person.name = @"Zubac";
   person.age = 21;
   person.number = 15;
   
   [arr addObject:person];
   
   NSSortDescriptor *ageDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES];
   NSSortDescriptor *numberDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"number" ascending:YES];
   [arr sortUsingDescriptors:@[numberDescriptor, ageDescriptor]];
   
   for (Person *person in arr) {
       NSLog(@"\n 球員姓名: %@ \n 球員號碼: %d \n 球員年齡: %d \n -------- \n", person.name, person.number, person.age);
       
   }
}
複製代碼

3. 請問APNs推送如何區分設備,如何將設備的信息傳給Apple,你上傳的時機時怎樣的,猜測這個設備信息是如何生成的

設備信息傳遞給apple

post請求; Use HTTP/2 and TLS 1.2 or later to establish a connection between your provider server and one of the following servers:

  • Development server: api.sandbox.push.apple.com:443
  • Production server: api.push.apple.com:443

也就是: 設備信息是經過一個POST請求將DeveiceToken和其餘信息發送給APNS,須要用 HTTP/2 和 TLS 1.2或以上的版本,在本身提供的服務和以上服務之間創建鏈接。

開發環境:api.sandbox.push.apple.com:443

生產環境:api.push.apple.com:443

固然,還能夠用一臺機器的 2197 端口讓 APNS 經過防火牆

請求示例:

HEADERS
 - END_STREAM
 + END_HEADERS
 :method = POST
 :scheme = https
 :path = /3/device/00fc13adff785122b4ad28809a3420982341241421348097878e577c991de8f0
 host = api.sandbox.push.apple.com
 authorization = bearer eyAia2lkIjogIjhZTDNHM1JSWDciIH0.eyAiaXNzIjogIkM4Nk5WOUpYM0QiLCAiaWF0I
    jogIjE0NTkxNDM1ODA2NTAiIH0.MEYCIQDzqyahmH1rz1s-LFNkylXEa2lZ_aOCX4daxxTZkVEGzwIhALvkClnx5m5eAT6
    Lxw7LZtEQcH6JENhJTMArwLf3sXwi
 apns-id = eabeae54-14a8-11e5-b60b-1697f925ec7b
 apns-expiration = 0
 apns-priority = 10
 apns-topic = com.example.MyApp
DATA
 + END_STREAM
 { "aps" : { "alert" : "Hello" } }
複製代碼

上傳時機

didRegisterForRemoteNotificationsWithDeviceToken方法,回調內處理設備信息上傳的業務。但有些狀況是,咱們但願根據用戶帳號來作推送,例如即時通信應用。那麼咱們就要在登陸或自動登陸後,上傳deviceToken,和用戶信息綁定並處理替換邏輯,避免推送錯亂。

設備信息

This address takes the form of a device token unique to both the device and your app.

猜想:UDID+bundleId+生產/開發環境+時間戳。

其中注意帶時間戳hash是爲何頻繁上傳device token的主要緣由。長期不活躍app,好比用戶一個月或者兩個月沒打開過該app,該服務器後端就再也推不到了。

3. 謹慎iOS黑魔法 - Method Swizzling

優勢:

區別於⼿動爲每⼀個類編寫埋點⽅法或者寫⼀個基類來作統⼀的埋點,前二者在某些場景下⼯ 做量都不算⼩。能夠作⼀個UIViewController的Category,置換原⽣⽅法,在置換⽅法中將寫⼊埋點代碼,這樣能夠直接⼀鍵埋點完成。以後新增的UIViewController類也不須要再關⼼這些的埋點代碼。

- (void)cyl_APOViewDidLoad {
Class class = [self class];
if (!([class isEqual:[UIViewController class]] || [class isEqual: [UINavigationController class]])) {
NSLog(@"統計該⻚⾯ %@", class);
}
}
複製代碼

置換 NSDictionary-setObject:forKey: 方法,用於防止 crashNSArray 同理。

- (void)cyl_safeSetObject:(id)object forKey:(id<NSCopying>)key {
if (object && key) {
[self safe_setObject:object forKey:key];
}
}
複製代碼

缺點:

總結:一時hook一時爽,debug火葬場。

緣由:

如下爲What are the Dangers of Method Swizzling in Objective-C? 中列舉出的7個問題:

  • Method swizzling is not atomic
  • Changes behavior of un-owned code
  • Possible naming conflicts
  • Swizzling changes the method's arguments
  • The order of swizzles matters
  • Difficult to understand (looks recursive)
  • Difficult to debug

可見,其沒有相似註解的東⻄,⽅法置換沒有有效聲明。若是濫⽤,反⽽會增長維護成本。若擅⾃使⽤未同步其餘同窗,會成爲極⼤的項⽬隱患。尤爲是⼀些封裝的模塊。

這裏着重說明幾個場景:

場景:(iTeaTime(技術清談)@國家一級保護廢物 提供答案)

若是屢次hook了同一個類的同一個方法, 跟分類重名的表現是同樣:表現爲沒法控制執行的前後順序,與編譯器build的順序有關,但編譯器順序有不可控性。

好比下面的實現方法,可能出現方法覆蓋的問題:

+ (void)load {
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewDidLoad); SEL swizzledSelector = @selector(XK_ViewDidLoad);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
複製代碼

場景:(iTeaTime(技術清談)@molon 提供) Hook了具備繼承關係的相同方法。

如下場景:

若是子類並無重寫父類的方法,拿父類的implement去swizzling原本就是錯誤的行爲。

A<—繼承---B<—繼承---C (B是A的子類,C又是B的子類)

A 裏有 test 方法,可是 B 和 C 都沒有重寫。 一般若是要對 B 或者 C 的 test 進行hook的話,不少開發者都喜歡去給 B 或者 C add A.test 的 implemention。 那若是先hook的C,又hook的B,彷佛就造成了C與A直接打交道的局面。可是以面向對象來講,C的原實現應該是B的當前實現才合理。 因此不該該hook當前類沒有重寫的方法,這種其實直接繼承(或者加category方法)就能夠作了,不須要hook,須要調用原實現直接[super test]便可。

4. iPhone在無耳機狀態下,經過實體按鍵設置靜音後,如下路徑好比: 微信主tab-朋友圈-點開feed流中的小視頻,能夠播放聲音。經過點擊頭像-我的朋友圈主頁,點開視頻沒法播放聲音。即便按聲音增長鍵也沒法播放。請問這個表現不一致的現象,是feature仍是bug,若是是bug你以爲是代碼哪裏寫的有問題。寫出修復代碼

視頻播放器默認靜音模式下是沒有聲音的,但能夠控制即便是靜音模式下依然有聲音,顯然前者設置了,後者沒有設置。推測前者是被提交了bug因此fix掉了,後者使用場景比較少,因此沒有被注意到。

//忽略靜音按鈕
   AVAudioSession *session =[AVAudioSession sharedInstance];
   [session setCategory:AVAudioSessionCategoryPlayback error:nil];
複製代碼

完整代碼:

- (AVAudioPlayer *)player {
if (!_player) {
NSURL *URL = [[NSBundle mainBundle] URLForResource:@"xxxx.wav"
withExtension:nil];
_player = [[AVAudioPlayer alloc] initWithContentsOfURL:URL error:nil];
AVAudioSession *autioSession = [AVAudioSession sharedInstance];
[autioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
[autioSession setActive:YES error:nil];
[_player prepareToPlay];
}
複製代碼

耳機場景下,統一作了處理,均可以播放視頻帶聲音。 好比如下代碼用於判斷耳機狀態,由於AVAudioSession是單例,對耳機優先處理便可。

- (BOOL)isHeadsetPluggedIn {  
   AVAudioSessionRouteDescription* route = [[AVAudioSession sharedInstance] currentRoute];  
   for (AVAudioSessionPortDescription* desc in [route outputs]) {  
       if ([[desc portType] isEqualToString:AVAudioSessionPortHeadphones])  
           return YES;  
   }  
   return NO;  
} 
複製代碼

5. 【iOS-autolayout】一個ScrollView上有3個UILabel,每一個label字數不固定,相似字數不少的那種,要求上下依次排列,當文字超出ScrollView的時候能夠滑動,左右不能滾動,上下可滾動。【難度🌟🌟🌟】【出題人羣內大佬:@起點】

出題人提示

就是label的寬度設置跟scrollView等寬,最底下的label底部要跟scrollView的底部約束上就能夠了。 考察的主要是scrollView的約束問題。scrollView的約束主要是從內部撐開寬度跟高度。

答案

三個label 那個,就是放了個scrollview而後裏面放三個label,從上往下邊距所有約束爲0,而後label寬度與scrollview相同,最下面那個label距離底部scrollview爲0。(在內部無需多放view)

  1. 在 Scrollview 添加⼀個 ContainView
  2. ContentView 徹底覆蓋 Scrollview
  3. ContainView 上添加了三個 Label。View 的 bottom 和 第三個 Label 的 bottom 作約束
  4. 三個 Label 互相作間距和寬的約束,不約束⾼

5. 經過將 Scrollview 的 ContentSize 和 ContainView 的 size 保持⼀致。

- (void)viewDidAppear:(BOOL)animated {
       [super viewDidAppear:animated];
       [self.contentView layoutIfNeeded];
        self.scrollview.contentSize =
       CGSizeMake(CGRectGetWidth(self.contentView.frame), CGRectGetHeight(self.contentView.frame));
}
複製代碼

DEMO

6. 如何用一行代碼,互換兩個變量的值,且不產生第三個變量。

  • 利用Swift元組特性:

能夠在定義的同時就取出元祖中的值

// 至關於同時定義了三個變量

let (name, age, score) = (「a」, 30, 99.9)

根據這一特性,咱們能夠這樣互換值: (a, b) = (b, a)

  • 異或或者加減

(a = a ^ b) && (b = a ^ b) && (a = a ^ b)

或者這樣

a = a ^ b;b = a ^ b;a = a ^ b;

(a = a + b) && (b = a - b) && (a = a - b)

(a = a x b) && (b = a / b) && (a = a / b)

7. 如何給view同時加上圓角和陰影?至少給出兩種實現方法,使用到的API越高級越好。

【提示】兩種方法,答案提示:UIBezierPath,和iOS11 layer有個新的方法 【答案】iOS11的layer是maskedCorners,CACornerMask。

參考連接:ios 圓角 cornerRadius 對性能的影響究竟多大? 你測試過嗎?

8. 猜測dequeueReusableCellWithIdentifier的實現是怎樣的,給出示例代碼。注意邊界條件:相鄰cell的identifier相等時。你的實現中該函數的時間複雜度是多少。爲何?【難度🌟🌟🌟🌟】【出題人 微博@iOS程序犭袁】

cell複用機制的實現猜測,見GitHub-Chameleon:

- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier
{
   for (UITableViewCell *cell in _reusableCells) {
       if ([cell.reuseIdentifier isEqualToString:identifier]) {
           UITableViewCell *strongCell = cell;
           
           // the above strongCell reference seems totally unnecessary, but without it ARC apparently
           // ends up releasing the cell when it's removed on this line even though we're referencing it
           // later in this method by way of the cell variable. I do not like this.
           [_reusableCells removeObject:cell];

           [strongCell prepareForReuse];
           return strongCell;
       }
   }
   
   return nil;
}
複製代碼

時間複雜度爲: O(n)

注意:

NSArray / NSMutableArray

containsObject:containsObject:``,indexOfObject*removeObject:會遍歷裏面元素查看是否與之匹對,因此複雜度等於或大於 O(n)。

這裏 _reusableCells 使用的是NSMutableSet,而 NSSet / NSMutableSet / NSCountedSet

這些集合類型是無序沒有重複元素。這樣就能夠經過 hash table 進行快速的操做。好比 addObject:, removeObject:, containsObject: 都是按照 O(1) 來的。須要注意的是將數組轉成 Set 時會將重複元素合成一個,同時失去排序。

加之 for 循環,能夠獲得複雜度計算結果。

參考:深刻剖析 iOS 性能優化

7. 【在IM開發中】app 接收到一個message,上層UI刷新一次,要求考慮到CPU和電量消耗,解決短期內接收到不少條消息的問題。怎麼解決?有幾種方案?【出題人:遠之²³³³-free zone-北】【 難度🌟🌟】

方案一:利用聯結(在異步線程上調用dispatch_source_merge_data後,就會執行 dispatch source事先定義好的handler)、DISPATCH_SOURCE_TYPE_DATA_ADD,將刷新UI的工做拼接起來,短期內作儘可能少次數的刷新。

方案二:本身實現隊列、肯定一個合適的時間閾值,在閾值時間到達時、主動取消息或者被動接受消息,最後刷新UI,達到消息限流的做用。舉例:假設咱們消息的獲取都是經過長鏈接推送過來的,而不是主動拉取的。能夠用消息隊列來作,消費者按期去隊列取數據進行數據展現。或者假設前一條消息和後一條消息間隔只在0.2s之內,就能夠認爲是頻繁收到消息。而後把這0.2s內的消息刷新相關操做,好比作個動畫效果。

8. 如圖label1在containerView上,containerView、label2在cell.contentView上問題:label1與label2的字數不固定,需求是,不管label2字數多少,label1都不能被拉伸或者壓縮:【 難度🌟🌟🌟】【出題人:記憶、擱淺】

效果圖見:

【答案】須要給label1設置一下優先級,設置平行的的Content compression resistance priority。

系統 Autolayout 參考 :Apple-Documentation-UIView-setContentHuggingPriority(_:for:)

Masonry 參考如下屬性:

static const MASLayoutPriority MASLayoutPriorityRequired = UILayoutPriorityRequired;
   static const MASLayoutPriority MASLayoutPriorityDefaultHigh = UILayoutPriorityDefaultHigh;
複製代碼

9.【計算機常識】若是你一直在用GitLab開發,如今公司要切換到GitHub開發,能夠兩個郵箱不同,你本身的提交記錄,GitHub沒法識別,簽到數據也沒了,請問如何讓GitHub可以識別你整個倉庫中全部的提交記錄。【難度🌟🌟】【出題人 微博@iOS程序犭袁】

【注】「簽到數據」指的是下圖:

【答案】參考:

Git 實戰手冊(一): 批量修改log中的提交信息

使用git遷移git項目並保留提交記錄

文章來源

iTeaTime(技術清談)

相關文章
相關標籤/搜索