iOS 常见面试题 -- Runtime
Runtime,运行时,
OC是一门动态性较强的语言,它允许很多操作延迟到运行时执行
OC的动态性就是由runtime来支撑和实现的,利用runtime可以做很多事情
比如
1、利用关联函数给分类添加属性
2、遍历类的成员变量以及方法
3、交换系统方法
4、利用消息转发机制解决方法找不到的问题
OC中方法的调用 其实都是转化为 objc_msgSend函数的调用
objc_msgSend函数的执行流程可以分为3大阶段
一、消息发送阶段
首先判断消息接收者是否为nil 如果为空直接退出
1、去自己类的方法缓存列表中查找该方法如果找到则执行该方法,
2、否则去自己类的方法列表中查找该方法 如果找到先缓存该方法然后再执行,
3、否则去自己父类的方法缓存列表中查找该方法 如果找到先缓存该方法到
自己类中,然后执行该方法,
4、否则去自己父类的方法列表中查找该方法如果找到先缓存该方法到自己类中,
然后执行该方法,否则 到第二阶段
第一阶段流程图如下所示
消息发送阶段流程图.png
二、动态方法解析阶段
1、首先判断曾经有过动态解析 如果没有则调用 +resolveInstanceMethod:
或则+resoveClassMethod:方法来动态解析然后标记为已经动态解析
最后重新走“消息发送”的流程
2、如果曾经有过动态解析 则直接消息发送流程
如果第二阶段不做任何处理 则直接进入第三阶段
第二阶段流程图如下所示:
动态解析阶段流程图.png
三、消息转发阶段
顾名思义就是 自己没有能力处理这个方法 他需要将此方法转交给别人类处理。
1、首先调用 forwardingTargetForSelector:方法
在此方法中可以返回一个可以处理消息的对象,
如果对象存在则直接给对象转发消息,
如果对象不存在否则进入第2步 调用方法签名函数
2、调用methodSignatureForSelector:方法(方法签名函数)在此方法中:
如果返回值为空 则直接调用 调用doesNotRecognizeSelector:方法
然后程序报错:unrecognized selector sent to instance 经典错误
如果返回值不为空 则直接进入第3步 调用 forwardInvocation方法
3、forwardInvocation:方法
开发人员可以在此方法中返回一个对象来转发此消息
第三阶段流程图如下所示
消息转发阶段流程图.png
举例:
1、首先我们创建一个类(Person 类) 然后在 Person 类中声明一个对象方法(test)且不去实现
Person.h 声明一个test方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
- (void)test;
@end
NS_ASSUME_NONNULL_END
2、在其他地方调用 Person 类对象方法(test)
ViewController.m 调用 Person 类对象方法(test)
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor redColor];
Person *person = [[Person alloc]init];
[person test];
}
如果我们不做任何处理 则程序就会奔溃 报如下错误:
2021-01-19 10:18:44.480279+0800 LLLL[40671:839012] -[Person test]: unrecognized selector sent to instance 0x600000e20000
2021-01-19 10:18:44.485940+0800 LLLL[40671:839012] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person test]: unrecognized selector sent to instance 0x600000e20000'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff23e3de6e __exceptionPreprocess + 350
1 libobjc.A.dylib 0x00007fff512a19b2 objc_exception_throw + 48
2 CoreFoundation 0x00007fff23e5eb94 -[NSObject(NSObject) doesNotRecognizeSelector:] + 132
3 CoreFoundation 0x00007fff23e4286c ___forwarding___ + 1436
4 CoreFoundation 0x00007fff23e44b58 _CF_forwarding_prep_0 + 120
5 LLLL 0x0000000102a9ff2e -[ViewController viewDidLoad] + 206
6 UIKitCore 0x00007fff48cc294e -[UIViewController _sendViewDidLoadWithAppearanceProxyObjectTaggingEnabled] + 83
7 UIKitCore 0x00007fff48cc786c -[UIViewController loadViewIfRequired] + 1084
8 UIKitCore 0x00007fff48cc7c89 -[UIViewController view] + 27
9 UIKitCore 0x00007fff493ab2d5 -[UIWindow addRootViewControllerViewIfPossible] + 326
10 UIKitCore 0x00007fff493aa8fe -[UIWindow _updateLayerOrderingAndSetLayerHidden:actionBlock:] + 219
11 UIKitCore 0x00007fff493ab989 -[UIWindow _setHidden:forced:] + 362
12 UIKit 0x0000000102fa4dc4 -[UIWindowAccessibility _orderFrontWithoutMakingKey] + 84
13 UIKitCore 0x00007fff493bedc5 -[UIWindow _mainQueue_makeKeyAndVisible] + 42
14 UIKitCore 0x00007fff495e0cdb -[UIWindowScene _makeKeyAndVisibleIfNeeded] + 202
15 UIKitCore 0x00007fff488cec30 +[UIScene _sceneForFBSScene:create:withSession:connectionOptions:] + 1405
16 UIKitCore 0x00007fff4936eca5 -[UIApplication _connectUISceneFromFBSScene:transitionContext:] + 1019
17 UIKitCore 0x00007fff4936efdc -[UIApplication workspace:didCreateScene:withTransitionContext:completion:] + 291
18 UIKitCore 0x00007fff48ec177c -[UIApplicationSceneClientAgent scene:didInitializeWithEvent:completion:] + 361
19 FrontBoardServices 0x00007fff36d03d2e -[FBSSceneImpl _callOutQueue_agent_didCreateWithTransitionContext:completion:] + 419
20 FrontBoardServices 0x00007fff36d29dc1 __86-[FBSWorkspaceScenesClient sceneID:createWithParameters:transitionContext:completion:]_block_invoke.154 + 102
21 FrontBoardServices 0x00007fff36d0e757 -[FBSWorkspace _calloutQueue_executeCalloutFromSource:withBlock:] + 220
22 FrontBoardServices 0x00007fff36d29a52 __86-[FBSWorkspaceScenesClient sceneID:createWithParameters:transitionContext:completion:]_block_invoke + 355
23 libdispatch.dylib 0x0000000102d0ae8e _dispatch_client_callout + 8
24 libdispatch.dylib 0x0000000102d0dda2 _dispatch_block_invoke_direct + 300
25 FrontBoardServices 0x00007fff36d4f6e9 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 30
26 FrontBoardServices 0x00007fff36d4f3d7 -[FBSSerialQueue _queue_performNextIfPossible] + 441
27 FrontBoardServices 0x00007fff36d4f8e6 -[FBSSerialQueue _performNextFromRunLoopSource] + 22
28 CoreFoundation 0x00007fff23da1c91 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
29 CoreFoundation 0x00007fff23da1bbc __CFRunLoopDoSource0 + 76
30 CoreFoundation 0x00007fff23da13ec __CFRunLoopDoSources0 + 268
31 CoreFoundation 0x00007fff23d9bf8e __CFRunLoopRun + 974
32 CoreFoundation 0x00007fff23d9b8a4 CFRunLoopRunSpecific + 404
33 GraphicsServices 0x00007fff38c05bbe GSEventRunModal + 139
34 UIKitCore 0x00007fff49372964 UIApplicationMain + 1605
35 LLLL 0x0000000102aa01d2 main + 114
36 libdyld.dylib 0x00007fff5211c1fd start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
接下来使用runtime运行时 来处理这个崩溃
第一阶段:消息发送阶段
此阶段为方法查找阶段 找到了则调用
找不到则 进行第二步 方法动态解析阶段
第二阶段:动态方法解析阶段
在此阶段 我们可以添加一个新的方法来 otherTest 来代替 test
Person.m
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test)) {
Method method = class_getInstanceMethod(self, @selector(otherTest));
class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)otherTest{
NSLog(@"我是代替方法 ---- %s",__func__);
}
@end
运行程序结果打印如下:
2021-01-19 10:41:28.143085+0800 LLLL[41023:856445] 我是代替方法 ---- -[Person otherTest]
这是第一补救阶段,如果 +resolveInstanceMethod:函数内部不做任何处理或则直接不实现 则进入 第三阶段
第三阶段:消息转发阶段
准备:
首先再创建一个类(Student类) 然后在这个类中创建一个对象方法test 这个对象方法一定要与Person对象中的一样 这样才能找到 方法
Student.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Student : NSObject
- (void)test;
@end
NS_ASSUME_NONNULL_END
Student.m
#import "Student.h"
@implementation Student
- (void)test{
NSLog(@"%s",__func__);
}
@end
实现:
进入第三阶段 首先会调用 forwardingTargetForSelector:方法 在此方法中我们可以把方法转发给别的类来处理
Person.m
#import "Person.h"
#import <objc/runtime.h>
#import "Student.h"
@implementation Person
//动态解析方法阶段
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// //注意⚠️ 如果不注释则无法进行第三阶段
// if (sel == @selector(test)) {
// Method method = class_getInstanceMethod(self, @selector(otherTest));
// class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
// return YES;
// }
return [super resolveInstanceMethod:sel];
}
- (void)otherTest{
NSLog(@"我是代替方法 ---- %s",__func__);
}
//消息转发阶段
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [[Student alloc]init];
}
return [super forwardingTargetForSelector:aSelector];
}
@end
此时运行代码 控制台打印如下:
2021-01-19 10:59:01.971909+0800 LLLL[41176:868929] -[Student test]
这一步则 完成了方法转发。
如果 forwardingTargetForSelector: 方法中未返回其他类 则会调用以下两个方法
methodSignatureForSelector:aSelector
forwardInvocation:anInvocation
Person.m
#import "Person.h"
#import <objc/runtime.h>
#import "Student.h"
@implementation Person
//动态解析方法阶段
+ (BOOL)resolveInstanceMethod:(SEL)sel{
// //注意⚠️ 如果不注释则无法进行第三阶段
// if (sel == @selector(test)) {
// Method method = class_getInstanceMethod(self, @selector(otherTest));
// class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
// return YES;
// }
return [super resolveInstanceMethod:sel];
}
- (void)otherTest{
NSLog(@"我是代替方法 ---- %s",__func__);
}
//消息转发阶段
- (id)forwardingTargetForSelector:(SEL)aSelector{
// //如果此方法不注释 则不会走下一步
// if (aSelector == @selector(test)) {
// return [[Student alloc]init];
// }
return [super forwardingTargetForSelector:aSelector];
}
/*
方法签名:返回值、返回类型
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
/*
v16@0:8
第一个参数 返回值类型 如 v代表 void
第二个参数 16 代表16个字这个节
第三个参数 @ 代表一个对象
第四个参数 0 从第0个字节开始
第五个参数 : 代表SEL
第六个参数 8代表从第八个字节开始
可以简写成: v@:
此处填写请参考:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1
举例1:
- (BOOL)ifSuccess:(NSString *)tag
其ObjCTypes为:"B@:@",其中:
"B":代表BOOL。 // NSLog(@"%s",@encode(BOOL))的结果为B
"@":一个id类型的对象,第一个参数类型,也就是objc _ msgSend的第一个参数
":":代表对应的SEL,第二个参数
"@":一个id类型的对象,也就是tag。
举例2;
- (void)goToSchoolWithPerson:(Person *)person;
[zhangsan goToSchoolWithPerson:lisi];
其ObjCTypes为: "v@:@" 那究竟是如何得来该字符串呢?其实我们有两种方式:
1、 直接查表。在Type Encodings里面列出了对应关系。 链接如上
2、使用 @encode()计算。(如: NSLog(@"%s",@encode(BOOL))的结果为B )
我们都知道消息发送会被转换成objc _ msgSend(id reciever,SEL sel,prarams1,params2,....)。所以上面的方法会被转换成:
void objc_msgSend(zhangsan,@selector(goToSchoolWithPerson:),lisi); //包含两个隐藏参数
这里的 “v@:@”就代表:
"v":代表返回值void
"@":代表一个对象,这里指代的id类型zhangsan,也就是消息的receiver
":":代表SEL
"@":代表参数lisi
*/
// return [NSMethodSignature signatureWithObjCTypes:"v@:"];
return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
}
return [super methodSignatureForSelector:aSelector];
}
/*
NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
anInvocation.target 方法调用者
anInvocation.selector 方法名
[anInvocation getArgument:NULL atIndex:0]
你可以在这个函数里面干你想干的事
*/
- (void)forwardInvocation:(NSInvocation *)anInvocation{
//1、你可以转发消息
// anInvocation.target = [[MJCat alloc] init];
// [anInvocation invoke];
// 或
// [anInvocation invokeWithTarget:[[Student alloc] init]];
//2、你可以干你想干的事。如:我就想打印
NSLog(@"%s",__func__);
}
@end
由此可知:我们可以在三个地方来实现test方法
第一步:
+resolveInstanceMethod:(对象方法)
+resoveClassMethod:(类方法)
第二步:
forwardingTargetForSelector:
第三步:
methodSignatureForSelector:aSelector
forwardInvocation:anInvocation
面试题 :什么是Runtime?平时项目中有用过么?
1、OC是一门动态性比较强的编程语言,允许很多操作推迟到程序运行时再进行
2、OC的动态性就是由Runtime来支撑和实现的,Runtime是一套C语言的API,封装了很多动态性相关的函数
3、平时编写的OC代码,底层都是转换成了Runtime API进行调用
具体应用
1、利用关联对象(AssociatedObject)给分类添加属性
2、遍历类的所有成员变量(修改textfield的占位文字颜色、字典转模型、自动归档解档)
3、交换方法实现(交换系统的方法)
4、利用消息转发机制解决方法找不到的异常问题
举例1:
1、窥探 系统自带对象的私有属性:如窥探UITextField 的属性
我们可以根据需求找到你想要的东西 然后去重写它等等
UITextField * textField = [[UITextField alloc]init];
// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList([UITextField class], &count);
for (int i = 0; i < count; i++) {
// 取出i位置的成员变量
Ivar ivar = ivars[i];
NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);
打印结果如下:
2021-01-20 10:06:44.646507+0800 LLLL[51081:1190161] _borderStyle q
2021-01-20 10:06:44.646605+0800 LLLL[51081:1190161] _minimumFontSize d
2021-01-20 10:06:44.646655+0800 LLLL[51081:1190161] _delegate @
2021-01-20 10:06:44.646713+0800 LLLL[51081:1190161] _background @"UIImage"
2021-01-20 10:06:44.646786+0800 LLLL[51081:1190161] _disabledBackground @"UIImage"
2021-01-20 10:06:44.646871+0800 LLLL[51081:1190161] _clearButtonMode q
2021-01-20 10:06:44.646929+0800 LLLL[51081:1190161] _leftView @"UIView"
2021-01-20 10:06:44.647006+0800 LLLL[51081:1190161] _leftViewMode q
2021-01-20 10:06:44.647095+0800 LLLL[51081:1190161] _rightView @"UIView"
2021-01-20 10:06:44.647180+0800 LLLL[51081:1190161] _rightViewMode q
2021-01-20 10:06:44.647274+0800 LLLL[51081:1190161] _contentCoverView @"UIView"
2021-01-20 10:06:44.647478+0800 LLLL[51081:1190161] _contentCoverViewMode q
2021-01-20 10:06:44.647650+0800 LLLL[51081:1190161] _backgroundCoverView @"UIView"
2021-01-20 10:06:44.647801+0800 LLLL[51081:1190161] _backgroundCoverViewMode q
2021-01-20 10:06:44.647988+0800 LLLL[51081:1190161] _traits @"UITextInputTraits"
2021-01-20 10:06:44.648146+0800 LLLL[51081:1190161] _nonAtomTraits @"UITextInputTraits"
2021-01-20 10:06:44.648569+0800 LLLL[51081:1190161] _fullFontSize @"_UIFullFontSize"
2021-01-20 10:06:44.648924+0800 LLLL[51081:1190161] _padding {UIEdgeInsets="top"d"left"d"bottom"d"right"d}
2021-01-20 10:06:44.649360+0800 LLLL[51081:1190161] _progress f
2021-01-20 10:06:44.649453+0800 LLLL[51081:1190161] _clearButton @"_UITextFieldClearButton"
2021-01-20 10:06:44.649528+0800 LLLL[51081:1190161] _clearButtonOffset {CGSize="width"d"height"d}
2021-01-20 10:06:44.649603+0800 LLLL[51081:1190161] _leftViewOffset {CGSize="width"d"height"d}
2021-01-20 10:06:44.649669+0800 LLLL[51081:1190161] _rightViewOffset {CGSize="width"d"height"d}
2021-01-20 10:06:44.665169+0800 LLLL[51081:1190161] _backgroundView @"UITextFieldBorderView"
2021-01-20 10:06:44.665276+0800 LLLL[51081:1190161] _disabledBackgroundView @"UITextFieldBorderView"
2021-01-20 10:06:44.665369+0800 LLLL[51081:1190161] _systemBackgroundView @"UITextFieldBackgroundView"
2021-01-20 10:06:44.665453+0800 LLLL[51081:1190161] _textContentView @"_UITextFieldCanvasView"
2021-01-20 10:06:44.665945+0800 LLLL[51081:1190161] _floatingContentView @"_UIFloatingContentView"
2021-01-20 10:06:44.666024+0800 LLLL[51081:1190161] _contentBackdropView @"UIVisualEffectView"
2021-01-20 10:06:44.666107+0800 LLLL[51081:1190161] _fieldEditorBackgroundView @"_UIDetachedFieldEditorBackgroundView"
2021-01-20 10:06:44.666174+0800 LLLL[51081:1190161] _fieldEditorEffectView @"UIVisualEffectView"
2021-01-20 10:06:44.666241+0800 LLLL[51081:1190161] _placeholderLabel @"UITextFieldLabel"
2021-01-20 10:06:44.666313+0800 LLLL[51081:1190161] _suffixLabel @"UITextFieldLabel"
2021-01-20 10:06:44.666406+0800 LLLL[51081:1190161] _prefixLabel @"UITextFieldLabel"
2021-01-20 10:06:44.666497+0800 LLLL[51081:1190161] _iconView @"UIImageView"
2021-01-20 10:06:44.666576+0800 LLLL[51081:1190161] _label @"UILabel"
2021-01-20 10:06:44.666659+0800 LLLL[51081:1190161] _labelOffset d
2021-01-20 10:06:44.666745+0800 LLLL[51081:1190161] _overriddenPlaceholder @"NSAttributedString"
2021-01-20 10:06:44.666833+0800 LLLL[51081:1190161] _overriddenPlaceholderAlignment q
2021-01-20 10:06:44.666997+0800 LLLL[51081:1190161] _interactionAssistant @"UITextInteractionAssistant"
2021-01-20 10:06:44.667143+0800 LLLL[51081:1190161] _selectGestureRecognizer @"UITapGestureRecognizer"
2021-01-20 10:06:44.667323+0800 LLLL[51081:1190161] _fieldEditor @"UIFieldEditor"
2021-01-20 10:06:44.667865+0800 LLLL[51081:1190161] __textContainer @"NSTextContainer"
2021-01-20 10:06:44.667974+0800 LLLL[51081:1190161] __layoutManager @"_UIFieldEditorLayoutManager"
2021-01-20 10:06:44.668068+0800 LLLL[51081:1190161] _textStorage @"_UICascadingTextStorage"
2021-01-20 10:06:44.668145+0800 LLLL[51081:1190161] _linkTextAttributes @"NSDictionary"
2021-01-20 10:06:44.668221+0800 LLLL[51081:1190161] _pasteController @"UITextPasteController"
2021-01-20 10:06:44.668360+0800 LLLL[51081:1190161] _inputView @"UIView"
2021-01-20 10:06:44.668574+0800 LLLL[51081:1190161] _inputAccessoryView @"UIView"
2021-01-20 10:06:44.668765+0800 LLLL[51081:1190161] _recentsAccessoryView @"UIView"
2021-01-20 10:06:44.669371+0800 LLLL[51081:1190161] _systemInputViewController @"UISystemInputViewController"
2021-01-20 10:06:44.669459+0800 LLLL[51081:1190161] _atomBackgroundView @"UITextFieldAtomBackgroundView"
2021-01-20 10:06:44.669525+0800 LLLL[51081:1190161] _textDragDropSupport @"<UITextDragDropSupport>"
2021-01-20 10:06:44.669603+0800 LLLL[51081:1190161] _textItemDiscoverer @"_UITextItemDiscoverer"
2021-01-20 10:06:44.669681+0800 LLLL[51081:1190161] _textFieldFlags {?="verticallyCenterText"b1"isAnimating"b4"inactiveHasDimAppearance"b1"becomesFirstResponderOnClearButtonTap"b1"clearsPlaceholderOnBeginEditing"b1"adjustsFontSizeToFitWidth"b1"fieldEditorAttached"b1"canBecomeFirstResponder"b1"shouldSuppressShouldBeginEditing"b1"inResignFirstResponder"b1"undoDisabled"b1"explicitAlignment"b1"implementsCustomDrawing"b1"needsClearing"b1"suppressContentChangedNotification"b1"allowsEditingTextAttributes"b1"usesAttributedText"b1"backgroundViewState"b2"clearingBehavior"b2"overridePasscodeStyle"b1"shouldResignWithoutUpdate"b1"blurEnabled"b1"visualEffectViewEnabled"b1"disableFocus"b1"disableRemoteTextEditing"b1"allowsAttachments"b1"isReceivingDrop"b1"contentCoverUnsecuresText"b1"forcesClearButtonHighContrastAppearance"b1"contentInsetsFromFontsValid"b1}
2021-01-20 10:06:44.669868+0800 LLLL[51081:1190161] _deferringBecomeFirstResponder B
2021-01-20 10:06:44.670089+0800 LLLL[51081:1190161] _animateNextHighlightChange B
2021-01-20 10:06:44.670723+0800 LLLL[51081:1190161] _cuiCatalog @"CUICatalog"
2021-01-20 10:06:44.671168+0800 LLLL[51081:1190161] _cuiStyleEffectConfiguration @"CUIStyleEffectConfiguration"
2021-01-20 10:06:44.671270+0800 LLLL[51081:1190161] _roundedRectBackgroundCornerRadius d
2021-01-20 10:06:44.671352+0800 LLLL[51081:1190161] _overriddenAttributesForEditing @"NSArray"
2021-01-20 10:06:44.671407+0800 LLLL[51081:1190161] _adjustsFontForContentSizeCategory B
2021-01-20 10:06:44.671486+0800 LLLL[51081:1190161] _tvUseVibrancy B
2021-01-20 10:06:44.671550+0800 LLLL[51081:1190161] _disableTextColorUpdateOnTraitCollectionChange B
2021-01-20 10:06:44.671659+0800 LLLL[51081:1190161] _pasteDelegate @"<UITextPasteDelegate>"
2021-01-20 10:06:44.671865+0800 LLLL[51081:1190161] _baselineLayoutConstraint @"NSLayoutConstraint"
2021-01-20 10:06:44.672058+0800 LLLL[51081:1190161] _baselineLayoutLabel @"_UIBaselineLayoutStrut"
2021-01-20 10:06:44.672269+0800 LLLL[51081:1190161] _tvCustomTextColor @"UIColor"
2021-01-20 10:06:44.672410+0800 LLLL[51081:1190161] _tvCustomFocusedTextColor @"UIColor"
2021-01-20 10:06:44.672573+0800 LLLL[51081:1190161] _textDragOptions q
2021-01-20 10:06:44.672777+0800 LLLL[51081:1190161] _textDragDelegate @"<UITextDragDelegate>"
2021-01-20 10:06:44.672989+0800 LLLL[51081:1190161] _textDropDelegate @"<UITextDropDelegate>"
2021-01-20 10:06:44.673218+0800 LLLL[51081:1190161] _visualStyle @"_UITextFieldVisualStyle"
举例2、字典转模型
好多第三方字典转模型 都会用到runtime 一般都是便利取出 模型(类)中的属性获取属性名称 然后经过一系列的复杂处理 得到一个字符串,最后经过 KVC来设置属性值 (可自行查看 三方字典转模型的内部实现)