天天看點

你能用到的iOS面試題(二)

Push Notification 是如何工作的?

  • 推送通知分為兩種,一個是本地推送,一個是遠端推送
    • 本地推送:不需要聯網也可以推送,是開發人員在APP内設定特定的時間來提醒使用者幹什麼
    • 遠端推送:需要聯網,使用者的裝置會于蘋果APNS伺服器形成一個長連接配接,使用者裝置會發送uuid和Bundle idenidentifier給蘋果伺服器,蘋果伺服器會加密生成一個deviceToken給使用者裝置,然後裝置會将deviceToken發送給APP的伺服器,伺服器會将deviceToken存進他們的資料庫,這時候如果有人發送消息給我,伺服器端就會去查詢我的deviceToken,然後将deviceToken和要發送的資訊發送給蘋果伺服器,蘋果伺服器通過deviceToken找到我的裝置并将消息推送到我的裝置上,這裡還有個情況是如果APP線上,那麼APP伺服器會于APP産生一個長連接配接,這時候APPF伺服器會直接通過deviceToken将消息推送到裝置上

什麼是 Runloop?

是一個與線程相關的機制,可以了解為一個循環,在這個循環裡面等待事件然後處理事件.而這個循環是基于線程的,在Cocoa中每個線程都有它的runroop,通過他這樣的機制,線程可以在沒有事件要處理的時候休息,有事件運作,減輕CPU壓力,這題可以衍生出為什麼在滑動時會導緻定時器失敗,在下面有解答

Toll-Free Bridging 是什麼?什麼情況下會使用?

Toll-Free Bridging用于在Foundation對象與Core Foundation對象之間交換資料,俗稱橋接

  • 在ARC環境下,Foundation對象轉成 Core Foundation對象
    • 使用

      __bridge

      橋接以後ARC會自動2個對象
    • 使用

      __bridge_retained

      橋接需要手動釋放Core Foundation對象
  • 在ARC環境下, Core Foundation對象轉成 Foundation對象
    • 使用

      __bridge

      橋接,如果Core Foundation對象被釋放,Foundation對象也同時不能使用了,需要手動管理Core Foundation對象
    • 使用

      __bridge_transfer

      橋接,系統會自動管理2個對象

當系統出現記憶體警告時會發生什麼?

  • 會将不在目前視窗上的view暫時移除
  • 如果放任記憶體警告,最終會導緻軟體強制被系統關閉

什麼是 Protocol,Delegate 一般是怎麼用的?

  • 協定是一個方法簽名的清單,在其中可以定義若幹個方法,遵守該協定的類可以實作協定裡的方法,在協定中使用

    @property

    隻會生成setter和getter方法的聲明
  • delegate用法:成為一個類的代理,可以去實作協定裡的方法

autorelease 對象在什麼情況下會被釋放?

  • 分兩種情況:手動幹預釋放和系統自動釋放
    • 手動幹預釋放就是指定autoreleasepool,目前作用域大括号結束就立即釋放
    • 系統自動去釋放:不手動指定autoreleasepool,Autorelease對象會在目前的 runloop 疊代結束時釋放
      • kCFRunLoopEntry(1):第一次進入會自動建立一個autorelease
      • kCFRunLoopBeforeWaiting(32):進入休眠狀态前會自動銷毀一個autorelease,然後重新建立一個新的autorelease
      • kCFRunLoopExit(128):退出runloop時會自動銷毀最後一個建立的autorelease

為什麼 NotificationCenter 要 removeObserver? 如何實作自動 remove?

  • 如果不移除的話,萬一注冊通知的類被銷毀以後又發了通知,程式會崩潰.因為向野指針發送了消息
  • 實作自動remove:通過自釋放機制,通過動态屬性将remove轉移給第三者,解除耦合,達到自動實作remove

當 TableView 的 Cell 改變時,如何讓這些改變以動畫的形式呈現?

這裡舉個例子,點選cell以後以動畫形式改變cell高度

@interface ViewController ()
@property (nonatomic, strong) NSIndexPath *index;
@end

@implementation ViewController

static NSString *ID = @"cell";
- (void)viewDidLoad {

    [super viewDidLoad];
    

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
    cell.textLabel.text = [NSString stringWithFormat:@"%ld",(long)indexPath.row];
    return cell;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 20;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    
    if(self.index == indexPath){
    
        return 120;
    }
    
    return 60;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.index = indexPath;
    
    [tableView deselectRowAtIndexPath:indexPath animated:TRUE];
    // 重點是這2句代碼實作的功能
    [tableView beginUpdates];
    [tableView endUpdates];
}
           

為什麼 UIScrollView 的滾動會導緻 NSTimer 失效?

定時器裡面有個runoop mode,一般定時器是運作在defaultmode上但是如果滑動了這個頁面,主線程runloop會轉到UITrackingRunLoopMode中,這時候就不能處理定時器了,造成定時器失效,原因就是runroop mode選錯了,解決辦法有2個,一個是更改mode為NSRunLoopCommonModes(無論runloop運作在哪個mode,都能運作),還有種辦法是切換到主線程來更新UI界面的重新整理

為什麼當 Core Animation 完成時,layer 又會恢複到原先的狀态?

因為這些産生的動畫隻是假象,并沒有對layer進行改變.那麼為什麼會這樣呢,這裡要講一下圖層樹裡的呈現樹.呈現樹實際上是模型圖層的複制,但是它的屬性值表示了目前外觀效果,動畫的過程實際上隻是修改了呈現樹,并沒有對圖層的屬性進行改變,是以在動畫結束以後圖層會恢複到原先狀态

你會如何存儲使用者的一些敏感資訊,如登入的 token

  • 使用keychain來存儲,也就是鑰匙串,使用keychain需要導入

    Security

    架構

自定義一個keychain的類

#import <Security/Security.h>

@implementation YCKKeyChain

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (__bridge_transfer id)kSecClassGenericPassword,(__bridge_transfer id)kSecClass,
            service, (__bridge_transfer id)kSecAttrService,
            service, (__bridge_transfer id)kSecAttrAccount,
            (__bridge_transfer id)kSecAttrAccessibleAfterFirstUnlock,(__bridge_transfer id)kSecAttrAccessible,
            nil];
}

+ (void)save:(NSString *)service data:(id)data {
    // 獲得搜尋字典
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    // 添加新的删除舊的
    SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
    // 添加新的對象到字元串
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge_transfer id)kSecValueData];
    // 查詢鑰匙串
    SecItemAdd((__bridge_retained CFDictionaryRef)keychainQuery, NULL);
}

+ (id)load:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    // 配置搜尋設定
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(__bridge_transfer id)kSecReturnData];
    [keychainQuery setObject:(__bridge_transfer id)kSecMatchLimitOne forKey:(__bridge_transfer id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((__bridge_retained CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge_transfer NSData *)keyData];
        } @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        } @finally {
        }
    }
    return ret;
}

+ (void)delete:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((__bridge_retained CFDictionaryRef)keychainQuery);
}
           

在别的類實作存儲,加載,删除敏感資訊方法

// 用來辨別這個鑰匙串
static NSString * const KEY_IN_KEYCHAIN = @"com.yck.app.allinfo";
// 用來辨別密碼
static NSString * const KEY_PASSWORD = @"com.yck.app.password";

+ (void)savePassWord:(NSString *)password
{
    NSMutableDictionary *passwordDict = [NSMutableDictionary dictionary];
    [passwordDict setObject:password forKey:KEY_PASSWORD];
    [YCKKeyChain save:KEY_IN_KEYCHAIN data:passwordDict];
}

+ (id)readPassWord
{
    NSMutableDictionary *passwordDict = (NSMutableDictionary *)[YCKKeyChain load:KEY_IN_KEYCHAIN];
    return [passwordDict objectForKey:KEY_PASSWORD];
}

+ (void)deletePassWord
{
    [YCKKeyChain delete:KEY_IN_KEYCHAIN];
}

           

有用過一些開源元件吧,能簡單說幾個麼,大概說說它們的使用場景實作。

  • AFN:網絡請求
  • FMDB:使用資料庫
  • MJExtension: JSON與Model互轉
  • SVProgressHUD:提示HUD
  • Masonry:自動布局
  • MJRefresh:下拉和上拉重新整理

什麼時候會發生 EXC BAD ACCESS 異常?

  • 通路一個僵屍對象,通路僵屍對象的成員變量或者向其發消息
  • 死循環

NSNotification 和 KVO 的使用場景?

  • KVO使用場景:當一個對象的特定屬性改變的時候,需要被通知一個或者多個對象的時候
  • NSNotification使用場景:跨層級傳遞值,多個對象通知多個對象

使用 Block 時需要注意哪些問題?

  • 在block内部使用外部指針且會造成循環引用情況下,需要用

    __weak

    修飾外部指針

    __weak typeof(self) weakSelf = self;

  • 在block内部如果調用了延時函數還使用弱指針會取不到該指針,因為已經被銷毀了,需要在block内部再将弱指針重新強引用一下

    __strong typeof(self) strongSelf = weakSelf;

  • 如果需要在block内部改變外部變量的話,需要在用

    __block

    修飾外部變量

    筆者也寫過一篇block部落格

performSelector:withObject:afterDelay: 内部大概是怎麼實作的,有什麼注意事項麼?

  • 建立一個定時器,時間結束後系統會使用runtime通過方法名稱(Selector本質就是方法名稱)去方法清單中找到對應的方法實作并調用方法
  • 注意事項
    • 調用

      performSelector:withObject:afterDelay:

      方法時,先判斷希望調用的方法是否存在

      respondsToSelector:

    • 這個方法是異步方法,必須在主線程調用,在子線程調用永遠不會調用到想調用的方法

使用 NSUserDefaults 時,如何處理布爾的預設值?(比如傳回 NO,不知道是真的 NO 還是沒有設定過)

if([[NSUserDefaults standardUserDefaults] objectForKey:ID] == nil){
        NSLog(@"沒有設定");
    }
           

哪些途徑可以讓 ViewController 瘦下來?

  • 把 Data Source 和其他 Protocols 分離出來(将UITableView或者UICollectionView的代碼提取出來放在其他類中)
  • 将業務邏輯移到 Model 中(和模型有關的邏輯全部在model中寫)
  • 把網絡請求邏輯移到 Model 層(網絡請求依靠模型)
  • 把 View 代碼移到 View 層(自定義View)

有哪些常見的 Crash 場景?

  • 通路了僵屍對象
  • 通路了不存在的方法
  • 數組越界
  • 在定時器下一次回調前将定時器釋放,會Crash

有一句話叫做三人行必有我師,其實做為一個開發者,有一個學習的氛圍跟一個交流圈子特别重要這是一個我的iOS交流群681503716,請備注編号朝拜,大牛歡迎入駐,正在求職的也可以加入,大家一起交流學習,話糙理不糙,互相學習,共同進步,一起加油吧。)