目錄
- 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)屬性或者h(yuǎn)ook相關(guān)的Method Swizzle
經(jīng)過前兩期內(nèi)容 我們這期來講一下 內(nèi)存部分的剩余問題 主要包含如下:
-
Method Swizzle注意事項(xiàng) - 屬性修飾符atomic的內(nèi)部實(shí)現(xiàn)是怎么樣的?能保證線程安全嗎
- iOS 中內(nèi)省的幾個(gè)方法有哪些?內(nèi)部實(shí)現(xiàn)原理是什么
-
class、objc_getClass、object_getclass方法有什么區(qū)別?
Method Swizzle注意事項(xiàng)
-
需要注意的是交換方法實(shí)現(xiàn)后的副作用,
method_exchangeImplementations().交換方法函數(shù)最終會以objc_msgSend()方式調(diào)用,副作用主要集中在第一個(gè)參數(shù) 如下示例
objc_msgSend(payment, @selector(quantity))
方法交換后再去調(diào)用quantity方法將有可能會crash.解決這種副作用的方式是使用method_setImplementation()來替換原來的交換方式,這樣才最為合理, 具體原理請參照 Objc 黑科技 - Method Swizzle 的一些注意事項(xiàng)
-
避免交換父類方法
如果當(dāng)前類沒有實(shí)現(xiàn)被交換的方法且父類實(shí)現(xiàn)了,此時(shí)父類的實(shí)現(xiàn)會被交換,若此父類的多個(gè)繼承者都在交換時(shí)會引起多次交換導(dǎo)致混亂,同時(shí)調(diào)用父類方法有可能因?yàn)檎也坏椒椒ê灻鴆rash.
所以交換前都應(yīng)該check能否為當(dāng)前類添加被交換的函數(shù)的新的實(shí)現(xiàn)IMP,這個(gè)過程大概分為3步驟-
class_addMethodcheck能否添加方法
-
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
給類cls的SEL添加一個(gè)實(shí)現(xiàn)IMP, 返回YES則表明類cls并未實(shí)現(xiàn)此方法,返回NO則表明類已實(shí)現(xiàn)了此方法。注意:添加成功與否,完全由該類本身來決定,與父類有無該方法無關(guān)。
-
class_replaceMethod替換類cls的SEL的函數(shù)實(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)
- 交換方法應(yīng)在+load方法
這個(gè)前面講消息轉(zhuǎn)發(fā)的時(shí)候講過,+load不是消息轉(zhuǎn)發(fā)的方式實(shí)現(xiàn)的且在運(yùn)行時(shí)初始化過程中類被加載的時(shí)候調(diào)用,而且父類,當(dāng)前類,category,子類等 都會調(diào)用一次.所以這里最適合寫方法交換的hook(Method Swizzle).
-
交換的分類方法應(yīng)該添加自定義前綴,避免沖突
這個(gè)毫無疑問,方法名稱一樣的時(shí)候會出現(xiàn),分類的方法會覆蓋類中同名的方法.
method swizzling你應(yīng)該注意的點(diǎn)
屬性修飾符atomic的內(nèi)部實(shí)現(xiàn)是怎么樣的?能保證線程安全嗎?
atomic內(nèi)部實(shí)現(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自旋鎖實(shí)現(xiàn)的.
能保證線程安全嗎?
atomic通過這種方法.在運(yùn)行時(shí)僅僅是保證了set,get方法的原子性.所以使用atomic并不能保證線程安全。
iOS 中內(nèi)省的幾個(gè)方法有哪些?內(nèi)部實(shí)現(xiàn)原理是什么?
首先要明白一個(gè)名詞 introspection 反省,內(nèi)省的意思,在iOS開發(fā)中我們會稱它為反射.
內(nèi)省方法 例如常用的NSObject中的isKindOfClass: 通過實(shí)例對象判斷class這就是一種內(nèi)省方法或者叫反射方法,但我認(rèn)為NSClassFromString()這個(gè)應(yīng)該也算一種反射方法.
iOS 中內(nèi)省的幾個(gè)方法
我們從NSObject.h中看下吧
- (BOOL)isKindOfClass:(Class)aClass; //判斷是否是這個(gè)類或者這個(gè)類的子類的實(shí)例
- (BOOL)isMemberOfClass:(Class)aClass; //判斷是否是這個(gè)類的實(shí)例
- (BOOL)conformsToProtocol:(Protocol *)aProtocol; //判斷是否遵守某個(gè)協(xié)議
+ (BOOL)conformsToProtocol:(Protocol *)protocol; //判斷某個(gè)類是否遵守某個(gè)協(xié)議
- (BOOL)respondsToSelector:(SEL)aSelector; //判讀實(shí)例是否有這樣方法
+ (BOOL)instancesRespondToSelector:(SEL)aSelector; //判斷類是否有這個(gè)方法
...
內(nèi)部實(shí)現(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)掩碼的方式判斷當(dāng)前是否是某個(gè)類的子類.
實(shí)例方法是通過objc_object::getIsa()函數(shù)通過存儲的tag_ext表形式拿到isa對于的class來取出class平check來實(shí)現(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;
}
兩個(gè)方法最終還是去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]);
}
這個(gè)源碼比較麻煩 我簡單敘述一下吧 實(shí)際上調(diào)用棧比較深就是一直尋找到當(dāng)前實(shí)例能響應(yīng)哪些方法,當(dāng)前類沒有就去父類,父類沒有則直到元類.
respondsToSelector:
|__ class_respondsToSelector_inst()
|__ lookUpImpOrNil()
|__ lookUpImpOrForward()
返回IMP結(jié)果
這就是整個(gè)消息轉(zhuǎn)發(fā)的過程 就不在這里贅述了.感興趣回看一下第二章 消息轉(zhuǎn)發(fā)部分
我上述列舉了一些常用的內(nèi)省方法,其它的都方法基本沒什么特別之處都是拿到isa各種操作內(nèi)部的獲取相關(guān)屬性的函數(shù)返回結(jié).
class、objc_getClass、object_getclass 方法有什么區(qū)別?
我用xcode隨便建了一個(gè)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 | 這個(gè)id的isa指針?biāo)赶虻腃lass | 這個(gè)類的類對象 |
| 實(shí)例對象時(shí) | 和object_getclass()一致 |
和class一致 |
N/a |
| 類對象/元類對象時(shí) | 返回的消息對象本身 | 返回的是下一個(gè)對象 | N/a |
原因:因?yàn)閏lass返回的是self,而object_getClass返回的是isa指向的對象
總結(jié)
以上就是"一套高效的iOS面試題之runtime相關(guān)問題3"中的內(nèi)存剩余部分,問題答案雖然簡短 但是每道題都問的非常到位,值得一看!
推薦
收錄:原文地址