iOS面試中經常問的點 - 基礎問題(一)

其二發表於2018-05-04

iOS面試中經常問的點 - 基礎部分

我將iOS的一些學習視訊書籍資料總結在“碼農Style”公眾號裡,需要的小夥伴可以自行獲取。

想要一起探討學習iOS底層原理,架構的可以加我Q_2336684744歡迎一起學習交流

#improt與#inlcude的區別

OC程式的原始檔的字尾名是.m m代表message表示訊息機制。main 仍然是OC程式的入口和出口,main函式有一個int型別的返回值,代表程式的結束狀態。

#import預處理指令,是#inlcude指令的增強版,作用是將檔案的內容在預編譯的時候拷貝到寫指令的地方。 #import做了優化,同一個檔案無論#import多少次,都只會包含一次。 簡要原理:#import指令在包含檔案的時候,底層會先判斷這個檔案是否被包含,如果包含過就會略過。

因此#import #include主要區別在於使用#include需要處理重複引用,而#import能防止同一個檔案被多次包含,不需要處理重複引用。

框架是什麼

  1. 一個功能集蘋果或者第三方事先將一個協成員在開發程式的時候經常要用到的功能事先寫好。把這些功能封裝在一個類或者函式中,這些函式和類的集合就叫做框架。
  2. Foundation框架 Foundation:基礎框架,這個框架中提供了一些最基礎的功能,輸入和輸出,一些資料型別。

如何使用物件導向來設計程式

物件導向後期維護和修改十分方便。當我們遇到一個需求的時候,不要親自去實現。

  1. 先看看有沒有現成的人是專門做這件事情的,框架,如果有直接使用。
  2. 如果沒有就自己造一個擁有這樣功能的物件,並且創造出來的這個物件可以多次被使用。

類和物件如何定義

  1. 物件 - 具體 物件是現實生活中一個具體存在,看得見,摸得著,拿過來就可以直接使用
  2. 類 - 統稱 物以類聚,人以群分。 類時對一群具有相同特徵或者行為的事物的一個統稱,抽象的,不能直接使用,如果非要使用類的話,只能去類中找到類的具體存在,也就是物件,然後使用。

類和物件的關係

類是模板,類的物件是根據這個模板建立出來的,類别範本中有什麼,物件中就有什麼,絕不可能多,也絕不可能少。

如何設計一個類

設計類的三要素

  1. 類的名字。
  2. 這類事物具有的相同的特徵,這類事物用處是什麼。
  3. 這類事物的能幹什麼。

什麼是類載入

  1. 在建立物件的時候,肯定是需要訪問類的。
  2. 宣告一個類的指標變數也會訪問類的。

在程式執行期間,當某個類第一次被訪問到的時候,會將這個類儲存到記憶體中的程式碼段區域,這個過程叫做類載入。

只有類在第一次被訪問的時候,才會做類載入,並且一旦類被載入到程式碼段以後,直到程式結束的時候才會被釋放。

物件在記憶體中究竟是如何儲存的。

例:Person *p1 = [Person new];

  1. Person *p1; 會在棧記憶體中申請一塊空間,在棧記憶體中宣告1個Person型別的指標變數p1。p1是一個指標變數,那麼只能儲存地址。
  2. [person new];真正在記憶體中建立物件的其實是這句程式碼。
  3. new方法在堆記憶體中建立一塊合適大小的空間,然後在空間中根據類的模板建立物件。 類别範本中定義了什麼屬性,就把這些屬性依次宣告在物件之中。 物件中還有另外一個屬性,叫做isa ,是一個指標,指向物件所屬的類在程式碼段中的地址。
  4. 初始化物件的屬性,給物件的屬性賦預設值。 如果屬性的型別是基本資料型別,那麼就賦值為0 如果屬性的型別是c語言的指標型別,那麼就賦值為NULL 如果屬性的型別為OC語言的類指標型別,那麼就賦值為nil
  5. 注意 1). 物件中只有屬性沒有方法,屬性包括自己類的屬性,外加一個isa指標指向程式碼段中的類。 2). 如何訪問物件的屬性,指標名->屬性名 根據指標,找到指標指向的物件,在找到物件中的屬性來訪問。 3). 如何呼叫方法。[指標名 方法名]; 先根據指標名找到物件,物件發現要呼叫方法,在根據物件的isa指標找到類。然後呼叫類裡的方法。 4). 為什麼不把方法儲存到物件之中。 因為每一個物件的方法的程式碼實現都是一模一樣的,沒有必要為每一個物件都儲存一個方法,這樣的話就太浪費空間了,既然都一樣,那麼就只儲存一份在程式碼段中。 5). 物件屬性是有預設值的。

nil與NULL的區別

NULL 可以作為指標變數的值,如果一個指標變數的值是NULL值代表這個指標不指向記憶體中的任何一塊空間,其實等價於0。NULL其實是一個巨集,就是0。

nil 只能作為指標變數的值,代表這個指標變數不指向記憶體中任何空間。nil其實也等價於0,也是一個巨集,就是0。

所以NULL和nil其實是一樣的,雖然使用NULL的地方可以使用nil,但是不建議隨意使用。C指標用NULL OC的類指標用nil。

Person *p1 = nil;表示p1指標不指向任何物件。 如果一個指標的值為nil代表這個指標不指向任何物件,此時如果通過p1指標去訪問p1指標指向的物件的屬性,執行就會報錯。如果通過p1指標去呼叫物件的方法,執行不會報錯,但是方法不會執行。

多個指標指向同一個物件有什麼作用

同型別的指標變數之間是可以相互賦值的。p1,p2指向同一個物件,無論誰修改物件的屬性都會修改。因為他們指向同一塊記憶體空間。

物件和方法的區別

物件可以作為方法的引數也可以作為方法的返回值。 類的本質是我們自定義的一種資料型別,並且物件在記憶體中的大小是由我們自己決定的,資料型別是在記憶體中開闢空間的一個模板

當物件作為方法的引數傳遞的時候,是地址傳遞。所以,在方法內部通過形參去修改形參指向的物件的時候,會影響實參變數指向的物件的值。物件作為方法的返回值,返回的是物件的地址

物件作為類的屬性的本質。

屬性的本質是變數,在建立物件的時候,物件當中的屬性是按照類别範本中的規定逐個建立出來的。類别範本中屬性是什麼型別,那麼物件中的屬性就是什麼型別。 如果物件的屬性是另外一個類的物件,這個屬性僅僅是一個指標變數而已,並沒有物件產生。這個時候還要為這個屬性賦值一個物件的地址,才可以正常使用。 類别範本中屬性是什麼型別,物件當中的屬性就是什麼型別。

類方法的宣告和呼叫知否依賴物件

類方法的呼叫不依賴物件,如果要呼叫類方法不需要去建立物件。而是直接使用類名就可以呼叫類方法。

類方法和物件方法的呼叫過程。

類方法和物件方法的呼叫過程

類方法節約空間且效率高,因為呼叫類方法不需要建立物件。但是在類方法中不能直接訪問屬性。因為屬性只有在物件建立的時候才會建立在物件之中,而類方法在執行的時候有可能還沒有類物件,所以不能訪問屬性。但是我們可以在類方法中建立類物件。

同理,在類方法中也不能通過self直接呼叫當前類的其他的物件方法,因為物件方法只能通過物件來呼叫,在物件方法中可以直接呼叫類方法。

因此如果方法不需要直接訪問屬性,也不需要直接呼叫其他的物件方法,那麼我們就可以直接將這個方法定義為類方法。

如何理解繼承

  1. 子類從父類繼承,就意味著子類擁有了父類的所有成員,包括屬性和方法。
  2. 繼承是類在繼承,而不是物件在繼承,子類物件中擁有父類物件中的同樣的成員。

如果不是所有的子類都擁有的方法,那麼這個方法就不應該定義在父類之中,因為一旦定義在父類之中,那麼所有的子類都擁有該方法

繼承的特點

  1. 一個類只能有一個父類,不能有多個父類。
  2. 傳遞性,A繼承B,B繼承C 那麼A就同時擁有B和C的成員。

NSObject 是什麼

NSObject類是所有類的父類,NSObject類中包含了建立物件的方法,所以我們自己建立的類必須直接或者間接的繼承自NSObject。 NSObject中有一個isa指標的屬性,所以每一個子類物件中都有一個叫做isa的指標。

Super關鍵字的作用

  1. 子類中已經有父類的屬性,相當於子類中已經定義過父類的屬性,因此子類當中不能存在和父類同名的屬性,否則會出現衝突。
  2. 可以使用super關鍵字呼叫當前物件從父類繼承過來的物件方法。可以使用self 也可以使用 super
  3. 類方法也可以被子類繼承。子類可以直接呼叫父類的類方法。
  4. super只能用來呼叫父類的物件方法或者類方法,不能用來訪問屬性。
  5. 子類從父類繼承,相當於子類别範本中擁有了父類别範本中的所有成員。
  6. 建立一個子類物件,仍然是根據子類别範本來建立物件,只不過子類别範本中擁有父類的屬性和方法,也有子類自己的屬性和方法。
  7. 父類的方法用super 可讀性更高,我們很快就能知道這個方法是父類方法。

如何使用訪問修飾符

用來修飾屬性,可以限定物件的屬性在那一段範圍之中訪問。

@private : 私有,被其修飾的屬性只能在本類的內部訪問。 @protected: 受保護的 被其修飾的屬性只能在本類以及本類的子類中訪問。只能在本類和子類的方法實現中訪問。 @package: 被其修飾的屬性,可以在當前框架中訪問。 @public: 公共的,被其修飾的屬性可以在任意地方訪問。

如果不為屬性指定訪問修飾符 預設:protected 子類仍然可以繼承父類的私有屬性。就算父類的屬性是private,只不過在子類當中無法直接訪問從父類繼承過來的私有屬性,可以通過set get方法來訪問。

訪問修飾符的作用域 從寫訪問修飾符的地方開始往下,直到遇到另外一個訪問修飾符的或者結束大括弧為止,中間的所有的屬性都應用這個訪問修飾符。

使用建議 @public 無論什麼情況下都不要使用,屬性不要直接暴漏給外界。 @private 如果屬性只想在本類中使用,不想再子類中使用。 @protected 如果你希望屬性只在本類和本類的子類中使用。

訪問修飾符只能用來修飾屬性,不能用來修飾方法。

如何理解里氏替換原則

子類可以替換父類的位置,並且程式的功能不受影響。 即一個父類指標指向一個子類物件,可正常呼叫子類的方法和屬性。

LSP 里氏替換原則: 一個指標中不僅可以儲存本類物件的地址,還可以儲存子類物件的地址 如果一個指標的型別是NSObject型別的,那麼這個指標中可以儲存任意的OC物件的地址。 如果一個陣列的元素的型別是一個OC指標型別的,那麼這個陣列中不僅可以儲存本類物件還可以儲存子類物件。 如果一個陣列元素是NSObject指標型別,那就意味著任意型別的物件都可以存在陣列中。 如果一個方法的引數是一個物件,我們可以傳本類物件也可以傳子類物件 當一個父類指標指向1個子類物件的時候,通過父類指標就只能去呼叫子類物件中的父類成員。

如何方法重寫

當子類擁有父類的行為,但是子類的行為與父類不同。這個時候就可以通過重寫父類的方法來實現。

當一個父類指標指向一個子類物件的時候,通過這個父類指標呼叫的方法,如果子類物件中重寫了這個方法,呼叫的就是子類重寫的方法。

多型是什麼

指的是同一個行為,對於不同的事物具有完全不同的表現形式。同一個行為具備多種形態。子類重寫父類的方法就是多型。

是否使用過 description 方法

description方法是定義在NSObject之中的。我們通過重寫description方法來修改NSLog的輸出形式。NSLog的底層就是description方法。

結構體與類的異同點

相同點: 都可以將多個資料封裝為一個資料

不同點:

  1. 結構體只能封裝資料,而類不僅可以封裝資料還可以封裝行為。
  2. 結構體變數分配在棧空間 (區域性)
  3. 物件變數分配在堆空間
  4. 棧的特點:空間相對較小,但是儲存在棧中的資料訪問的效率更高一些
  5. 堆的特點:空間相對較大,但資料訪問的效率相對要低。

如果表示的實體沒有行為,只有屬性。 那麼如果屬性比較少,只有幾個,那麼這個時候就定義為結構體,分配在棧,提高效率。如果屬性比較多,不要定義成結構體,因為這樣結構體變數會在棧中佔據比較大的空間,導致訪問效率降低。

類的本質是什麼

類是以Class物件儲存在程式碼段中的

記憶體中的五大區域

  1. 棧 儲存區域性變數
  2. 堆 允許程式設計師自己申請的空間,需要程式設計師自己控制
  3. BSS段 儲存沒有初始化的全域性變數和靜態變數
  4. 資料段 用來儲存已經初始化的全域性變數,靜態變數還有常量
  5. 程式碼段 用來儲存程式的程式碼。

類載入:當類第一次被訪問的時候,這個類就會被載入到程式碼段儲存起來。

  1. 類什麼時候載入到程式碼段 類第一次被訪問的時候,類就會被載入到程式碼段儲存,即類載入時。

  2. 類以什麼樣的形式儲存在程式碼段。 任何儲存在記憶體中的資料都有一個資料型別,任何在記憶體中申請的空間也有自己的型別。那麼在程式碼段儲存類的那塊空間是什麼型別的? 在程式碼段中儲存類的步驟 1). 先在程式碼段中建立一個Class物件,Class是Foundation框架中的一個類,這個Class物件就是用來儲存類的資訊的。 2). 將類的資訊儲存在這個Class物件中 所以類是以Class物件的形式儲存在程式碼段的,儲存類的這個Class物件也叫作類物件,所以儲存類的類物件也有一個isa指標,指向儲存父類物件。

  3. 類一旦被載入到程式碼段之後,什麼時候會被釋放。 類一旦被載入到程式碼段之後是不會被回收的,除非程式結束。

  4. 如何拿到儲存在程式碼段中的類物件? 1). 呼叫類的類方法class就可以得到儲存類的類物件的地址。 2). 呼叫物件的物件方法 class就可以得到儲存這個物件所屬的類的class物件的地址。
    3). 物件中的isa指標的值其實就是程式碼段中儲存類的類物件的地址。 4). 拿到儲存類的類物件以後,完全等價於類 Class c1 = [Person class];c1物件就是Person類,c1完全等價於Person,可以使用類物件來呼叫類的類方法。 注意:宣告Class指標的時候,不需要加*因為在typedef的時候已經加了*

  5. 如何使用類物件 1). 拿到儲存類的類物件以後, Class c1 = [Person class]; c1物件就是Person類,c1物件完全等價於Person 2). 使用類物件來呼叫類的類方法。 3). 可以使用類物件來呼叫new方法,建立儲存在類物件中的類的物件。 4). 使用類物件,只能呼叫類的類方法,因為類物件就等價於存在其中的類

  6. 總結 1). 類是以Class物件的形式儲存在程式碼段之中的。 2). 可以使用類物件來呼叫類的類方法。 3). 通過class方法拿到儲存類的類物件。

###SEL選擇器 SEL其實是一個類,要在記憶體中申請空間儲存資料,SEL物件是用來儲存一個方法的。 類是以Class物件的形式儲存在程式碼段之中。那麼如何將方法儲存在類物件之中?

  1. 先建立一個SEL物件
  2. 將方法的資訊儲存在這個SEL物件之中
  3. SEL物件作為Class物件的一個屬性來儲存。
  4. 類似一個陣列的形式將所有的SEL物件儲存,即方法列表。SEL物件中儲存方法的資訊。

如何拿到儲存方法的SEL物件

  1. 因為SEL是一個typedef型別的,在自定義的時候已經加了*所以我們宣告SEL指標的時候不需要加*
  2. 取到儲存方法的SEL物件 SEL sel = @selector(sayHi);
  3. 呼叫方法的本質:[p1 sayHi]; 1). 先拿到儲存sayHi方法的SEL物件,也就是拿到儲存sayHi方法的SEL資料,SEL訊息。 2). 將SEL訊息傳送給p1物件。 3). p1物件接收到SEL訊息以後,就知道要呼叫方法 4). 根據物件的isa指標找到儲存類的類物件。 5). 找到這個類物件以後,在這個類物件中去搜尋是否有和傳入的SEL資料相匹配的方法。如果有就執行,如果沒有再找父類,直到NSObject。

OC最重要的1個機制:訊息機制,呼叫方法的本質其實就是為物件傳送SEL訊息,[p1 sayHi];表示為p1物件傳送1條sayHi訊息。

如何手動的為物件傳送SEL訊息

  1. 先得到方法的SEL資料。
  2. 將這個SEL訊息傳送給p1物件。 通過方法- (id)performSelector:(SEL)aSelector;手動傳送訊息 Person *p1 = [Person new]; SEL s1 = @selector(sayHi); [p1 performSelector:s1]; 與 [p1 sayHi]效果是完全一樣的.
// 帶一個引數和兩個引數的方法
// 如果有多個引數可以把引數封裝在一個物件裡面,傳遞物件
 - (id)performSelector:(SEL)aSelector withObject:(id)object;
 - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
複製程式碼

點語法的作用

使用點語法訪問物件的屬性。

語法:物件名.去掉下劃線的屬性名
p.name = @“jack”;
這個時候就會將@“jack”賦值給p物件的_name屬性。
複製程式碼

原理: 本質並不是把@"jack"直接賦值給p物件的_name屬性,實質上是呼叫set方法。

當使用點語法賦值的時候,編譯器會將點語法轉換為呼叫setter方法的程式碼。 當使用點語法取值的時候,編譯器會將點語法轉換為呼叫getter方法的程式碼。

在getter setter方法中慎用點語法,可能會造成無限遞迴而程式崩潰。 主要是看情況,get方法中如果點語法是呼叫set方法是可以使用的。 例如懶載入中我們是可以使用點語法為賦值的,因為懶載入是get方法,而賦值是呼叫set方法,所部不會遞迴呼叫。 如果屬性沒有封裝setter getter 是無法使用點語法的。

@property 的作用

@property 自動生成getter setter 方法的宣告。 原理:由編譯器在編譯的時候自動生成。

@synthesize 自動生成getter setter 方法的實現。 @synthesize age // age一定要是前面@property宣告過的。

  1. 生成一個真私有屬性,屬性的型別和@synthesize對應的@property型別一致,屬性的名字和@synthesize對應的@property名字一致。
  2. 自動生成setter方法的實現。實現的方式,將引數直接賦值給自動生成的那個私有屬性。 self->age = age;
  3. 自動生成getter方法的實現。將生成的私有屬性的值返回

希望@synthesize 不要生成私有屬性,setter getter 的實現中操作我們已經寫好的屬性就可以了。

@synthesize @property名稱 = 已經存在的屬性名;
@synthesize age = _age;
複製程式碼
  1. 不會再去生成私有屬性
  2. 直接生成setter getter的實現 setter的實現:把引數的值直接賦值給指定的屬性。getter實現:直接返回指定的屬性的值。

@property增強之後的作用

Xcode 4.4之後,只要寫一個@property 編譯器會自動生成私有屬性,並且自動生成getter setter 宣告和實現。

@property NSString *name;

  1. 自動的生成一個私有屬性,屬性的型別和@property型別一致,屬性的名稱和@property的名稱一致,屬性的名稱自動的加下劃線。
  2. 自動生成這個屬性的 setter getter方法的宣告和實現。直接將引數的值賦值給自動生成的私有屬性,直接返回生成的私有屬性的值。

@property生成set get方法和成員屬性,但是如果同時重寫了setter getter方法,那麼就不會自動生成私有屬性了。需要自己寫。 父類的property一樣可以被子類繼承,但是生成的屬性是私有的,可以通過setter getter方法來訪問

動態型別和靜態型別的區別

OC是一門弱語言,編譯器在編譯的時候,語法檢查的時候沒有那麼嚴格。 強型別的語言:編譯器在編譯的時候,做語法檢查的時候,就非常嚴格,行就是行,不行就是不行。

靜態型別 指的是一個指標指向的物件是一個本類物件。 動態型別 一個指標指向的物件不是本類物件。

編譯檢查時做什麼

編譯器在編譯的時候,檢查能否通過一個指標去呼叫指標指向的物件的方法。

判斷原則:看指標所屬的型別之中有沒有這個方法,如果有就認為可以呼叫,編譯通過,如果這個類中沒有,那麼編譯報錯。這就叫做編譯檢查,在編譯的時候,能不能呼叫物件的方法主要是看指標的型別,我們可以將指標的型別做轉換,來達到騙過編譯器的目的。

執行檢查時做什麼

編譯檢查只是騙過了編譯器,但是這個方法究竟能不能執行,所以在執行的時候執行檢查會去檢查物件中是否真的有這個方法,如果有就執行,如果沒有就報錯

編譯器,用編譯器將C程式碼OC程式碼轉化成二進位制,編譯器是個軟體,蘋果自己寫的叫做LLVM,可以編譯C OC Swift語言。

我們可以通過以下方法先來先判斷以下物件中是否有這個方法,如果有再去執行,如果沒有就別去執行,避免程式在沒有方法的時候報錯。

 1). 判斷物件中是否有這個方法可以執行.
     - (BOOL)respondsToSelector:(SEL)aSelector; (最常用)     
 2). 判斷類中是否有指定的類方法.
     + (BOOL)instancesRespondToSelector:(SEL)aSelector;
 3). 判斷指定的物件是否為 指定類的物件或者子類物件.
     - (BOOL)isKindOfClass:(Class)aClass;
 4). 判斷物件是否為指定類的物件 不包括子類.
     - (BOOL)isMemberOfClass:(Class)aClass;
 5). 判斷類是否為另外1個類的子類.
    + (BOOL)isSubclassOfClass:(Class)aClass;
複製程式碼

NSObject

OC中所有類的基類,根據LSP NSObject指標就可以指向任意的OC物件,所有NSObject指標是一個萬能指標,可以指向任意的OC物件 缺點:如果要呼叫指向的子類物件的獨有的方法,就必須要做型別轉換。

id指標是什麼

是一個萬能指標,可以指向任意的OC物件

  1. id是一個typedef自定義型別
  2. id指標,是1個萬能指標,可以指向任意的OC物件。 1). id是1個typedef自定義型別,在定義的時候已經加了*所以,宣告id指標的時候不需要再加*了。 2). id指標是1個萬能指標,任意的OC物件都可以指.
  3. NSObject和id的異同. 相同點: 萬能指標,都可以執行任意的OC物件。 不同點: 通過NSObject指標去呼叫物件的方法的時候,編譯器會做編譯檢查, 通過id型別的指標去呼叫物件的方法的時候,編譯器直接通過,無論呼叫什麼方法。 注意: id指標只能呼叫物件的方法,不能使用點語法,如果使用點語法就會直接報編譯錯誤 。如果要宣告1個萬能指標 千萬不要使用NSObject 而是使用id

instancetype 同 id的區別

id和instancetype的區別.

  1. instancetype只能作為方法的返回值,不能在別的地方使用。 id既可以宣告指標變數,也可以作為引數,也可以作為返回值。
  2. instancetype是1個有型別的代表當前類的物件。 id是1個無型別的指標,僅僅是1個地址,沒有型別的指標。

new方法的作用

建立物件,我們之前通過new方法 類名 *指標名 = [類名 new]; new實際上是1個類方法,其作用為:

  1. 建立物件。
  2. 初始化物件。
  3. 把物件的地址返回。

new方法的內部,其實是先呼叫的alloc方法,再呼叫的init方法。 alloc方法是1個類方法。作用: 那1個類呼叫這個方法就建立那個類的物件,並把物件返回,分配記憶體空間。 init方法是1個物件方法。作用: 初始化物件。

init方法 作用: 初始化物件,為物件的屬性賦初始值,這個init方法我們叫做構造方法。 init方法做的事情:初始化物件,併為物件的屬性賦預設值。 如果屬性的型別是基本資料型別就賦值為0,C指標賦值NULL,OC指標賦值nil。 所以,我們建立1個物件如果沒有為這個物件的屬性賦值這個物件的屬性是有預設值的。

重寫init方法的規範: 1). 必須要先呼叫父類的init方法,然後將方法的返回值賦值給self。 2). 呼叫init方法初始化物件有可能會失敗,如果初始化失敗,返回的就是nil。 3). 判斷父類是否初始化成功,判斷self的值是否為nil,如果不為nil說明初始化成功。 4). 如果初始化成功就初始化當前物件的屬性。 5). 最後 返回self的值。

自定義構造方法: 1). 自定義構造方法的返回值必須是instancetype。 2). 自定義構造方法的名稱必須以initWith開頭。 3). 方法的實現和init的要求一樣。


我將iOS的一些學習視訊書籍資料總結在“碼農Style”公眾號裡,需要的小夥伴可以自行獲取。

掃碼關注

相關文章