iOS面試題一(技術類)

weixin_34337265發表於2017-02-13

1、風格糾錯題:

844323-d5db1b4db0d1f793.jpg
51530583jw1eqo0v3zgr8j20qc0f2dja.jpg

要找出10處以上的錯誤,下面是我的答案:

// 1 處:列舉應當使用 NS_ENUM
typedef NS_ENUM(NSUInteger, UserSex) {
    UserSexMan = 0, // 2 處:沒必要用下劃線,駝峰法即可
    UserSexWoman
};

// 3 處:User 沒必要在後面帶Model,畫蛇添足
@interface User : NSObject

// 4 處:nonatomic 前面的括號不要挨著 @property,空一格
@property (nonatomic, copy) NSString *name; // 5 處:NSString 應當用 copy 修飾
@property (nonatomic, assign) NSUInteger age; // 6 處:儘量使用 NSUInteger,NSInteger等,age明顯應該使用無符號型別的
@property (nonatomic, assign) UserSex sex;

// 7 處:構造方法使用 instancetype   8 處:使用 initWith   9處:第一個引數前有 With,後面的不需要 With
- (instancetype)initWithName:(NSString *)name age:(NSUInteger)age;
// 10處:login已經是動詞,前面的 do 是畫蛇添足
- (void)login;

@end

有幾點:1、使用 NS_ENUM 的原因是用它寫列舉的時候有自動補全,另外在寫switch語句的時候編譯器也會幫你檢查是否覆蓋了所有的情況。2、NSString 使用 copy 修飾的原因是避免使用時將 NSMutableString 賦值給 name 屬性,導致 name 是可變的。3、使用 NSUInteger,NSInteger,不建議使用 int 的原因是不用去考慮系統是32位還是64位。4、instancetype 只能用於返回值,可以返回和方法所在類相同型別的物件,而id返回的是未知物件,用 instancetype 構造方法的返回值的好處是讓編譯器知道返回型別,從而更好的定位書寫問題。

2、@property 後面可以有哪些修飾符?
1類:nonatomic 和 atomic,非原子屬性和原子屬性,預設為 atomic,實際上 atomic 並不能完全保證執行緒安全,用的較多的是 nonatomic;
2類:setter 修飾符,用於描述setter如何實現:copy,strong,assign,retain,weak等;
3類:readonly,readwrite 等;
4類:getter=xxx,自定義 getter方法名稱。

3、什麼情況使用 weak 關鍵字,相比 assign 有什麼不同?
1點:weak 用來修飾物件,assign 主要用來修飾非 OC 物件,當然也可以用 assign 來修飾 OC 物件,它們的共同點都是弱引用,區別在於 weak 在物件被釋放的時候會自動置為 nil,但是 assign 不會。比如 delegate 物件,用 weak 來修飾會更安全。

*4、怎麼用 copy 關鍵字?這個寫法會出什麼問題:@property (copy) NSMutableArray array;?
copy 關鍵字一般用於 NSString,NSArray,NSSet,NSDictionary,block 等屬性的修飾上。
copy 修飾的屬性在 setter 方法中會將賦值的物件呼叫“copy”方法拷貝一份,然後將指標傳給屬性儲存。這個寫法會導致下面的程式碼直接崩潰掉:

@interface KTObject : NSObject

@property (copy) NSMutableArray *array;

@end

@implementation KTObject

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    KTObject *ob = [[KTObject alloc] init];
    ob.array = [NSMutableArray array]; // 2
    [ob.array addObject:@"test"];
}

在上面的程式碼 2 處,將一個 NSMutableArray 物件賦值給 array 的時候,會呼叫 setter,而 array 是用 copy 修飾的,setter 方法裡會先呼叫 NSMutableArray 物件的 copy 方法生成一個不可變的物件,然後將它的指標傳給array 屬性引用,那麼後面對不可變物件呼叫 addObject 會崩潰。
另外最好使用 nonatomic 關鍵字,不寫的話預設使用 atomic,影響效能。

5、如何讓自己的類能用 copy 修飾符?如何重寫帶 copy 關鍵字的 setter?
實現 NSCopying 協議,實現協議中的 copyWithZone 方法:

- (id)copyWithZone:(NSZone *)zone { 
      CYLUser *copy = [[[self class] allocWithZone:zone] 
                                      initWithName:_name
                                               age:_age sex:_sex];
      return copy;
}

重寫帶copy關鍵字的方法:

- (void)setArray:(NSMutableArray *)array
{
    _array = [array copy];
}

另外,在初始化方法中也需要考慮到copy,比如下面這樣寫,因為傳進來的引數 array 有可能是 mutable 的。

- (instancetype)initWithArray:(NSArray *)array
{
    if (self = [super init]) {
        _array = [array copy];
    }
    
    return self;
}

6、@property 的本質是什麼?ivar、getter、setter 是如何生成並新增到這個類中的?
@property = ivar + getter + setter;
“屬性” (property) 有兩大概念:ivar(例項變數)、存取方法(access method = getter + setter)。“屬性” (property) 作為 Objective-C 的一項特性,主要的作用就在於封裝物件中的資料。 Objective-C 物件通常會把其所需要的資料儲存為各種例項變數。例項變數一般通過“存取方法” (access method) 來訪問。其中,“獲取方法” (getter) 用於讀取變數值,而“設定方法” (setter) 用於寫入變數值。這個概念已經定型,並且經由“屬性”這一特性而成為 Objective-C 2.0 的一部分。 而在正規的 Objective-C 編碼風格中,存取方法有著嚴格的命名規範。 正因為有了這種嚴格的命名規範,所以 Objective-C 這門語言才能根據名稱自動建立出存取方法。其實也可以把屬性當做一種關鍵字,其表示:編譯器會自動寫出一套存取方法,用以訪問給定型別中具有給定名稱的變數。例如下面這個類:

@interface Person : NSObject 
@property NSString *firstName; 
@property NSString *lastName; 
@end

上述程式碼寫出來的類與下面這種寫法等效:

@interface Person : NSObject 
- (NSString *)firstName; 
- (void)setFirstName:(NSString *)firstName; 
- (NSString *)lastName; 
- (void)setLastName:(NSString *)lastName; 
@end

ivar、getter、setter 是如何生成並新增到這個類中的?
“自動合成”( autosynthesis):完成屬性定義後,編譯器會自動編寫訪問這些屬性所需的方法,此過程叫做“自動合成”( autosynthesis)。需要強調的是,這個過程由編譯器在編譯期執行,所以編輯器裡看不到這些“合成方法”(synthesized method)的原始碼。除了生成方法程式碼 getter、setter 之外,編譯器還要自動向類中新增適當型別的例項變數,並且在屬性名前面加下劃線,以此作為例項變數的名字。在前例中,會生成兩個例項變數,其名稱分別為 _firstName與_lastName。也可以在類的實現程式碼裡通過 @synthesize語法來指定例項變數的名字:

@implementation Person 
@synthesize firstName = _myFirstName; 
@synthesize lastName = myLastName; 
@end

我為了搞清屬性是怎麼實現的,曾經反編譯過相關的程式碼,大致生成了五個東西:
1)OBJC_IVAR_$類名$屬性名稱 :該屬性的“偏移量” (offset),這個偏移量是“硬編碼” (hardcode),表示該變數距離存放物件的記憶體區域的起始地址有多遠。
2)setter與getter方法對應的實現函式
3)ivar_list :成員變數列表
4)method_list :方法列表
也就是說我們每次在增加一個屬性,系統都會在ivar_list中新增一個成員變數的描述,在method_list中增加setter與getter方法的描述,在屬性列表中增加一個屬性的描述,然後計算該屬性在物件中的偏移量,然後給出setter與getter方法對應的實現,在setter方法中從偏移量的位置開始賦值,在getter方法中從偏移量開始取值,為了能夠讀取正確位元組數,系統物件偏移量的指標型別進行了型別強轉。

7、@protocol 和 category 中如何使用 @property?
1)在 protocol 中使用 property 只會生成 setter 和 getter 方法宣告,我們使用屬性的目的,是希望遵守我協議的物件能實現該屬性,那麼遵守協議的物件需要實現該setter和setter。
2)category 使用 @property 也是隻會生成 setter 和 getter 方法的宣告,我們需要實現相應的setter和getter;如果我們真的需要給category增加屬性相對應的例項變數,需要藉助於執行時的兩個函式:
①objc_setAssociatedObject
②objc_getAssociatedObject

8、runtime 如何實現 weak 屬性?
系統有一個全域性的weak表,當執行下面的語句的時候:

id __weak weakObject = object;

實際上編譯器把它變成了下面的程式碼:

id weakObject = 0;
objc_storeWeak(&weakObject, object);

objc_storeWeak函式的作用是以object物件的地址為鍵,以__weak修飾的變數的地址(注意這裡是&weakObject)為值存到weak表中,一個object可以儲存多個__weak修飾的變數的地址,當object dealloc的時候,會去這個weak表中找出以object物件的地址為鍵的值,然後將其賦值為nil。

9、@synthesize和@dynamic分別有什麼作用?
1)@property有兩個對應的詞,一個是@synthesize,一個是@dynamic。如果@synthesize和@dynamic都沒寫,那麼預設的就是@syntheszie var = _var;
2)@synthesize的語義是如果你沒有手動實現setter方法和getter方法,那麼編譯器會自動為你加上這兩個方法,@synthesize可以指定屬性對應的例項變數的名字;
3)@dynamic告訴編譯器:屬性的setter與getter方法由使用者自己實現,不自動生成(當然對於readonly的屬性只需提供getter即可)。假如一個屬性被宣告為@dynamic var,然後你沒有提供@setter方法和@getter方法,編譯的時候沒問題,但是當程式執行到instance.var = someVar,由於缺setter方法會導致程式崩潰;或者當執行到 someVar = var時,由於缺getter方法同樣會導致崩潰。編譯時沒問題,執行時才執行相應的方法,這就是所謂的動態繫結。

10、ARC下,不顯式指定任何屬性關鍵字時,預設的關鍵字都有哪些?
1)對應基本資料型別預設關鍵字是:atomic, readwrite, assign;
2)對於普通的OC物件是:atomic, readwrite, strong。

11、用@property宣告的NSString(或NSArray,NSDictionary)經常使用copy關鍵字,為什麼?如果改用strong關鍵字,可能造成什麼問題?
1)因為父類指標可以指向子類物件, 使用copy的目的是為了讓本物件的屬性不受外界影響, 使用copy無論給我傳入是一個可變物件還是不可物件, 我本身持有的就是一個不可變的副本;
2)如果我們使用是strong, 那麼這個屬性就有可能指向一個可變物件, 如果這個可變物件在外部被修改了, 那麼會影響該屬性。
copy此特質所表達的所屬關係與strong類似。然而設定方法並不保留新值,而是將其“拷貝” (copy)。 當屬性型別為NSString時,經常用此特質來保護其封裝性,因為傳遞給設定方法的新值有可能指向一個NSMutableString類的例項。這個類是NSString的子類,表示一種可修改其值的字串,此時若是不拷貝字串,那麼設定完屬性之後,字串的值就可能會在物件不知情的情況下遭人更改。所以,這時就要拷貝一份“不可變” (immutable)的字串,確保物件中的字串值不會無意間變動。只要實現屬性所用的物件是“可變的” (mutable),就應該在設定新屬性值時拷貝一份。

12、@synthesize合成例項變數的規則是什麼?假如property名為foo,存在一個名為_foo的例項變數,那麼還會自動合成新變數麼?
1)如果指定了成員變數的名稱, 會生成一個指定的名稱的成員變數;
2)如果這個成員已經存在了就不再生成了;
3)如果是 @synthesize foo,還會生成一個名稱為foo的成員變數,也就是說:如果沒有指定成員變數的名稱會自動生成一個屬性同名的成員變數;
4)如果是 @synthesize foo = _foo,就不會生成成員變數了;

13、在有了自動合成屬性例項變數之後,@synthesize還有哪些使用場景?
回答這個問題前,我們要搞清楚一個問題,什麼情況下不會autosynthesis(自動合成)?
1)同時重寫了setter和getter時
2)重寫了只讀屬性的getter時
3)使用了@dynamic時
4)在 @protocol 中定義的所有屬性
5)在 category 中定義的所有屬性
6)過載的屬性
當你在子類中過載了父類中的屬性,你必須使用@synthesize來手動合成ivar。
其實,@synthesize語法還有一個應用場景,但是不太建議大家使用:可以在類的實現程式碼裡通過@synthesize語法來指定例項變數的名字,但是不建議這樣使用。

14、objc中向一個nil物件傳送訊息將會發生什麼?
不會幹任何事。

15、objc中向一個物件傳送訊息 [obj foo] 和 objc_msgSend()函式之間有什麼關係?
[obj foo]在objc動態編譯時,會被轉意為:objc_msgSend(obj, @selector(foo));。

16、什麼時候會報unrecognized selector的異常?
1、對一個物件呼叫方法,會被轉為訊息傳送,objc_msgSend(receiver, selector)。首先會根據物件的isa指標查詢有沒有方法實現,找到就呼叫,找不到就繼續往父類查詢,如果一直到根類還找不到方法實現,那麼會進入下面的流程:
2、動態方法解析:objc執行時會呼叫 +resolveInstanceMethod: 或者 +resolveClassMethod:,讓你有機會提供一個函式實現。如果你新增了函式並返回 YES,那執行時系統就會重新啟動一次訊息傳送的過程,一般可以在這個時候動態插入方法實現,比如用class_addMethod函式新增方法實現。如果 resolve 方法返回 NO ,執行時就會移到下一步,訊息轉發(Message Forwarding)。
3、(Fast Fowarding)快速訊息轉發:(備援接收者)此時會呼叫方法 -forwardingTargetForSelector: 選文能否將訊息轉給其他接收者,只要這個方法返回的不是nil和self,整個訊息傳送的過程就會被重啟,當然傳送的物件會變成你返回的那個物件。否則,就會繼續Normal Fowarding。 這裡叫Fast,只是為了區別下一步的轉發機制。因為這一步不會建立任何新的物件,但下一步轉發會建立一個NSInvocation物件,所以相對更快點。
4、(Normal Fowarding)完整的訊息轉發:這一步是Runtime最後一次給你挽救的機會。首先它會傳送-methodSignatureForSelector:訊息獲得函式的引數和返回值型別。如果-methodSignatureForSelector:返回nil,Runtime則會發出-doesNotRecognizeSelector:訊息,程式這時也就掛掉了。如果返回了一個函式簽名,Runtime就會建立一個NSInvocation物件併傳送-forwardInvocation:訊息給目標物件。通常到了這一步,實現方式為:在觸發訊息前,先以某種方式改變訊息內容,比如追加引數,更換選擇子等等。

17、一個objc物件如何進行記憶體佈局?(考慮有父類的情況)
物件實際上是一個結構體:

typedef struct objc_class *Class;
struct objc_class {
  Class isa; // 指向metaclass
  
  Class super_class ; // 指向其父類
  const char *name ; // 類名
  long version ; // 類的版本資訊,初始化預設為0,可以通過runtime函式class_setVersion和class_getVersion進行修改、讀取
  long info; // 一些標識資訊,如CLS_CLASS (0x1L) 表示該類為普通 class ,其中包含物件方法和成員變數;CLS_META (0x2L) 表示該類為 metaclass,其中包含類方法;
  long instance_size ; // 該類的例項變數大小(包括從父類繼承下來的例項變數);
  struct objc_ivar_list *ivars; // 用於儲存每個成員變數的地址
  struct objc_method_list **methodLists ; // 與 info 的一些標誌位有關,如CLS_CLASS (0x1L),則儲存物件方法,如CLS_META (0x2L),則儲存類方法;
  struct objc_cache *cache; // 指向最近使用的方法的指標,用於提升效率;
  struct objc_protocol_list *protocols; // 儲存該類遵守的協議
}

所有父類的成員變數和自己的成員變數都會存放在該物件所對應的儲存空間中,每一個物件內部都有一個isa指標,指向他的類物件,類物件中存放著本物件的下列內容,而類物件內部也有一個isa指標指向元類物件(meta class),元類物件內部存放的是類方法列表,類物件內部還有一個superclass的指標,指向他的父類物件,對應的元類物件的superclass指標則指向父類的元類物件。如下圖所示:

844323-0c0e16745553fec4.png
Paste_Image.png

18、一個objc物件的isa的指標指向什麼?有什麼作用?
指向他的類物件,從而可以找到物件上的方法實現。

19、下面的程式碼輸出什麼?

@implementation Son : Father
- (id)init {
     self = [super init];
     if (self) {
           NSLog(@"%@", NSStringFromClass([self class]));
           NSLog(@"%@", NSStringFromClass([super class])); 
     }
     return self;
}
@end

當呼叫 [self class] 時,實際先呼叫的是 objc_msgSend 函式,第一個引數是 Son當前的這個例項,然後在 Son 這個類裡面去找 - (Class)class這個方法,沒有,去父類 Father裡找,也沒有,最後在 NSObject類中發現這個方法。而 - (Class)class的實現就是返回self的類別,故上述輸出結果為 Son。
而當呼叫 [super class] 時,會轉換成 objc_msgSendSuper 函式。是這樣呼叫的:objc_msgSendSuper(struct objc_super *super, SEL op, ...),第一步先構造 objc_super 結構體,結構體是這樣定義的:

objc_msgSendSuper({self, class_getSuperclass(objc_getClass("Son"))}, sel_registerName("class"));

struct objc_super {
   __unsafe_unretained id receiver;
   __unsafe_unretained Class super_class;
};

第一個成員就是 self 。 第二個成員是 (id)class_getSuperclass(objc_getClass(“Son”)) , 實際該函式輸出結果為 Father。 第二步是去 Father這個類裡去找 - (Class)class,沒有,然後去NSObject類去找,找到了。最後內部是使用 objc_msgSend(objc_super->receiver, @selector(class))去呼叫, 此時已經和[self class]呼叫相同了,故上述輸出結果仍然返回 Son。

20、runtime如何通過selector找到對應的IMP地址?(分別考慮類方法和例項方法)
每一個物件的例項都有一個isa指標,指向對應的類物件,而類物件中都有一個方法列表,方法列表中記錄著方法的名稱,方法實現,以及引數型別,其實 selector 本質就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的例項方法實現,如果找不到就去父類的類物件的方法列表中找。而類物件也有一個isa指標指向元類物件,元類物件的方法列表裡面記錄的是類方法的名稱,實現,引數型別,相應的類方法也可以找到。

21、使用runtime Associate方法關聯的物件,需要在主物件dealloc的時候釋放麼?
在ARC下不需要,在MRC中,對於使用retain或copy策略的需要。

22、objc中的類方法和例項方法有什麼本質區別和聯絡?
1)類方法:
類方法是屬於類物件的
類方法只能通過類物件呼叫
類方法中的self是類物件
類方法可以呼叫其他的類方法
類方法中不能訪問成員變數
類方法中不能直接呼叫物件方法
2)例項方法:
例項方法是屬於例項物件的
例項方法只能通過例項物件呼叫
例項方法中的self是例項物件
例項方法中可以訪問成員變數
例項方法中可以直接呼叫例項方法
例項方法中也可以呼叫類方法(通過類名)

23、_objc_msgForward 函式是做什麼的,直接呼叫它將會發生什麼?
前面曾提到objc_msgSend在“訊息傳遞”中的作用。在“訊息傳遞”過程中,objc_msgSend的動作比較清晰:首先在 Class 中的快取查詢 IMP(沒快取則初始化快取),如果沒找到,則向父類的 Class 查詢。如果一直查詢到根類仍舊沒有實現,則用_objc_msgForward函式指標代替 IMP ,最後,執行這個 IMP 。
直接呼叫_objc_msgForward是非常危險的事,如果用不好會直接導致程式Crash,但是如果用得好,能做很多非常酷的事。就好像跑酷,幹得好,叫“耍酷”,幹不好就叫“作死”。一旦呼叫_objc_msgForward,將跳過查詢 IMP 的過程,直接觸發“訊息轉發”,如果呼叫了_objc_msgForward,即使這個物件確實已經實現了這個方法,你也也相當於告訴objc_msgSend:“我沒有在這個物件裡找到這個方法的實現”。
有哪些場景需要直接呼叫_objc_msgForward?最常見的場景是:你想獲取某方法所對應的NSInvocation物件。舉例說明:JSPatch (Github 連結)就是直接呼叫_objc_msgForward來實現其核心功能的:同時 RAC(ReactiveCocoa)原始碼中也用到了該方法。

24、UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))的作用是?
這個函式被呼叫的主入口點建立應用程式的物件和應用程式委託和事件迴圈。注意這個函式雖有返回值,但這個函式在程式沒有結束時,永遠不會返回。

25、能否向編譯後得到的類中增加例項變數?能否向執行時建立的類中新增例項變數?為什麼?
不能向編譯後得到的類中增加例項變數;能向執行時建立的類中新增例項變數;因為編譯後的類已經註冊在 runtime 中,類結構體中的 objc_ivar_list 例項變數的連結串列和 instance_size 例項變數的記憶體大小已經確定,同時runtime 會呼叫 class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用。所以不能向存在的類中新增例項變數;執行時建立的類是可以新增例項變數,呼叫 class_addIvar 函式。但是得在呼叫 objc_allocateClassPair 之後,objc_registerClassPair 之前,原因同上。

26、runloop和執行緒有什麼關係?
Runloop與執行緒是一一對應的。
1、主執行緒的run loop預設是啟動的,iOS的應用程式裡面,程式啟動後會有一個如下的main()函式:

int main(int argc, char * argv[]) {
@autoreleasepool {
 return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

重點是UIApplicationMain()函式,這個方法會為main thread設定一個NSRunLoop物件,這就解釋了:為什麼我們的應用可以在無人操作的時候休息,需要讓它幹活的時候又能立馬響應。
2、對其它執行緒來說,run loop預設是沒有建立的,如果你需要更多的執行緒互動則可以手動配置和啟動,如果執行緒只是去執行一個長時間的已確定的任務則不需要。在任何一個 Cocoa 程式的執行緒中,都可以通過以下程式碼來獲取到當前執行緒的 run loop,此時runloop會被建立出來。

NSRunLoop *runloop = [NSRunLoop currentRunLoop];

RunLoop有幾種事件源?
有兩種種:Input sources 和 Timer sources。

28、runloop的mode作用是什麼?
Run Loop Mode 是指要被監聽的事件源(包括 Input sources 和 Timer sources)的集合 + 要被通知的 run-loop observers 的集合,mode 主要是用來指定事件在執行迴圈中的優先順序的,分為:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):預設,空閒狀態
UITrackingRunLoopMode:ScrollView滑動時
UIInitializationRunLoopMode:啟動時
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合
蘋果公開提供的 Mode 有兩個:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)
比如在在scrollView滑動的時候,主執行緒的runloop是處於UITrackingRunLoopMode的,此時一般的nstimer事件都不會相應。

29、以+ scheduledTimerWithTimeInterval...的方式觸發的timer,在滑動頁面上的列表時,timer會暫定回撥,為什麼?如何解決?
RunLoop只能執行在一種mode下,如果要換mode,當前的loop也需要停下重啟成新的。利用這個機制,ScrollView滾動過程中NSDefaultRunLoopMode(kCFRunLoopDefaultMode)的mode會切換到UITrackingRunLoopMode來保證ScrollView的流暢滑動。可以通過將timer新增到NSRunLoopCommonModes(kCFRunLoopCommonModes)來解決。

30、猜想runloop內部是如何實現的?
一般來講,一個執行緒一次只能執行一個任務,執行完成後執行緒就會退出。如果我們需要一個機制,讓執行緒能隨時處理事件但並不退出,通常的程式碼邏輯是這樣的:

function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

31、objc使用什麼機制管理物件記憶體?
通過引用計數的機制來決定物件是否需要釋放,當引用計數為0的時候就釋放。

32、ARC通過什麼方式幫助開發者管理記憶體?
編譯器編譯時根據程式碼上下文,自動插入retain/release,autorelease等程式碼。

33、不手動指定autoreleasepool的前提下,一個autorealese物件在什麼時刻釋放?(比如在一個vc的viewDidLoad中建立)?
分兩種情況:手動干預釋放時機、系統自動去釋放。
手動干預釋放時機:指定autoreleasepool當前作用域大括號結束時會釋放。
系統自動去釋放:不手動指定autoreleasepool。Autorelease物件出了作用域之後,會被新增到最近一次建立的autoreleasepool中,並會在當前的 runloop 迭代結束時釋放。
從程式啟動到載入完成是一個完整的執行迴圈,然後會停下來,等待使用者互動,使用者的每一次互動都會啟動一次執行迴圈,來處理使用者所有的點選事件、觸控事件。我們都知道: 所有 autorelease 的物件,在出了作用域之後,會被自動新增到最近建立的自動釋放池中。但是如果每次都放進應用程式的 main.m 中的 autoreleasepool 中,遲早有被撐滿的一刻。這個過程中必定有一個釋放的動作。何時?在一次完整的執行迴圈結束之前,會被銷燬。那什麼時間會建立自動釋放池?執行迴圈檢測到事件並啟動後,就會建立自動釋放池。
@autoreleasepool 當自動釋放池被銷燬或者耗盡時,會向自動釋放池中的所有物件傳送 release 訊息,釋放自動釋放池中的所有物件。
如果在一個vc的viewDidLoad中建立一個 Autorelease物件,那麼該物件會在 viewDidAppear 方法執行前就被銷燬了。
參考:http://blog.sunnyxx.com/2014/10/15/behind-autorelease/

34、BAD_ACCESS在什麼情況下出現?
對已經釋放的物件發訊息,或訪問其成員變數。

35、蘋果是如何實現autoreleasepool的?
autoreleasepool以一個佇列陣列的形式實現,主要通過下列三個函式完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
看函式名就可以知道,對autorelease分別執行push,和pop操作。銷燬物件時執行release操作。

36、使用block時什麼情況會發生引用迴圈,如何解決?
比如一個物件強引用了block,在block中又使用了該物件,就會發生迴圈引用。 解決方法是將該物件使用 __weak 或者__block 修飾符修飾之後再在block中使用。
1、__weak typeof(self) weakSelf = self;
2、id __block weakSelf = self。
參考:http://www.cocoachina.com/ios/20150106/10850.html

37、在block內如何修改block外部變數?
用__block宣告外部變數,或者static變數,或者全域性變數。

38、使用系統的某些block api(如UIView的block版本寫動畫時),是否也考慮引用迴圈問題?
不用考慮,此時只有block引用物件,而物件並不會引用block,並不會造成迴圈引用。

39、GCD的佇列(dispatch_queue_t)分哪兩種型別?
序列佇列Serial Dispatch Queue
並行佇列Concurrent Dispatch Queue

queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", 
DISPATCH_QUEUE_CONCURRENT);

40、如何用GCD同步若干個非同步呼叫?(如根據若干個url非同步載入多張圖片,然後在都下載完成後合成一張整圖)
使用Dispatch Group追加block到並行Queue,這些block如果全部執行完畢,就會執行Main Dispatch Queue中的結束處理的block。

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(), ^{
        // 合併圖片
});

41、dispatch_barrier_async的作用是什麼?
在並行佇列中,為了保持某些任務的順序,需要等待一些任務完成後才能繼續進行,使用 barrier 來等待之前任務完成,避免資料競爭等問題。 dispatch_barrier_async 函式會等待追加到Concurrent Dispatch Queue並行佇列中的操作全部執行完之後,然後再執行 dispatch_barrier_async 函式追加的處理,等 dispatch_barrier_async 追加的處理執行結束之後,Concurrent Dispatch Queue才恢復之前的動作繼續執行。
打個比方:比如你們公司週末跟團旅遊,高速休息站上,司機說:大家都去上廁所,速戰速決,上完廁所就上高速。超大的公共廁所,大家同時去,程式猿很快就結束了,但程式媛就可能會慢一些,即使你第一個回來,司機也不會出發,司機要等待所有人都回來後,才能出發。 dispatch_barrier_async 函式追加的內容就如同 “上完廁所就上高速”這個動作。

42、蘋果為什麼要廢棄dispatch_get_current_queue?
《Effective Objective-C》上面有解釋,同步到佇列的時候容易造成死鎖。
參考:
http://www.cnblogs.com/javawebsoa/archive/2013/08/01/3231015.html
http://blog.csdn.net/yiyaaixuexi/article/details/17752925

43、以下程式碼執行結果如何?

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
        // 此處返回
    });
    NSLog(@"3");
}

主執行緒死鎖,dispatch_sync表示同步呼叫,也就是說要等待block中的程式碼執行完畢之後,才會返回,然後再執行“NSLog(@"3");”這行程式碼。然而dispatch_sync的第一個引數是主執行緒,也就是說這個block會丟到主執行緒去執行。那麼此時主執行緒正在執行dispatch_sync,等待dispatch_sync的返回,dispatch_sync要等block執行完畢後才能返回,而block此時又在等待主執行緒空閒才能執行,因此死鎖。

44、addObserver:forKeyPath:options:context:各個引數的作用分別是什麼,observer中需要實現哪個方法才能獲得KVO回撥?

/*
1 觀察者,負責處理監聽事件的物件
2 觀察的屬性
3 觀察的選項,有4個值,分別是:
  NSKeyValueObservingOptionNew 把更改之後的新值提供給處理方法
  NSKeyValueObservingOptionOld 把更改之前的舊值提供給處理方法
  NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦註冊,立馬就會呼叫一次
  NSKeyValueObservingOptionPrior 分2次呼叫。在值改變之前和值改變之後
4 上下文:可以帶入一些引數,其實這個挺好用的,任何型別都可以,自己強轉就好了
*/
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person Name"];

獲得回撥,需要實現下面的方法:

/*
 1. 觀察的屬性
 2. 觀察的物件
 3. change 屬性變化字典(新/舊)
 4. 上下文,與監聽的時候傳遞的一致
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;

45、如何手動觸發一個value的KVO?
手動呼叫willChangeValueForKey和didChangevlueForKey。

*46、若一個類有例項變數NSString _foo,呼叫setValue:forKey:時,可以以foo還是_foo作為key?
都可以。當呼叫 setValue:屬性值 forKey:@“key” 的程式碼時,底層的執行機制如下:

  1. 首先搜尋set<Key>:方法,如果成員用@property,@synthsize處理,因為@synthsize告訴編譯器自動生成set<Key>:格式的setter方法,所以這種情況下會直接搜尋到。注意:這裡的<Key>是指成員名,而且首字母大寫,下同;
  2. 上面的setter方法沒有找到,如果類方法accessInstanceVariablesDirectly返YES(注:這是NSKeyValueCodingCatogery中實現的類方法,預設實現為返回YES)。那麼按_<key>,_is<Key>,<key>,is<key>的順序搜尋成員名。
  3. 如果找到設定成員的值,如果沒有呼叫setValue:forUndefinedKey:。

當呼叫ValueforKey:@”name“的程式碼時,KVC對key的搜尋方式不同於setValue:屬性值 forKey:@”name“,其搜尋方式如下
首先按get<Key>,<key>,is<Key>的順序方法查詢getter方法,找到的話會直接呼叫。如果是BOOL或者int等值型別, 會做NSNumber轉換
如果上面的getter沒有找到,KVC則會查詢countOf<Key>,objectIn<Key>AtIndex,<Key>AtIndex格式的方法。如果countOf<Key>和另外兩個方法中的要個被找到,那麼就會返回一個可以響應NSArray所的方法的代理集合(它是NSKeyValueArray,是NSArray的子類),呼叫這個代理集合的方法,或者說給這個代理集合傳送NSArray的方法,就會以countOf<Key>,objectIn<Key>AtIndex,<Key>AtIndex這幾個方法組合的形式呼叫。還有一個可選的get<Ket>:range:方法。所以你想重新定義KVC的一些功能,你可以新增這些方法,需要注意的是你的方法名要符合KVC的標準命名方法,包括方法簽名。
如果上面的方法沒有找到,那麼會查詢countOf<Key>,enumeratorOf<Key>,memberOf<Key>格式的方法。如果這三個方法都找到,那麼就返回一個可以響應NSSet所的方法的代理集合,以送給這個代理集合訊息方法,就會以countOf<Key>,enumeratorOf<Key>,memberOf<Key>組合的形式呼叫。
如果還沒有找到,再檢查類方法+ (BOOL)accessInstanceVariablesDirectly,如果返回YES(預設行為),那麼和先前的設值一樣,會按_<key>,_is<Key>,<key>,is<Key>的順序搜尋成員變數名,這裡不推薦這麼做,因為這樣直接訪問例項變數破壞了封裝性,使程式碼更脆弱。如果重寫了類方法+ (BOOL)accessInstanceVariablesDirectly返回NO的話,那麼會直接呼叫valueForUndefinedKey。
參考:http://www.cnblogs.com/Free-Thinker/p/5919043.html

47、KVC的keyPath中的集合運算子如何使用?
1、必須用在集合物件上或普通物件的集合屬性上;
2、簡單集合運算子有@avg, @count , @max , @min ,@sum;
3、格式 @"@sum.age"或 @"集合屬性.@max.age"。

48、KVC和KVO的keyPath一定是屬性麼?
不一定,KVO的基礎是KVC,在KVC中,最重要的是下面幾個方法:

- (nullable id)valueForKey:(NSString *)key; //直接通過Key來取值
- (void)setValue:(nullable id)value forKey:(NSString *)key; //通過Key來設值
- (nullable id)valueForKeyPath:(NSString *)keyPath; //通過KeyPath來取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通過KeyPath來設值

其中key方法是keyPath方法的基礎,這裡的key不一定要是屬性,也可以是例項變數或者屬性路徑。
參考:http://www.cnblogs.com/Free-Thinker/p/5919043.html

49、如何關閉預設的KVO的預設實現,並進入自定義的KVO實現?
+ (BOOL)automaticallyNotifiesObserversForxxx方法返回NO,關閉KVO。
自定義的KVO實現:
1、檢查物件的類有沒有相應的 setter 方法,如果沒有丟擲異常;
2、檢查物件 isa 指向的類是不是一個 KVO 類,如果不是,新建一個繼承原來類的子類,並把 isa 指向這個新建的子類;
3、檢查物件的 KVO 類重寫過沒有這個 setter 方法。如果沒有,新增重寫的 setter 方法,在setter方法裡面通知觀察者;
4、新增這個觀察者。
參考:http://tech.glowing.com/cn/implement-kvo/

50、apple用什麼方式實現對一個物件的KVO?
當你觀察一個物件時,一個新的類會被動態建立。這個類繼承自該物件的原本的類,並重寫了被觀察屬性的 setter 方法。重寫的 setter 方法會負責在呼叫原 setter 方法之前和之後,通知所有觀察物件值的更改。最後通過 isa 混寫(isa-swizzling) 把這個物件的 isa 指標 ( isa 指標告訴 Runtime 系統這個物件的類是什麼 ) 指向這個新建立的子類,物件就神奇的變成了新建立的子類的例項。如下所示:

844323-f8153d6538d7e38e.png
Paste_Image.png

鍵值觀察通知依賴於 NSObject 的兩個方法: willChangeValueForKey: 和 didChangevlueForKey: 。在一個被觀察屬性發生改變之前, willChangeValueForKey: 一定會被呼叫,這會記錄舊值。而當改變發生後, didChangeValueForKey: 會被呼叫,繼而 observeValueForKey:ofObject:change:context: 也會被呼叫。可以手動實現這些呼叫,但很少有人這麼做,一般我們只在希望能控制回撥的呼叫時機時才會這麼做。比如在now屬性的setter方法中,你這樣手動去寫是完全沒有必要的:

- (void)setNow:(NSDate *)aDate {
    [self willChangeValueForKey:@"now"];
    _now = aDate;
    [self didChangeValueForKey:@"now"];
}

因為當你觀察now屬性時,系統會建立子類,重寫setter方法,然後在前後加上willChangeValueForKey和didChangeValueForKey,你這麼寫的話會導致KVO通知被呼叫兩次。什麼時候需要手動寫willChangeValueForKey和didChangeValueForKey方法呢?當在Catagory中新增屬性時,比如MJRefresh開源框架,檢視裡面的原始碼:

static const char MJRefreshHeaderKey = '\0';
- (void)setMj_header:(MJRefreshHeader *)mj_header
{
    if (mj_header != self.mj_header) {
        // 刪除舊的,新增新的
        [self.mj_header removeFromSuperview];
        [self insertSubview:mj_header atIndex:0];
        
        // 儲存新的
        [self willChangeValueForKey:@"mj_header"]; // KVO
        objc_setAssociatedObject(self, &MJRefreshHeaderKey,
                                 mj_header, OBJC_ASSOCIATION_ASSIGN);
        [self didChangeValueForKey:@"mj_header"]; // KVO
    }
}

這是因為Catagory是無法真正新增例項變數的,只能間接通過objc_setAssociatedObject的方法新增屬性,那麼這個時候需要你手動呼叫willChangeValueForKey和didChangeValueForKey來觸發KVO,因為系統是無法正確的複寫setter的。
除了複寫setter,isa混寫,Apple 還重寫、覆蓋了 -class 方法並返回原來的類。 企圖欺騙我們:這個類沒有變,就是原本那個類。
參考:https://www.mikeash.com/pyblog/friday-qa-2009-01-23.html

51、IBOutlet連出來的檢視屬性為什麼可以被設定成weak?
對於一些view,新增到xib或者storyboard,在載入的時候,會直接作為subview強引用到父view的subviews陣列屬性下,因此IBOutlet定義的時候可以直接設為weak。如果根view呢?根view直接被ViewController的view屬性強引用。

844323-59a943070afda8ef.png
Paste_Image.png

對於一些top level objects,比如UIGestureRecognizer物件,載入的時候會加入
_topLevelObjectsToKeepAliveFromStoryboard陣列強引用,因此也可以設定為weak。
當然你硬要設定為strong也是可以的,而且有的情況需要設定為strong,比如一個view在執行過程中被移除出當前的view hierachy,你不想它被銷燬的話,應該用strong。

52、IB中User Defined Runtime Attributes如何使用?
它能夠通過KVC的方式配置一些你在interface builder中不能配置的屬性當你希望在IB中作儘可能多得事情,這個特效能夠幫助你編寫更加輕量級的viewController。比如設定圓角:

844323-ca0315c9071275be.png
Paste_Image.png

53、如何除錯BAD_ACCESS錯誤
1、重寫object的respondsToSelector方法,列印出現EXEC_BAD_ACCESS前訪問的最後一個selector;
2、Xcode中設定Zombie;
3、設定全域性斷點;
4、使用Xcode 7中整合的BAD_ACCESS捕獲功能:Address Sanitizer。
參考:
http://mobile.51cto.com/iphone-279455.htm

53、lldb(gdb)常用的除錯命令?
gdb是UNIX以及UNIX-like下面的除錯工具,XCode以前的老版本就是使用的gdb,後來Xcode5的釋出帶來了新的llvm編譯器和lldb除錯工具。
lldb常見的命令有:
p:輸出基本型別
po:輸出Objective-C物件
bt:列印呼叫堆疊
n:斷點執行下一步
參考:
http://lldb.llvm.org/lldb-gdb.html
http://www.dreamingwish.com/article/lldb-usage-a.html
http://www.jianshu.com/p/087cd19d49ba

相關文章