目錄
- 2020 阿里躲舌、字節(jié)iOS面試題之Runtime相關(guān)問題1
- 2020 阿里震肮、字節(jié)iOS面試題之Runtime相關(guān)問題2
- 2020 阿里而账、字節(jié)iOS面試題之Runtime相關(guān)問題3
runtime相關(guān)問題之內(nèi)存部分的關(guān)聯(lián)屬性或者hook相關(guān)的Method Swizzle
經(jīng)過前兩期內(nèi)容 我們這期來講一下 內(nèi)存部分的剩余問題 主要包含如下:
-
Method Swizzle
注意事項 - 屬性修飾符atomic的內(nèi)部實現(xiàn)是怎么樣的?能保證線程安全嗎
- iOS 中內(nèi)省的幾個方法有哪些胰坟?內(nèi)部實現(xiàn)原理是什么
-
class
、objc_getClass
泞辐、object_getclass
方法有什么區(qū)別?
Method Swizzle
注意事項
-
需要注意的是交換方法實現(xiàn)后的副作用,
method_exchangeImplementations()
.交換方法函數(shù)最終會以objc_msgSend()
方式調(diào)用,副作用主要集中在第一個參數(shù) 如下示例
objc_msgSend(payment, @selector(quantity))
方法交換后再去調(diào)用quantity方法將有可能會crash.解決這種副作用的方式是使用method_setImplementation()
來替換原來的交換方式,這樣才最為合理, 具體原理請參照 Objc 黑科技 - Method Swizzle 的一些注意事項
-
避免交換父類方法
如果當前類沒有實現(xiàn)被交換的方法且父類實現(xiàn)了,此時父類的實現(xiàn)會被交換,若此父類的多個繼承者都在交換時會引起多次交換導致混亂,同時調(diào)用父類方法有可能因為找不到方法簽名而crash.
所以交換前都應該check能否為當前類添加被交換的函數(shù)的新的實現(xiàn)IMP,這個過程大概分為3步驟-
class_addMethod
check能否添加方法
-
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
給類cls的SEL添加一個實現(xiàn)IMP, 返回YES則表明類cls并未實現(xiàn)此方法笔横,返回NO則表明類已實現(xiàn)了此方法。注意:添加成功與否咐吼,完全由該類本身來決定吹缔,與父類有無該方法無關(guān)。
-
class_replaceMethod
替換類cls的SEL的函數(shù)實現(xiàn)為imp
class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,
const char * _Nullable types)
-
method_exchangeImplementations
最終方法交換
method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2)
- 交換方法應在+load方法
這個前面講消息轉(zhuǎn)發(fā)的時候講過,+load不是消息轉(zhuǎn)發(fā)的方式實現(xiàn)的且在運行時初始化過程中類被加載的時候調(diào)用,而且父類,當前類,category,子類等 都會調(diào)用一次.所以這里最適合寫方法交換的hook(Method Swizzle).
-
交換的分類方法應該添加自定義前綴,避免沖突
這個毫無疑問,方法名稱一樣的時候會出現(xiàn),分類的方法會覆蓋類中同名的方法.
屬性修飾符atomic的內(nèi)部實現(xiàn)是怎么樣的?能保證線程安全嗎?
atomic內(nèi)部實現(xiàn)
id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
...
id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;
// Atomic retain release world
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
id value = objc_retain(*slot);
slotlock.unlock();
return objc_autoreleaseReturnValue(value);
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
...
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
property
的 atomic
是采用 spinlock_t
自旋鎖實現(xiàn)的.
能保證線程安全嗎?
atomic
通過這種方法.在運行時僅僅是保證了set
,get
方法的原子性.所以使用atomic并不能保證線程安全锯茄。
iOS 中內(nèi)省的幾個方法有哪些厢塘?內(nèi)部實現(xiàn)原理是什么?
首先要明白一個名詞 introspection
反省,內(nèi)省的意思,在iOS開發(fā)中我們會稱它為反射.
內(nèi)省方法 例如常用的NSObject
中的isKindOfClass:
通過實例對象判斷class
這就是一種內(nèi)省方法或者叫反射方法,但我認為NSClassFromString()
這個應該也算一種反射方法.
iOS 中內(nèi)省的幾個方法
我們從NSObject.h中看下吧
- (BOOL)isKindOfClass:(Class)aClass; //判斷是否是這個類或者這個類的子類的實例
- (BOOL)isMemberOfClass:(Class)aClass; //判斷是否是這個類的實例
- (BOOL)conformsToProtocol:(Protocol *)aProtocol; //判斷是否遵守某個協(xié)議
+ (BOOL)conformsToProtocol:(Protocol *)protocol; //判斷某個類是否遵守某個協(xié)議
- (BOOL)respondsToSelector:(SEL)aSelector; //判讀實例是否有這樣方法
+ (BOOL)instancesRespondToSelector:(SEL)aSelector; //判斷類是否有這個方法
...
內(nèi)部實現(xiàn)原理
1.isKindOfClass:
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
類方法是通過ISA()函數(shù)拿到指向元類的存儲isa指針數(shù)據(jù)的地址bit位按位與上相關(guān)掩碼的方式判斷當前是否是某個類的子類.
實例方法是通過objc_object::getIsa()
函數(shù)通過存儲的tag_ext
表形式拿到isa對于的class來取出class平check來實現(xiàn)的.
2.isMemberOfClass:
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
這倆方法非常簡單直接 拿到isa指針對比
3.conformsToProtocol:
+ (BOOL)conformsToProtocol:(Protocol *)protocol {
if (!protocol) return NO;
for (Class tcls = self; tcls; tcls = tcls->superclass) {
if (class_conformsToProtocol(tcls, protocol)) return YES;
}
return NO;
}
- (BOOL)conformsToProtocol:(Protocol *)protocol {
if (!protocol) return NO;
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (class_conformsToProtocol(tcls, protocol)) return YES;
}
return NO;
}
兩個方法最終還是去isa->data()->protocols 拿到相關(guān)協(xié)議然后判斷是否存在相關(guān)協(xié)議 如下代碼:
BOOL class_conformsToProtocol(Class cls, Protocol *proto_gen)
{
protocol_t *proto = newprotocol(proto_gen);
if (!cls) return NO;
if (!proto_gen) return NO;
mutex_locker_t lock(runtimeLock);
checkIsKnownClass(cls);
ASSERT(cls->isRealized())
for (const auto& proto_ref : cls->data()->protocols) {
protocol_t *p = remapProtocol(proto_ref);
if (p == proto || protocol_conformsToProtocol_nolock(p, proto)) {
return YES;
}
}
return NO;
}
這里可以清晰的看到for循環(huán) 取出相關(guān)protocol指針 然后通過指針和傳入的參數(shù)生成的
proto
對比
4.respondsToSelector:
+ (BOOL)respondsToSelector:(SEL)sel {
return class_respondsToSelector_inst(self, sel, self->ISA());
}
- (BOOL)respondsToSelector:(SEL)sel {
return class_respondsToSelector_inst(self, sel, [self class]);
}
這個源碼比較麻煩 我簡單敘述一下吧 實際上調(diào)用棧比較深就是一直尋找到當前實例能響應哪些方法,當前類沒有就去父類,父類沒有則直到元類.
respondsToSelector:
|__ class_respondsToSelector_inst()
|__ lookUpImpOrNil()
|__ lookUpImpOrForward()
返回IMP結(jié)果
這就是整個消息轉(zhuǎn)發(fā)的過程 就不在這里贅述了.感興趣回看一下第二章 消息轉(zhuǎn)發(fā)部分
我上述列舉了一些常用的內(nèi)省方法,其它的都方法基本沒什么特別之處都是拿到isa各種操作內(nèi)部的獲取相關(guān)屬性的函數(shù)返回結(jié).
class
、objc_getClass
肌幽、object_getclass
方法有什么區(qū)別?
我用xcode隨便建了一個demo 打印一下viewcontrooller的內(nèi)容
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Class cls1 = [self class];
Class cls2 = object_getClass(cls1);
Class cls3 = objc_getClass(object_getClassName([self class]));
NSLog(@"%p",cls1);
NSLog(@"%p",cls2);
NSLog(@"%p",cls3);
}
@end
輸出
2020-08-31 16:15:48.150285+0800 ClassDemo[5582:55836] 0x10205b3b0
2020-08-31 16:15:48.150456+0800 ClassDemo[5582:55836] 0x10205b3d8
2020-08-31 16:15:48.150575+0800 ClassDemo[5582:55836] 0x10205b3b0
我簡單列舉了一張表格
class |
object_getclass() |
objc_getClass() |
|
---|---|---|---|
傳入?yún)?shù) | N/a | id類型 | 類名的字符串 |
操作對象 | obj | 這個id的isa指針所指向的Class | 這個類的類對象 |
實例對象時 | 和object_getclass() 一致 |
和class 一致 |
N/a |
類對象/元類對象時 | 返回的消息對象本身 | 返回的是下一個對象 | N/a |
原因:因為class返回的是self晚碾,而object_getClass返回的是isa指向的對象
總結(jié)
以上就是"一套高效的iOS面試題之runtime相關(guān)問題3"中的內(nèi)存剩余部分,問題答案雖然簡短 但是每道題都問的非常到位,值得一看!
推薦
收錄:原文地址