iOS面試題--UI篇

Size Classes 具體使用

  • 對屏幕進行分類

UIView和CALayer是什麼關係?

  • UIView顯示在屏幕上歸功於CALayer,通過調用drawRect方法來渲染自身的內容,調節CALayer屬性可以調整UIView的外觀,
  • UIView繼承自UIResponder,比起CALayer可以響應用戶事件,Xcode6之後可以方便的通過視圖調試功能查看圖層之間的關係
  • UIView是iOS系統中界面元素的基礎,所有的界面元素都繼承自它。它內部是由Core Animation來實現的,它真正的繪圖部分,是由一個叫CALayer(Core Animation Layer)的類來管理。UIView本身,更像是一個CALayer的管理器,訪問它的跟繪圖和座標有關的屬性,如frame,bounds等,實際上內部都是訪問它所在CALayer的相關屬性
  • UIView有個layer屬性,可以返回它的主CALayer實例,UIView有一個layerClass方法,返回主layer所使用的類,UIView的子類,可以通過重載這個方法,來讓UIView使用不同的CALayer來顯示,如:
- (class) layerClass {
    // 使某個UIView的子類使用GL來進行繪製
    return ([CAEAGLLayer class]);
}
  • UIView的CALayer類似UIView的子View樹形結構,也可以向它的layer上添加子layer,來完成某些特殊的顯示。例如下面的代碼會在目標View上敷上一層黑色的透明薄膜。
grayCover = [[CALayer alloc]init];
grayCover.backgroudColor = [[UIColor blackColor]colorWithAlphaComponent:0.2].CGColor;
[self.layer addSubLayer:grayCover];

  • 補充部分,這部分有深度了,大致瞭解一下吧,UIView的layer樹形在系統內部被系統維護着三份copy
    • 邏輯樹,就是代碼裏可以操縱的,例如更改layer的屬性等等就在這一份
    • 動畫樹,這是一箇中間層,系統正是在這一層上更改屬性,進行各種渲染操作
    • 顯示樹,這棵樹的內容是當前正被顯示在屏幕上的內容
    • 這三棵樹的邏輯結構都是一樣的,區別只有各自的屬性

作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

推薦閱讀

iOS開發——最新 BAT面試題合集(持續更新中)

loadView的作用?

  • loadView用來自定義view,只要實現了這個方法,其他通過xib或storyboard創建的view都不會被加載
  • 看懂控制器view創建的這個圖就行

image

IBOutlet連出來的視圖屬性爲什麼可以被設置成weak?

  • 因爲父控件的subViews數組已經對它有一個強引用

IB中User Defined Runtime Attributes如何使用?

  • User Defined Runtime Attributes是一個不被看重但功能非常強大的的特性,它能夠通過KVC的方式配置一些你在interface builder中不能配置的屬性
  • 當你希望在IB中作儘可能多得事情,這個特性能夠幫助你編寫更加輕量級的viewcontroller

沙盒目錄結構是怎樣的?各自用於那些場景?

  • Application:存放程序源文件,上架前經過數字簽名,上架後不可修改
  • Documents:常用目錄,iCloud備份目錄,存放數據
  • Library
    • Caches:存放體積大又不需要備份的數據
    • Preference:設置目錄,iCloud會備份設置信息
  • tmp:存放臨時文件,不會被備份,而且這個文件下的數據有可能隨時被清除的可能

pushViewController和presentViewController有什麼區別

  • 兩者都是在多個試圖控制器間跳轉的函數
  • presentViewController提供的是一個模態視圖控制器(modal)
  • pushViewController提供一個棧控制器數組,push/pop

請簡述UITableView的複用機制

  • 每次創建cell的時候通過dequeueReusableCellWithIdentifier:方法創建cell,它先到緩存池中找指定標識的cell,如果沒有就直接返回nil
  • 如果沒有找到指定標識的cell,那麼會通過initWithStyle:reuseIdentifier:創建一個cell
  • 當cell離開界面就會被放到緩存池中,以供下次複用

如何高性能的給 UIImageView 加個圓角?

  • 不好的解決方案

    • 使用下面的方式會強制Core Animation提前渲染屏幕的離屏繪製, 而離屏繪製就會給性能帶來負面影響,會有卡頓的現象出現

      self.view.layer.cornerRadius = 5;
      self.view.layer.masksToBounds = YES;
      
    • 正確的解決方案:使用繪圖技術

    - (UIImage *)circleImage
    {
        // NO代表透明
        UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);

        // 獲得上下文
        CGContextRef ctx = UIGraphicsGetCurrentContext();

        // 添加一個圓
        CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
        CGContextAddEllipseInRect(ctx, rect);

        // 裁剪
        CGContextClip(ctx);

        // 將圖片畫上去
        [self drawInRect:rect];

        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

        // 關閉上下文
        UIGraphicsEndImageContext();

        return image;
    }

  • 還有一種方案:使用了貝塞爾曲線"切割"個這個圖片, 給UIImageView 添加了的圓角,其實也是通過繪圖技術來實現的
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.center = CGPointMake(200, 300);
UIImage *anotherImage = [UIImage imageNamed:@"image"];
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds
                       cornerRadius:50] addClip];
[anotherImage drawInRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:imageView];

使用drawRect有什麼影響?

  • drawRect方法依賴Core Graphics框架來進行自定義的繪製
  • 缺點:它處理touch事件時每次按鈕被點擊後,都會用setNeddsDisplay進行強制重繪;而且不止一次,每次單點事件觸發兩次執行。這樣的話從性能的角度來說,對CPU和內存來說都是欠佳的。特別是如果在我們的界面上有多個這樣的UIButton實例,那就會很糟糕了
  • 這個方法的調用機制也是非常特別. 當你調用 setNeedsDisplay 方法時, UIKit 將會把當前圖層標記爲dirty,但還是會顯示原來的內容,直到下一次的視圖渲染週期,纔會將標記爲 dirty 的圖層重新建立Core Graphics上下文,然後將內存中的數據恢復出來, 再使用 CGContextRef 進行繪製

描述下SDWebImage裏面給UIImageView加載圖片的邏輯

  • SDWebImage 中爲 UIImageView 提供了一個分類UIImageView+WebCache.h, 這個分類中有一個最常用的接口sd_setImageWithURL:placeholderImage:,會在真實圖片出現前會先顯示佔位圖片,當真實圖片被加載出來後在替換佔位圖片
  • 加載圖片的過程大致如下:
    • 首先會在 SDWebImageCache 中尋找圖片是否有對應的緩存, 它會以url 作爲數據的索引先在內存中尋找是否有對應的緩存
    • 如果緩存未找到就會利用通過MD5處理過的key來繼續在磁盤中查詢對應的數據, 如果找到了, 就會把磁盤中的數據加載到內存中,並將圖片顯示出來
    • 如果在內存和磁盤緩存中都沒有找到,就會向遠程服務器發送請求,開始下載圖片
    • 下載後的圖片會加入緩存中,並寫入磁盤中
    • 整個獲取圖片的過程都是在子線程中執行,獲取到圖片後回到主線程將圖片顯示出來

設計個簡單的圖片內存緩存器

  • 類似上面SDWebImage實現原理即可
  • 一定要有移除策略:釋放數據模型對象

控制器的生命週期

  • 就是問的view的生命週期,下面已經按方法執行順序進行了排序
// 自定義控制器view,這個方法只有實現了纔會執行
- (void)loadView
{
    self.view = [[UIView alloc] init];
    self.view.backgroundColor = [UIColor orangeColor];
}
// view是懶加載,只要view加載完畢就調用這個方法
- (void)viewDidLoad
{
    [super viewDidLoad];

    NSLog(@"%s",__func__);
}

// view即將顯示
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    NSLog(@"%s",__func__);
}
// view即將開始佈局子控件
- (void)viewWillLayoutSubviews
{
    [super viewWillLayoutSubviews];

    NSLog(@"%s",__func__);
}
// view已經完成子控件的佈局
- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    NSLog(@"%s",__func__);
}
// view已經出現
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    NSLog(@"%s",__func__);
}
// view即將消失
- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    NSLog(@"%s",__func__);
}
// view已經消失
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    NSLog(@"%s",__func__);
}
// 收到內存警告
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];

    NSLog(@"%s",__func__);
}
// 方法已過期,即將銷燬view
- (void)viewWillUnload
{

}
// 方法已過期,已經銷燬view
- (void)viewDidUnload
{

}

你是怎麼封裝一個view的

  • 可以通過純代碼或者xib的方式來封裝子控件
  • 建立一個跟view相關的模型,然後將模型數據傳給view,通過模型上的數據給view的子控件賦值
/**
 *  純代碼初始化控件時一定會走這個方法
 */
- (instancetype)initWithFrame:(CGRect)frame
{
    if(self = [super initWithFrame:frame])
    {
        [self setup];
    }

    return self;
}

/**
 *  通過xib初始化控件時一定會走這個方法
 */
- (id)initWithCoder:(NSCoder *)aDecoder
{
    if(self = [super initWithCoder:aDecoder])
    {
        [self setup];
    }

    return self;
}

- (void)setup
{
    // 初始化代碼
}

如何進行iOS6、7的適配

  • 通過判斷版本來控制,來執行響應的代碼
  • 功能適配:保證同一個功能在6、7上都能用
  • UI適配:保證各自的顯示風格
// iOS版本爲7.0以上(包含7.0)
#define iOS7 ([[UIDevice currentDevice].systemVersion doubleValue]>=7.0)

如何渲染UILabel的文字?

  • 通過NSAttributedString/NSMutableAttributedString(富文本)

UIScrollView的contentSize能否在viewDidLoad中設置?

  • 因爲UIScrollView的內容尺寸是根據其內部的內容來決定的,所以是可以在viewDidLoad中設置的
  • 補充:(這僅僅是一種特殊情況)
    • 前提,控制器B是控制器A的一個子控制器,且控制器B的內容只在控制器A的view的部分區域中顯示
    • 假設控制器B的view中有一個UIScrollView這樣一個子控件
    • 如果此時在控制器B的viewDidLoad中設置UIScrollView的contentSize的話會導致不準確的問題
    • 因爲任何控制器的view在viewDidLoad的時候的尺寸都是不準確的,如果有子控件的尺寸依賴父控件的尺寸,在這個方法中設置會導致子控件的frame不準確,所以這時應該在下面的方法中設置子控件的尺寸
-(void)viewDidLayoutSubviews;

觸摸事件的傳遞

  • 觸摸事件的傳遞是從父控件傳遞到子控件
  • 如果父控件不能接收觸摸事件,那麼子控件就不可能接收到觸摸事件
  • 不能接受觸摸事件的四種情況
    • 不接收用戶交互,即:userInteractionEnabled = NO
    • 隱藏,即:hidden = YES
    • 透明,即:alpha <= 0.01
    • 未啓用,即:enabled = NO
  • 提示:UIImageView的userInteractionEnabled默認就是NO,因此UIImageView以及它的子控件默認是不能接收觸摸事件的
  • 如何找到最合適處理事件的控件:
    • 首先,判斷自己能否接收觸摸事件
    • 可以通過重寫hitTest:withEvent:方法驗證
    • 其次,判斷觸摸點是否在自己身上
    • 對應方法pointInside:withEvent:
    • 從後往前(先遍歷最後添加的子控件)遍歷子控件,重複前面的兩個步驟
    • 如果沒有符合條件的子控件,那麼就自己處理

事件響應者鏈

  • 如果當前view是控制器的view,那麼就傳遞給控制器
  • 如果控制器不存在,則將其傳遞給它的父控件
  • 在視圖層次結構的最頂層視圖也不能處理接收到的事件或消息,則將事件或消息傳遞給UIWindow對象進行處理
  • 如果UIWindow對象也不處理,則將事件或消息傳遞給UIApplication對象
  • 如果UIApplication也不能處理該事件或消息,則將其丟棄
  • 補充:如何判斷上一個響應者
    • 如果當前這個view是控制器的view,那麼控制器就是上一個響應者
    • 如果當前這個view不是控制器的view,那麼父控件就是上一個響應者

image

作爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個我的iOS交流羣:413038000,不管你是大牛還是小白都歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 大家一起交流學習成長!

推薦閱讀

iOS開發——最新 BAT面試題合集(持續更新中)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章