1、runtime怎么添加屬性于樟、方法等
ivar 表示成員變量
class_addIvar
class_addMethod
class_addProperty
class_addProtocol
class_replaceProperty
2 、是否可以把比較耗時的操作放在NSNotificationCenter中
首先必須明確通知在哪個線程中發(fā)出娜搂,那么處理接受到通知的方法也在這個線
程中調(diào)用
如果在異步線程發(fā)的通知誓禁,那么可以執(zhí)行比較耗時的操作;
如果在主線程發(fā)的通知罕扎,那么就不可以執(zhí)行比較耗時的操作
3聚唐、runtime 如何實現(xiàn) weak 屬性
首先要搞清楚 weak 屬性的特點
weak策略表明該屬性定義了一種“非擁有關(guān)系” (nonowningrelationship)。
為這種屬性設(shè)置新值時腔召,設(shè)置方法既不保留新值杆查,也不釋放舊值。此特質(zhì)同 assign類似;然而在屬性所指的對象遭到摧毀時臀蛛,屬性值也會清空(nil out)亲桦。
那么 runtime 如何實現(xiàn) weak 變量的自動置 nil?
runtime 對注冊的類浊仆,會進行布局客峭,會將 weak 對象放入一個 hash 表中。用 weak 指向的對象內(nèi)存地址作為 key氧卧,當此對象的引用計數(shù)為 0 的時候會調(diào)用對象的 dealloc 方法桃笙,假設(shè) weak 指向的對象內(nèi)存地址是 a,那么就會以 a 為 key沙绝,在這個 weak hash 表中搜索搏明,找到所有以 a 為 key 的 weak 對象,從而設(shè)置為 nil闪檬。
4星著、weak屬性需要在 dealloc中置 nil么
在 ARC 環(huán)境無論是強指針還是弱指針都無需在 dealloc 設(shè)置為 nil , ARC 會自動幫我們處理粗悯,即便是編譯器不幫我們做這些虚循,weak 也不需要在 dealloc 中置 nil。在屬性所指的對象遭到摧毀時样傍,屬性值也會清空横缔。
// 模擬下 weak的 setter方法,大致如下
- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, "object", object,
OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
5衫哥、一個 Objective-C對象如何進行內(nèi)存布局茎刚?(考慮有父類的情況)
所有父類的成員變量和自己的成員變量都會存放在該對象所對應的存儲空間中
父類的方法和自己的方法都會緩存在類對象的方法緩存中,類方法是緩存在元類對象中
每一個對象內(nèi)部都有一個 isa指針,指向他的類對象,類對象中存放著本對象的如下信息
- 對象方法列表
- 成員變量的列表
- 屬性列表
每個 Objective-C 對象都有相同的結(jié)構(gòu)
Objective-C 對象的結(jié)構(gòu)圖
ISA指針
根類(NSObject)的實例變量
倒數(shù)第二層父類的實例變量
...
父類的實例變量
類的實例變量
根類對象就是 NSObject撤逢,它的 super class 指針指向 nil
類對象既然稱為對象膛锭,那它也是一個實例。類對象中也有一個 isa 指針指向它的元類(meta class)蚊荣,即類對象是元類的實例初狰。元類內(nèi)部存放的是類方法列表,根元類的 isa指針指向自己互例,superclass指針指向 NSObject類
6奢入、一個 objc對象的 isa的指針指向什么?有什么作用媳叨?
每一個對象內(nèi)部都有一個 isa 指針俊马,這個指針是指向它的真實類型
根據(jù)這個指針就能知道將來調(diào)用哪個類的方法
7丁存、下面的代碼輸出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
答案:都輸出 Son
這個題目主要是考察關(guān)于 objc 中對 self 和 super 的理解:
self 是類的隱藏參數(shù)柴我,指向當前調(diào)用方法的這個類的實例。而 super 本質(zhì)是一個編譯器標示符扩然,和 self 是指向的同一個消息接受者艘儒,當使用 self 調(diào)用方法時,會從當前類的方法列表中開始找夫偶,如果沒有界睁,就從父類中再找;而當使用 super 時兵拢,則從父類的方法列表中開始找翻斟。然后調(diào)用父類的這個方法調(diào)用[self class] 時,會轉(zhuǎn)化成 objc_msgSend 函數(shù)id objc_msgSend(id self, SELop, ...)说铃。
調(diào)用[super class] 時 访惜,會轉(zhuǎn)化成objc_msgSendSuper函數(shù)id objc_msgSendSuper(struct objc_super *super, SEL op, ...)。
第一個參數(shù)是objc_super 這樣一個結(jié)構(gòu)體腻扇,其定義如下
struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
第一個成員是 receiver, 類似于上面的 objc_msgSend 函數(shù)第一個參數(shù) self.
第二個成員是記錄當前類的父類是什么债热,告訴程序從父類中開始找方法,找到方法后幼苛,最后內(nèi)部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用窒篱,此時已經(jīng)和[self class]調(diào)用相同了,故上述輸出結(jié)果仍然返回 Son objc Runtime 開源代碼對- (Class)class 方法的實現(xiàn)
-(Class)class {
return object_getClass(self);
}
8舶沿、runtime如何通過 selector找到對應的 IMP地址墙杯?(分別考慮類方法和實例方法)
每一個類對象中都一個對象方法列表(對象方法緩存)
類方法列表是存放在類對象中 isa 指針指向的元類對象中(類方法緩存)
方法列表中每個方法結(jié)構(gòu)體中記錄著方法的名稱,方法實現(xiàn),以及參數(shù)類型,其實selector 本質(zhì)就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現(xiàn).
當我們發(fā)送一個消息給一個 NSObject 對象時括荡,這條消息會在對象的類對象方法列表里查找,當我們發(fā)送一個消息給一個類時高镐,這條消息會在類的 Meta Class 對象的方法列表里查找。
9一汽、objc中的類方法和實例方法有什么本質(zhì)區(qū)別和聯(lián)系
類方法:
類方法是屬于類對象的
類方法只能通過類對象調(diào)用
類方法中的 self 是類對象
類方法可以調(diào)用其他的類方法
類方法中不能訪問成員變量
類方法中不能直接調(diào)用對象方法
類方法是存儲在元類對象的方法緩存中
實例方法:
實例方法是屬于實例對象的
實例方法只能通過實例對象調(diào)用
實例方法中的 self 是實例對象
實例方法中可以訪問成員變量
實例方法中直接調(diào)用實例方法
實例方法中可以調(diào)用類方法(通過類名)
實例方法是存放在類對象的方法緩存中
10避消、使用 runtime Associate方法關(guān)聯(lián)的對象,需要在主對象 dealloc的時候釋放么召夹?
無論在 MRC 下還是 ARC 下均不需要
被關(guān)聯(lián)的對象在生命周期內(nèi)要比對象本身釋放的晚很多岩喷,它們會在被NSObject -dealloc 調(diào)用的 object_dispose()方法中釋放。
補充:對象的內(nèi)存銷毀時間表监憎,分四個步驟
1.調(diào)用 -release :引用計數(shù)變?yōu)榱?/p>
- 對象正在被銷毀纱意,生命周期即將結(jié)束.
- 不能再有新的 __weak 弱引用,否則將指向 nil.
- 調(diào)用 [self dealloc]
- 父類調(diào)用 -dealloc
- 繼承關(guān)系中最直接繼承的父類再調(diào)用 -dealloc
- 如果是 MRC 代碼 則會手動釋放實例變量們(iVars)
- 繼承關(guān)系中每一層的父類 都再調(diào)用 -dealloc
- NSObject 調(diào) -dealloc
- 只做一件事:調(diào)用 Objective-C runtime 中的 object_dispose() 方法
- 調(diào)用 object_dispose()
- 為 C++ 的實例變量們(iVars)調(diào)用 destructors
- 為 ARC 狀態(tài)下的 實例變量們(iVars) 調(diào)用 -release
- 解除所有使用 runtime Associate 方法關(guān)聯(lián)的對象
- 解除所有 __weak 引用
- 調(diào)用 free()
11鲸阔、_objc_msgForward函數(shù)是做什么的偷霉?直接調(diào)用它將會發(fā)生什么迄委?
_objc_msgForward 是 IMP 類型,用于消息轉(zhuǎn)發(fā)的:當向一個對象發(fā)送一條消息类少,但它并沒有實現(xiàn)的時候叙身,_objc_msgForward 會嘗試做消息轉(zhuǎn)發(fā)。
直接調(diào)用_objc_msgForward 是非常危險的事硫狞,這是把雙刃刀信轿,如果用不好會直接導致程序 Crash,但是如果用得好残吩,能做很多非巢坪觯酷的事。
JSPatch 就是直接調(diào)用_objc_msgForward 來實現(xiàn)其核心功能的
12泣侮、能否向編譯后得到的類中增加實例變量即彪?能否向運行時創(chuàng)建的類中添加實例變量?為什么活尊?
不能向編譯后得到的類中增加實例變量隶校;
能向運行時創(chuàng)建的類中添加實例變量;
- 分析如下:
因為編譯后的類已經(jīng)注冊在 runtime 中酬凳,類結(jié)構(gòu)體中的 objc_ivar_list 實例變量的鏈表和 instance_size 實例變量的內(nèi)存大小已經(jīng)確定 惠况,同時runtime會調(diào)用class_setIvarLayout 或 class_setWeakIvarLayout 來處理 strong weak 引用,所以不能向存在的類中添加實例變量宁仔。
運行時創(chuàng)建的類是可以添加實例變量稠屠,調(diào)用 class_addIvar 函數(shù),但是得在調(diào)用objc_allocateClassPair 之后翎苫,objc_registerClassPair 之前权埠,原因同上。
13煎谍、runloop和線程有什么關(guān)系攘蔽?
每條線程都有唯一的一個 RunLoop 對象與之對應的
主線程的 RunLoop 是自動創(chuàng)建并啟動
子線程的 RunLoop 需要手動創(chuàng)建
子線程的 RunLoop 創(chuàng)建步驟如下:
在子線程中調(diào)用[NSRunLoop currentRunLoop]創(chuàng)建 RunLoop 對象(懶加載,只創(chuàng)建一次)呐粘。
獲得 RunLoop 對象后要調(diào)用 run方法來啟動一個運行循環(huán)
// 啟動 RunLoop
[[NSRunLoop currentRunLoop] run];
RunLoop 的其他啟動方法 // 第一個參數(shù):指定運行模式
// 第二個參數(shù):指定 RunLoop 的過期時間满俗,即:到了這個時間后
RunLoop就失效了
[[NSRunLoop currentRunLoop] runMode:kCFRunLoopDefaultMode
beforeDate:[NSDate distantFuture]];
14、runloop的 mode作用是什么作岖?
用來控制一些特殊操作只能在指定模式下運行唆垃,一般可以通過指定操作的運行。
mode 來控制執(zhí)行時機痘儡,以提高用戶體驗
- 系統(tǒng)默認注冊了 5 個 Mode
1.kCFRunLoopDefaultMode:App 的默認 Mode辕万,通常主線程是在這個 Mode下運行,對應 OC 中的:NSDefaultRunLoopMode
2.UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動渐尿,保證界面滑動時不受其他 Mode 影響
3.kCFRunLoopCommonModes:這是一個標記 Mode醉途,不是一種真正的 Mode,事件可 以 運 行 在 所 有 標 有 common modes 標 記 的 模 式 中 砖茸, 對 應 OC 中 的NSRunLoopCommonModes 隘擎, 帶 有 common modes 標 記 的 模 式 有 :UITrackingRunLoopMode 和 kCFRunLoopDefaultMode
4.UIInitializationRunLoopMode:在啟動 App 時進入的第一個 Mode,啟動完成后就不再使用
5.GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部 Mode渔彰,通常用不到
15嵌屎、以+scheduledTimerWithTimeInterval...的方式觸發(fā)的 timer,在滑動頁面上的列表時恍涂,timer會暫定回調(diào),為什么植榕?如何解決再沧?
這里強調(diào)一點:在主線程中以+scheduledTimerWithTimeInterval...的方式觸發(fā)的timer 默認是運行在 NSDefaultRunLoopMode模式下的,當滑動頁面上的列表時尊残,進入了 UITrackingRunLoopMode模式炒瘸,這時候 timer 就會停止∏奚溃可以修改 timer 的運行模式為 NSRunLoopCommonModes顷扩,這樣定時器就可以一直運行了。
以下是我的補充:
在子線程中通過 scheduledTimerWithTimeInterval:...方法來構(gòu)建
NSTimer方法內(nèi)部已經(jīng)創(chuàng)建NSTimer 對象 慰毅,并加入到 RunLoop 中 隘截, 運行模式為NSDefaultRunLoopMode。
由于 Mode 有 timer 對象汹胃,所以 RunLoop 就開始監(jiān)聽定時器事件了婶芭,從而開始進入運行循環(huán)。
這個方法僅僅是創(chuàng)建 RunLoop 對象着饥,并不會主動啟動 RunLoop犀农,需要再調(diào)用 run方法來啟動
如果在主線程中通過 scheduledTimerWithTimeInterval:...方法來構(gòu)
建 NSTimer,就不需要主動啟動 RunLoop 對象宰掉,因為主線程的 RunLoop 對象在程序運行起來就已經(jīng)被啟動了
// userInfo 參數(shù):用來給 NSTimer 的
userInfo屬性賦值呵哨,userInfo是只讀的,只能在構(gòu)建 NSTimer對象
時賦值
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self
selector:@selector(run:) userInfo:@"ya了個hoo"
repeats:YES];
//scheduledTimer...方法創(chuàng)建出來 NSTimer雖然已經(jīng)指定了默認模
式轨奄,但是【允許你修改模式】
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:NSRunLoopCommonModes];
// 【僅在子線程】需要手動啟動 RunLoop對象孟害,進入運行循環(huán)
[[NSRunLoop currentRunLoop] run];
16、猜想 runloop內(nèi)部是如何實現(xiàn)的戚绕?
從字面意思看:運行循環(huán)纹坐、跑圈;
本質(zhì):內(nèi)部就是 do-while循環(huán),在這個循環(huán)內(nèi)部不斷地處理各種事件(任務)耘子,
比如:Source果漾、Timer、Observer谷誓;
每條線程都有唯一一個 RunLoop 對象與之對應绒障,主線程的 RunLoop 默認已經(jīng)啟動,子線程的 RunLoop 需要手動啟動捍歪;
每次 RunLoop 啟動時户辱,只能指定其中一個 Mode,這個 Mode 被稱作 CurrentMode糙臼,如果需要切換 Mode庐镐,只能退出 Loop,再重新指定一個 Mode 進入变逃,這樣做主要是為了隔離不同 Mode 中的 Source必逆、Timer、Observer揽乱,讓其互不影響名眉;
附上RunLoop的運行圖
17、不手動指定 autoreleasepool的前提下凰棉,一個 autorealese對象在什么時刻釋放损拢?(比如在一個 vc的 viewDidLoad中創(chuàng)建)
分兩種情況:手動干預釋放時機、系統(tǒng)自動去釋放
手動干預釋放時機:指定 autoreleasepool 就是所謂的:當前作用域大括號結(jié)束時就立即釋放
系統(tǒng)自動去釋放:不手動指定 autoreleasepool撒犀,Autorelease 對象會在當前的runloop 迭代結(jié)束時釋放福压,下面詳細說明釋放時機
RunLoop 中的三個狀態(tài)會處理自動釋放池,通過打印代碼發(fā)現(xiàn)有兩個 Observer 監(jiān)聽到狀態(tài)值為:1 和 160(32+128)
kCFRunLoopEntry(1)// 第一次進入會創(chuàng)建一個自動釋放池kCFRunLoopBeforeWaiting(32)// 進入休眠狀態(tài)前先銷毀自動釋放池绘证,
再創(chuàng)建一個新的自動釋放池
kCFRunLoopExit(128)// 退出 RunLoop 時銷毀最后一次創(chuàng)建的自動釋放池
如果在一個 vc 的 viewDidLoad 中創(chuàng)建一個 Autorelease 對象隧膏,那么該對象會在viewDidAppear 方法執(zhí)行前就被銷毀了(是這樣的嗎?嚷那?胞枕?)
18、蘋果是如何實現(xiàn) autoreleasepool的魏宽?
autoreleasepool 以一個隊列數(shù)組的形式實現(xiàn),主要通過下列三個函數(shù)完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
看函數(shù)名就可以知道腐泻,對 autorelease 分別執(zhí)行 push,和 pop 操作队询。銷毀對象時執(zhí)行 release 操作
19派桩、GCD的隊列(dispatch_queue_t)分哪兩種類型?背后的線程模型是什么樣的蚌斩?
串行隊列
并行隊列
dispatch_global_queue();是全局并發(fā)隊列
dispatch_main_queue();是一種特殊串行隊列
背后的線程模型:自定義隊列 dispatch_queue_t queue; 可以自定義是并行:
DISPATCH_QUEUE_CONCURRENT 或者 串行 DISPATCH_QUEUE_SERIAL
20铆惑、蘋果為什么要廢棄dispatch_get_current_queue?
容易誤用造成死鎖
21如何用 GCD同步若干個異步調(diào)用?(如根據(jù)若干個 url異步加載多張圖片员魏,然后在都下載完成后合成一張整圖)
必須是并發(fā)隊列才起作用
需求分析
首先丑蛤,分別異步執(zhí)行 2 個耗時的操作
其次,等 2 個異步操作都執(zhí)行完畢后撕阎,再回到主線程執(zhí)行一些操作使用隊列組實現(xiàn)上面的需求
// 創(chuàng)建隊列組
dispatch_group_t group = dispatch_group_create();
// 獲取全局并發(fā)隊列
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 往隊列組中添加耗時操作
dispatch_group_async(group, queue, ^{
// 執(zhí)行耗時的異步操作 1
});
// 往隊列組中添加耗時操作
dispatch_group_async(group, queue, ^{
// 執(zhí)行耗時的異步操作 2
});
// 當并發(fā)隊列組中的任務執(zhí)行完畢后才會執(zhí)行這里的代碼
dispatch_group_notify(group, queue, ^{
// 如果這里還有基于上面兩個任務的結(jié)果繼續(xù)執(zhí)行一些代碼受裹,建議還是放到子
線程中,等代碼執(zhí)行完畢后在回到主線程
// 回到主線程
dispatch_async(group, dispatch_get_main_queue(), ^{
// 執(zhí)行相關(guān)代碼...
});
});
22虏束、dispatch_barrier_async的作用是什么棉饶?
函數(shù)定義
dispatch_barrier_async(dispatch_queue_t queue,
dispatch_block_t block);
必須是并發(fā)隊列,要是串行隊列镇匀,這個函數(shù)就沒啥意義了
注意:這個函數(shù)的第一個參數(shù) queue 不能是全局的并發(fā)隊列
作用:在它前面的任務執(zhí)行結(jié)束后它才執(zhí)行照藻,在它后面的任務等它執(zhí)行完成后才會執(zhí)行。
示例代碼
-(void)barrier
{
dispatch_queue_t queue = dispatch_queue_create("12342234",
DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
// 在它前面的任務執(zhí)行結(jié)束后它才執(zhí)行汗侵,在它后面的任務等它執(zhí)行完成后才會
執(zhí)行
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}
23岩梳、以下代碼運行結(jié)果如何?
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
答案:主線程死鎖
24晃择、lldb(gdb)常用的調(diào)試命令?
po:打印對象也物,會調(diào)用對象 description 方法宫屠。是 print-object 的簡寫
expr:可以在調(diào)試時動態(tài)執(zhí)行指定表達式,并將結(jié)果打印出來滑蚯,很有用的命令
print:也是打印命令浪蹂,需要指定類型
bt:打印調(diào)用堆棧,是 thread backtrace 的簡寫告材,加 all 可打印所有 thread 的堆棧
brl:是 breakpoint list 的簡寫
25坤次、BAD_ACCESS在什么情況下出現(xiàn)?
訪問一個僵尸對象斥赋,訪問僵尸對象的成員變量或者向其發(fā)消息死循環(huán)
26缰猴、如何調(diào)試 BAD_ACCESS錯誤
設(shè)置全局斷點快速定位問題代碼所在行
開啟僵尸對象調(diào)試功能
27、簡述下 Objective-C 中調(diào)用方法的過程(runtime)
Objective-C 是動態(tài)語言疤剑,每個方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送滑绒,即:objc_msgSend(receiver, selector),整個過程介紹如下:
objc 在向一個對象發(fā)送消息時隘膘,runtime 庫會根據(jù)對象的 isa 指針找到該對象實際所屬的類
然后在該類中的方法列表以及其父類方法列表中尋找方法運行疑故,如果,在最頂層的父類(一般也就 NSObject)中依然找不到相應的方法時弯菊,程序在運行時會掛掉并拋出異常 unrecognized selector sent to XXX
但是在這之前纵势,objc 的運行時會給出三次拯救程序崩潰的機會,這三次拯救程序奔潰的說明見問題《什么時候會報 unrecognized selector 的異常》中的說明钦铁。
補充說明:Runtime 鑄就了 Objective-C 是動態(tài)語言的特性软舌,使得 C 語言具備了面向?qū)ο蟮奶匦裕诔绦蜻\行期創(chuàng)建育瓜,檢查葫隙,修改類、對象及其對應的方法躏仇,這些操作都可以使用 runtime 中的對應方法實現(xiàn)恋脚。
28、什么是 method swizzling(俗稱黑魔法)
簡單說就是進行方法交換
在 Objective-C 中調(diào)用一個方法焰手,其實是向一個對象發(fā)送消息糟描,查找消息的唯一依據(jù)是 selector 的名字。利用 Objective-C 的動態(tài)特性书妻,可以實現(xiàn)在運行時偷換 selector 對應的方法實現(xiàn)船响,達到給方法掛鉤的目的
每個類都有一個方法列表,存放著方法的名字和方法實現(xiàn)的映射關(guān)系躲履,selector 的本質(zhì)其實就是方法名见间,IMP 有點類似函數(shù)指針,指向具體的 Method 實現(xiàn)工猜,通過selector 就可以找到對應的IMP
交換方法的幾種實現(xiàn)方式
利用 method_exchangeImplementations 交換兩個方法的實現(xiàn)
利用 class_replaceMethod 替換方法的實現(xiàn)
利用 method_setImplementation來直接設(shè)置某個方法的IMP
29米诉、objc中向一個 nil對象發(fā)送消息將會發(fā)生什么?
在 Objective-C 中向 nil 發(fā)送消息是完全有效的——只是在運行時不會有任何作用
如果一個方法返回值是一個對象篷帅,那么發(fā)送給 nil 的消息將返回 0(nil)
如果方法返回值為指針類型史侣,其指針大小為小于或者等于 sizeof(void*)
float,double魏身,long double 或者 long long 的整型標量惊橱,發(fā)送給 nil 的消息將返回 0
如果方法返回值為結(jié)構(gòu)體,發(fā)送給 nil 的消息將返回 0。結(jié)構(gòu)體中各個字段的值將都是 0
如果方法的返回值不是上述提到的幾種情況箭昵,那么發(fā)送給 nil 的消息的返回值將是未定義的
具體原因分析
objc 是動態(tài)語言 税朴,每個方法在運行時會被動態(tài)轉(zhuǎn)為消息發(fā)送,即 :
objc_msgSend(receiver, selector)
為了方便理解這個內(nèi)容宙枷,還是貼一個 objc 的源代碼
struct objc_class
{
// isa 指針指向 Meta Class掉房,因為 Objc 的類的本身也是一個 Object,
// 為了處理這個關(guān)系慰丛,runtime 就創(chuàng)造了 Meta Class卓囚,
// 當給類發(fā)送[NSObject alloc]這樣消息時,實際上是把這個消息發(fā)給了 Class
Object
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息诅病,默認為 0
long info OBJC2_UNAVAILABLE; // 類信息哪亿,供運行期使用的一些位標識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
// 方法緩存粥烁,對象接到一個消息會根據(jù) isa 指針查找消息對象,
// 這時會在 method Lists 中遍歷蝇棉,
// 如果 cache 了讨阻,常用的方法調(diào)用時就能夠提高調(diào)用的效率。
// 這個方法緩存只存在一份篡殷,不是每個類的實例對象都有一個方法緩存
// 子類會在自己的方法緩存中緩存父類的方法钝吮,父類在自己的方法緩存中也會緩存
自己的方法,而不是說子類就不緩存父類方法了
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
objc 在向一個對象發(fā)送消息時板辽,runtime 庫會根據(jù)對象的 isa 指針找到該對象實際所屬的類奇瘦,然后在該類中的方法列表以及其父類方法列表中尋找方法運行,然后再發(fā)送消息的時候劲弦,objc_msgSend 方法不會返回值耳标,所謂的返回內(nèi)容都是具體調(diào)用時執(zhí)行的。
如果向一個 nil 對象發(fā)送消息邑跪,首先在尋找對象的 isa 指針時就是 0 地址返回了次坡,所以不會出現(xiàn)任何錯誤
30、objc 中向一個對象發(fā)送消息[obj foo]和objc_msgSend()函數(shù)之間有什么關(guān)系画畅?
[obj foo];在 objc 動態(tài)編譯時砸琅,會被轉(zhuǎn)意為:objc_msgSend(obj,@selector(foo));
31、什么時候會報 unrecognized selector的異常轴踱?
當調(diào)用該對象上某個方法,而該對象上沒有實現(xiàn)這個方法的時候明棍, 可以通過“消息轉(zhuǎn)發(fā)”進行解決,如果還是不行就會報 unrecognized selector 異常
objc 是 動 態(tài) 語 言 寇僧, 每 個 方 法 在 運 行 時 會 被 動 態(tài) 轉(zhuǎn) 為 消 息 發(fā) 送 , 即 :
objc_msgSend(receiver, selector)沸版,整個過程介紹如下:
objc 在向一個對象發(fā)送消息時嘁傀,runtime 庫會根據(jù)對象的 isa 指針找到該對象實際所屬的類
然后在該類中的方法列表以及其父類方法列表中尋找方法運行
如果,在最頂層的父類中依然找不到相應的方法時视粮,程序在運行時會掛掉并拋出異常 unrecognized selector sent to XXX 细办。但是在這之前,objc 的運行時會給出三次拯救程序崩潰的機會
三次拯救程序崩潰的機會
- Method resolution
objc運行時會調(diào)用+resolveInstanceMethod: 或者+resolveClassMethod:蕾殴,讓你有機會提供一個函數(shù)實現(xiàn)笑撞。
如果你添加了函數(shù)并返回 YES,那運行時系統(tǒng)就會重新啟動一次消息發(fā)送的過程
如果 resolve 方法返回 NO 钓觉,運行時就會移到下一步茴肥,消息轉(zhuǎn)發(fā) - Fast forwarding
如果目標對象實現(xiàn)了-forwardingTargetForSelector:,Runtime 這時就會調(diào)用這個方法荡灾,給你把這個消息轉(zhuǎn)發(fā)給其他對象的機會
只要這個方法返回的不是 nil和 self瓤狐,整個消息發(fā)送的過程就會被重啟瞬铸,當然發(fā)送的對象會變成你返回的那個對象。否則础锐,就會繼續(xù) Normal Fowarding嗓节。這里叫 Fast,只是為了區(qū)別下一步的轉(zhuǎn)發(fā)機制皆警。因為這一步不會創(chuàng)建任何新的對象拦宣,但 Normal forwarding 轉(zhuǎn)發(fā)會創(chuàng)建一個 NSInvocation 對象,相對 Normal forwarding 轉(zhuǎn)發(fā)更快點信姓,所以這里叫 Fast forwarding - Normal forwarding
這一步是 Runtime 最后一次給你挽救的機會鸵隧。
首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型。
如果 -methodSignatureForSelector:返回nil财破,Runtime則會發(fā)出
-doesNotRecognizeSelector:消息掰派,程序這時也就掛掉了。
如果返回了一個函數(shù)簽名左痢,Runtime 就會創(chuàng)建一個 NSInvocation 對象并發(fā)送-forwardInvocation:消息給目標對象
32靡羡、HTTP協(xié)議中 POST方法和 GET方法有那些區(qū)別?
- .先說一個不正確的理解:GET 用于向服務器請求數(shù)據(jù),POST 用于提交數(shù)據(jù)俊性,這樣說是不對的略步,GET也可以提交數(shù)據(jù),POST也可以請求數(shù)據(jù)定页。
你輕輕松松的給出了一個“標準答案”:
- GET在瀏覽器回退時是無害的趟薄,而POST會再次提交請求。
- GET產(chǎn)生的URL地址可以被Bookmark典徊,而POST不可以杭煎。
- GET請求會被瀏覽器主動cache,而POST不會卒落,除非手動設(shè)置羡铲。
- GET請求只能進行url編碼,而POST支持多種編碼方式儡毕。
- GET請求參數(shù)會被完整保留在瀏覽器歷史記錄里也切,而POST中的參數(shù)不會被保留。
- GET請求在URL中傳送的參數(shù)是有長度限制的腰湾,而POST么有雷恃。
- 對參數(shù)的數(shù)據(jù)類型,GET只接受ASCII字符费坊,而POST沒有限制倒槐。
- GET比POST更不安全,因為參數(shù)直接暴露在URL上附井,所以不能用來傳遞敏感信息导犹。
- GET參數(shù)通過URL傳遞唱凯,POST放在Request body中。
(本標準答案參考自w3schools)
“很遺憾谎痢,這不是我們要的回答磕昼!”
如果我告訴你GET和POST本質(zhì)上沒有區(qū)別你信嗎?
讓我們扒下GET和POST的外衣节猿,坦誠相見吧票从!
GET和POST是什么?HTTP協(xié)議中的兩種發(fā)送請求的方法滨嘱。
HTTP是什么峰鄙?HTTP是基于TCP/IP的關(guān)于數(shù)據(jù)如何在萬維網(wǎng)中如何通信的協(xié)議。
HTTP的底層是TCP/IP太雨。所以GET和POST的底層也是TCP/IP吟榴,也就是說,GET/POST都是TCP鏈接囊扳。GET和POST能做的事情是一樣一樣的吩翻。你要給GET加上request body,給POST帶上url參數(shù)锥咸,技術(shù)上是完全行的通的狭瞎。
HTTP只是個行為準則,而TCP才是GET和POST怎么實現(xiàn)的基本
GET和POST本質(zhì)上就是TCP鏈接搏予,并無差別熊锭。但是由于HTTP的規(guī)定和瀏覽器/服務器的限制,導致他們在應用過程中體現(xiàn)出一些不同
GET和POST還有一個重大區(qū)別
簡單的說:
GET產(chǎn)生一個TCP數(shù)據(jù)包雪侥;POST產(chǎn)生兩個TCP數(shù)據(jù)包碗殷。
長的說:
對于GET方式的請求,瀏覽器會把http header和data一并發(fā)送出去速缨,服務器響應200(返回數(shù)據(jù))亿扁;
而對于POST,瀏覽器先發(fā)送header鸟廓,服務器響應100 continue,瀏覽器再發(fā)送data襟己,服務器響應200 ok(返回數(shù)據(jù))引谜。
注意 :并不是所有瀏覽器都會在POST中發(fā)送兩次包,F(xiàn)irefox就只發(fā)送一次尽楔。
33查蓉、使用 block時什么情況會發(fā)生引用循環(huán)惹骂,如何解決?
一個對象中強引用了block贝室,在block中又強引用了該對象契讲,就會發(fā)射循環(huán)引用。
解決方法是將該對象使用__weak
或者__block
修飾符修飾之后再在block中使用滑频。
-
id __weak weakSelf = self;
或者__weak __typeof(self)weakSelf = self;
. 該方法可以設(shè)置宏 id __block weakSelf = self;
或者將其中一方強制制空 xxx = nil
捡偏。
檢測代碼中是否存在循環(huán)引用問題,可使用 Facebook 開源的一個檢測工具 FBRetainCycleDetector 峡迷。
34银伟、在 block內(nèi)如何修改 block外部變量?
__block int a = 0;
void (^foo)(void) = ^{
a = 1;
};
foo(); //這里绘搞,a的值被修改為1
35彤避、使用系統(tǒng)的某些 block api(如 UIView的block版本寫動畫時),是否也考慮循環(huán)引用問題夯辖?
系統(tǒng)的某些 block api 中琉预,UIView 的 block 版本寫動畫時不需要考慮,但也有一些 api 需要考慮
以下這些使用方式不會引起循環(huán)引用的問題
[UIView animateWithDuration:duration animations:^
{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{ self.someProperty = xyz; }];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification)
{ self.someProperty = xyz; }];
但如果方法中的一些參數(shù)是 成員變量蒿褂,那么可以造成循環(huán)引用圆米,如 GCD 、
NSNotificationCenter 調(diào)用就要小心一點贮缅,比如 GCD 內(nèi)部如果引用了 self榨咐,而且
GCD 的參數(shù)是 成員變量,則要考慮到循環(huán)引用谴供,舉例如下:
GCD
分析:self-->_operationsQueue-->block-->self 形成閉環(huán)块茁,就造成了循環(huán)引用__weak
__typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
[weakSelf doSomething];
[weakSelf doSomethingElse];
} );
NSNotificationCenter
分析:self-->_observer-->block-->self 形成閉環(huán),就造成了循環(huán)引用
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter]
addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note){
[weakSelf dismissModalViewControllerAnimated:YES];
}];