runtime怎么添加屬性腮郊、方法等
- ivar表示成員變量
- class_addIvar
- class_addMethod
- class_addProperty
- class_addProtocol
- class_replaceProperty
是否可以把比較耗時的操作放在NSNotificationCenter中
- 首先必須明確通知在哪個線程中發(fā)出捆交,那么處理接受到通知的方法也在這個線程中調用
- 如果在異步線程發(fā)的通知,那么可以執(zhí)行比較耗時的操作荠卷;
- 如果在主線程發(fā)的通知,那么就不可以執(zhí)行比較耗時的操作
runtime 如何實現(xiàn) weak 屬性
- 首先要搞清楚weak屬性的特點
weak策略表明該屬性定義了一種“非擁有關系” (nonowning relationship)。
為這種屬性設置新值時输虱,設置方法既不保留新值,也不釋放舊值脂凶。此特質同assign類似;
然而在屬性所指的對象遭到摧毀時宪睹,屬性值也會清空(nil out)
那么runtime如何實現(xiàn)weak變量的自動置nil?
runtime對注冊的類蚕钦,會進行布局亭病,會將 weak 對象放入一個 hash 表中。
用 weak 指向的對象內存地址作為 key嘶居,當此對象的引用計數(shù)為0的時候會調用對象的 dealloc 方法罪帖,
假設 weak 指向的對象內存地址是a,那么就會以a為key邮屁,在這個 weak hash表中搜索整袁,找到所有以a為key的 weak 對象,從而設置為 nil佑吝。
weak屬性需要在dealloc中置nil么
- 在ARC環(huán)境無論是強指針還是弱指針都無需在 dealloc 設置為 nil 坐昙, ARC 會自動幫我們處理
- 即便是編譯器不幫我們做這些,weak也不需要在dealloc中置nil
- 在屬性所指的對象遭到摧毀時芋忿,屬性值也會清空
// 模擬下weak的setter方法炸客,大致如下
- (void)setObject:(NSObject *)object
{
objc_setAssociatedObject(self, "object", object, OBJC_ASSOCIATION_ASSIGN);
[object cyl_runAtDealloc:^{
_object = nil;
}];
}
一個Objective-C對象如何進行內存布局?(考慮有父類的情況)
所有父類的成員變量和自己的成員變量都會存放在該對象所對應的存儲空間中
父類的方法和自己的方法都會緩存在類對象的方法緩存中戈钢,類方法是緩存在元類對象中
每一個對象內部都有一個isa指針,指向他的類對象,類對象中存放著本對象的如下信息
對象方法列表
成員變量的列表
屬性列表
每個 Objective-C 對象都有相同的結構痹仙,如下圖所示
Objective-C 對象的結構圖 |
---|
ISA指針 |
根類(NSObject)的實例變量 |
倒數(shù)第二層父類的實例變量 |
... |
父類的實例變量 |
類的實例變量 |
- ? 根類對象就是NSObject,它的super class指針指向nil
- 類對象既然稱為對象殉了,那它也是一個實例开仰。類對象中也有一個isa指針指向它的元類(meta class),即類對象是元類的實例。元類內部存放的是類方法列表抖所,根元類的isa指針指向自己梨州,superclass指針指向NSObject類
一個objc對象的isa的指針指向什么?有什么作用田轧?
- 每一個對象內部都有一個isa指針暴匠,這個指針是指向它的真實類型
- 根據(jù)這個指針就能知道將來調用哪個類的方法
下面的代碼輸出什么?
@implementation Son : Father
- (id)init
{
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
答案:都輸出 Son
這個題目主要是考察關于objc中對 self 和 super 的理解:
self 是類的隱藏參數(shù)傻粘,指向當前調用方法的這個類的實例每窖。而 super 本質是一個編譯器標示符,和 self 是指向的同一個消息接受者
當使用 self 調用方法時弦悉,會從當前類的方法列表中開始找窒典,如果沒有,就從父類中再找稽莉;
而當使用 super時瀑志,則從父類的方法列表中開始找。然后調用父類的這個方法
調用[self class] 時污秆,會轉化成 objc_msgSend函數(shù) id objc_msgSend(id self, SEL op, ...)
調用 [super class]時劈猪,會轉化成 objc_msgSendSuper函數(shù) id objc_msgSendSuper(struct objc_super *super, SEL op, ...)
第一個參數(shù)是 objc_super 這樣一個結構體,其定義如下 struct objc_super {
__unsafe_unretained id receiver;
__unsafe_unretained Class super_class;
};
第一個成員是 receiver, 類似于上面的 objc_msgSend函數(shù)第一個參數(shù)self
第二個成員是記錄當前類的父類是什么良拼,告訴程序從父類中開始找方法战得,找到方法后,最后內部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調用庸推, 此時已經(jīng)和[self class]調用相同了常侦,故上述輸出結果仍然返回 Son
objc Runtime開源代碼對- (Class)class方法的實現(xiàn)
-(Class)class {return object_getClass(self);
}
runtime如何通過selector找到對應的IMP地址?(分別考慮類方法和實例方法)
- 每一個類對象中都一個對象方法列表(對象方法緩存)
- 類方法列表是存放在類對象中isa指針指向的元類對象中(類方法緩存)
- 方法列表中每個方法結構體中記錄著方法的名稱,方法實現(xiàn),以及參數(shù)類型贬媒,其實selector本質就是方法名稱,通過這個方法名稱就可以在方法列表中找到對應的方法實現(xiàn).
- 當我們發(fā)送一個消息給一個NSObject對象時聋亡,這條消息會在對象的類對象方法列表里查找
- 當我們發(fā)送一個消息給一個類時,這條消息會在類的Meta Class對象的方法列表里查找
objc中的類方法和實例方法有什么本質區(qū)別和聯(lián)系
類方法:
- 類方法是屬于類對象的
- 類方法只能通過類對象調用
- 類方法中的self是類對象
- 類方法可以調用其他的類方法
- 類方法中不能訪問成員變量
- 類方法中不能直接調用對象方法
- 類方法是存儲在元類對象的方法緩存中
實例方法:
- 實例方法是屬于實例對象的
- 實例方法只能通過實例對象調用
- 實例方法中的self是實例對象
- 實例方法中可以訪問成員變量
- 實例方法中直接調用實例方法
- 實例方法中可以調用類方法(通過類名)
- 實例方法是存放在類對象的方法緩存中
使用runtime Associate方法關聯(lián)的對象际乘,需要在主對象dealloc的時候釋放么杀捻?
- 無論在MRC下還是ARC下均不需要
- 被關聯(lián)的對象在生命周期內要比對象本身釋放的晚很多,它們會在被 NSObject -dealloc 調用的object_dispose()方法中釋放
- 補充:對象的內存銷毀時間表蚓庭,分四個步驟
1.調用 -release :引用計數(shù)變?yōu)榱?/p>
對象正在被銷毀,生命周期即將結束.
不能再有新的 __weak 弱引用仅仆,否則將指向 nil.
調用 [self dealloc]
- 父類調用 -dealloc
繼承關系中最直接繼承的父類再調用 -dealloc
如果是 MRC 代碼 則會手動釋放實例變量們(iVars)
繼承關系中每一層的父類 都再調用 -dealloc
- NSObject 調 -dealloc
- 只做一件事:調用 Objective-C runtime 中的 object_dispose() 方法
- 調用 object_dispose()
為 C++ 的實例變量們(iVars)調用 destructors
為 ARC 狀態(tài)下的 實例變量們(iVars) 調用 -release
解除所有使用 runtime Associate方法關聯(lián)的對象
解除所有 __weak 引用
調用 free()
_objc_msgForward函數(shù)是做什么的器赞?直接調用它將會發(fā)生什么?
- _objc_msgForward是IMP類型墓拜,用于消息轉發(fā)的:當向一個對象發(fā)送一條消息港柜,但它并沒有實現(xiàn)的時候,_objc_msgForward會嘗試做消息轉發(fā)
- 直接調用_objc_msgForward是非常危險的事,這是把雙刃刀夏醉,如果用不好會直接導致程序Crash爽锥,但是如果用得好,能做很多非撑先幔酷的事
- JSPatch就是直接調用_objc_msgForward來實現(xiàn)其核心功能的
- 詳細解說參見這里的第一個問題解答
能否向編譯后得到的類中增加實例變量氯夷?能否向運行時創(chuàng)建的類中添加實例變量?為什么靶擦?
不能向編譯后得到的類中增加實例變量腮考;
能向運行時創(chuàng)建的類中添加實例變量;
分析如下:
因為編譯后的類已經(jīng)注冊在runtime中玄捕,類結構體中的objc_ivar_list 實例變量的鏈表和instance_size實例變量的內存大小已經(jīng)確定踩蔚,同時runtime 會調用class_setIvarLayout 或 class_setWeakIvarLayout來處理strong weak引用,所以不能向存在的類中添加實例變量
運行時創(chuàng)建的類是可以添加實例變量枚粘,調用 class_addIvar函數(shù)馅闽,但是得在調用objc_allocateClassPair之后,objc_registerClassPair之前馍迄,原因同上福也。
runloop和線程有什么關系?
每條線程都有唯一的一個RunLoop對象與之對應的
主線程的RunLoop是自動創(chuàng)建并啟動
子線程的RunLoop需要手動創(chuàng)建
子線程的RunLoop創(chuàng)建步驟如下:
在子線程中調用[NSRunLoop currentRunLoop]創(chuàng)建RunLoop對象(懶加載柬姚,只創(chuàng)建一次)
獲得RunLoop對象后要調用run方法來啟動一個運行循環(huán)
// 啟動RunLoop[[NSRunLoop currentRunLoop] run];
RunLoop的其他啟動方法
// 第一個參數(shù):指定運行模式// 第二個參數(shù):指定RunLoop的過期時間拟杉,即:到了這個時間后RunLoop就失效了
[[NSRunLoop currentRunLoop] runMode:kCFRunLoopDefaultMode beforeDate:[NSDate distantFuture]];
runloop的mode作用是什么?
用來控制一些特殊操作只能在指定模式下運行量承,一般可以通過指定操作的運行mode來控制執(zhí)行時機搬设,以提高用戶體驗
系統(tǒng)默認注冊了5個Mode
kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行撕捍,對應OC中的:NSDefaultRunLoopMode
UITrackingRunLoopMode:界面跟蹤 Mode拿穴,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他Mode影響
kCFRunLoopCommonModes:這是一個標記Mode忧风,不是一種真正的Mode默色,事件可以運行在所有標有common modes標記的模式中,對應OC中的NSRunLoopCommonModes狮腿,帶有common modes標記的模式有:UITrackingRunLoopMode和kCFRunLoopDefaultMode
UIInitializationRunLoopMode:在啟動 App時進入的第一個 Mode腿宰,啟動完成后就不再使用
GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內部Mode,通常用不到
以+scheduledTimerWithTimeInterval...的方式觸發(fā)的timer缘厢,在滑動頁面上的列表時吃度,timer會暫定回調,為什么贴硫?如何解決椿每?
這里強調一點:在主線程中以+scheduledTimerWithTimeInterval...的方式觸發(fā)的timer默認是運行在NSDefaultRunLoopMode模式下的伊者,當滑動頁面上的列表時,進入了UITrackingRunLoopMode模式间护,這時候timer就會停止
可以修改timer的運行模式為NSRunLoopCommonModes亦渗,這樣定時器就可以一直運行了
以下是我的筆記補充:
在子線程中通過scheduledTimerWithTimeInterval:...方法來構建NSTimer
方法內部已經(jīng)創(chuàng)建NSTimer對象,并加入到RunLoop中汁尺,運行模式為NSDefaultRunLoopMode
由于Mode有timer對象法精,所以RunLoop就開始監(jiān)聽定時器事件了,從而開始進入運行循環(huán)
這個方法僅僅是創(chuàng)建RunLoop對象均函,并不會主動啟動RunLoop亿虽,需要再調用run方法來啟動
如果在主線程中通過scheduledTimerWithTimeInterval:...方法來構建NSTimer,就不需要主動啟動RunLoop對象苞也,因為主線程的RunLoop對象在程序運行起來就已經(jīng)被啟動了 // userInfo參數(shù):用來給NSTimer的userInfo屬性賦值洛勉,userInfo是只讀的,只能在構建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];
猜想runloop內部是如何實現(xiàn)的?
- 從字面意思看:運行循環(huán)殷勘、跑圈此再;
- 本質:內部就是do-while循環(huán),在這個循環(huán)內部不斷地處理各種事件(任務)玲销,比如:Source输拇、Timer、Observer贤斜;
- 每條線程都有唯一一個RunLoop對象與之對應策吠,主線程的RunLoop默認已經(jīng)啟動,子線程的RunLoop需要手動啟動瘩绒;
- 每次RunLoop啟動時猴抹,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode锁荔,如果需要切換Mode蟀给,只能退出Loop,再重新指定一個Mode進入阳堕,這樣做主要是為了隔離不同Mode中的Source跋理、Timer、Observer恬总,讓其互不影響前普;
? 附上RunLoop的運行圖
不手動指定autoreleasepool的前提下,一個autorealese對象在什么時刻釋放越驻?(比如在一個vc的viewDidLoad中創(chuàng)建)
分兩種情況:手動干預釋放時機、系統(tǒng)自動去釋放
手動干預釋放時機:指定autoreleasepool就是所謂的:當前作用域大括號結束時就立即釋放
系統(tǒng)自動去釋放:不手動指定autoreleasepool,Autorelease對象會在當前的 runloop 迭代結束時釋放缀旁,下面詳細說明釋放時機
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í)行前就被銷毀了(是這樣的嗎目木??懊渡?)
蘋果是如何實現(xiàn)autoreleasepool的刽射?
- autoreleasepool以一個隊列數(shù)組的形式實現(xiàn),主要通過下列三個函數(shù)完成.
objc_autoreleasepoolPush
objc_autoreleasepoolPop
objc_aurorelease
- 看函數(shù)名就可以知道,對autorelease分別執(zhí)行push剃执,和pop操作誓禁。銷毀對象時執(zhí)行release操作
GCD的隊列(dispatch_queue_t)分哪兩種類型?背后的線程模型是什么樣的肾档?
- 串行隊列
- 并行隊列
- dispatch_global_queue();是全局并發(fā)隊列
- dispatch_main_queue();是一種特殊串行隊列
- 背后的線程模型:自定義隊列 dispatch_queue_t queue; 可以自定義是并行:DISPATCH_QUEUE_CONCURRENT 或者 串行DISPATCH_QUEUE_SERIAL
蘋果為什么要廢棄dispatch_get_current_queue摹恰?
- 容易誤用造成死鎖
如何用GCD同步若干個異步調用?(如根據(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, ^{
// 如果這里還有基于上面兩個任務的結果繼續(xù)執(zhí)行一些代碼闺阱,建議還是放到子線程中,等代碼執(zhí)行完畢后在回到主線程
// 回到主線程
dispatch_async(group, dispatch_get_main_queue(), ^{
// 執(zhí)行相關代碼...
});
});
dispatch_barrier_async的作用是什么舵变?
- 函數(shù)定義
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
- 必須是并發(fā)隊列酣溃,要是串行隊列,這個函數(shù)就沒啥意義了
- 注意:這個函數(shù)的第一個參數(shù)queue不能是全局的并發(fā)隊列
- 作用:在它前面的任務執(zhí)行結束后它才執(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í)行結束后它才執(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]);
});
}
以下代碼運行結果如何?
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
* 答案:主線程死鎖