Runtime在iOS开发中的实际利用

运作时的稿子向来被同班们热炒,当然现在面试中也都欣赏问道,当大家说的大王是道时候,可到真正的体系中差不离局限只会涉及对象或者MethodSwizzling奉为神剑四处挥砍,开发毕竟不可以用空想来安慰自己,实践出真知,介绍近日在档次中runtime的求实选拔,真切希望和各位同学切磋。

1 关联对象(AssociatedObject )

Catagory主要为早已存在的类(主要是系统类)扩大新的格局,关联对象是runtime在付出中行使的最普遍,其根本用以为Catagory的靶子增添质量。

AFNetworking的关联对象的

Masony的关系的目的

至于分类的介绍可以查看美团技术公司写的深深明白Objective-C:Category

1.1 为何catagory 不可能设置属性

struct objc_category {
    char *category_name                                      OBJC2_UNAVAILABLE;
    char *class_name                                         OBJC2_UNAVAILABLE;
    struct objc_method_list *instance_methods                OBJC2_UNAVAILABLE;
    struct objc_method_list *class_methods                   OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

分拣中得以拉长实例方法,类措施,甚至能够兑现协议,添加属性,不得以添加成员变量。主要因为方法定义都在objc_class中管理的,不管怎样增删方法,都不影响类实例的内存布局,创设一个目的自然会分配一块内存区域,包蕴了isa指针和享有的积极分子变量。如果允许动态修改类成员变量布局,已经创办出的类实例就不相符类定义了,变成了不算对象。

1.2 相关函数

//为一个实例对象添加一个关联对象,由于是C函数只能使用C字符串,这个key就是关联对象的名称,value为具体的关联对象的值,policy为关联对象策略,与我们自定义属性时设置的修饰符类似
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
//通过key和实例对象获取关联对象的值
id objc_getAssociatedObject(id object, const void *key);
//删除实例对象的关联对象
void objc_removeAssociatedObjects(id object);

(1)key值
  关于前多少个函数中的 key 值是大家需求重视关注的一个点,这么些 key
值必须有限接济是一个对象级别(为啥是目标级别?看完上边的章节你就会精通了)的唯一常量。一般的话,有以下二种推荐的
key 值:
声明 static char kAssociatedObjectKey; ,使用 &kAssociatedObjectKey 作为
key 值;
声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; ,使用
kAssociatedObjectKey 作为 key 值;
用 selector ,使用 getter 方法的称号作为 key 值。
static char kAssociatedObjectKey;

objc_getAssociatedObject(self, &kAssociatedObjectKey);

只是还有更简便易行的办法, 可以运用selector:

objc_getAssociatedObject(self, @selector(associatedObject));

抑或直接拔取_cmd: _cmd在Objective-C的措施中代表近日情势的selector,
正就好像self表示调用当前艺术的对象(类)一样.

objc_getAssociatedObject(self, _cmd);

(2) 关联规则 objc_AssociationPolicy
policy和property使用的修饰符神似,具体意思也与property修饰符相同。

image.png

objc_AssociationPolicy modifier
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic, strong
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic, copy
OBJC_ASSOCIATION_RETAIN atomic, strong
OBJC_ASSOCIATION_COPY atomic, copy

(3)objc_removeAssociatedObjects函数实际行使很少,它会移除一个对象的持有关乎对象,将该目标苏醒成“原始”状态。那样做就很有可能把别人添加的关系对象也一并移除,那并不是我们所梦想的。所以一般的做法是通过给
objc_setAssociatedObject 函数传入 nil 来移除某个已有些涉及对象。

1.4 category关联对象的光景原理

isa 结构体中的标记位 has_assoc 标记为
true,表示近日目标有关联对象,关联对象并不是成员变量,关联对象是由一个大局哈希表存储的键值对中的值。

2 对象关联映射(ORM)

由此逆向APP会发现眼前目的转模型那块近来主要用的是MJExtension和YYModel,老项目一般是MJExtension,新崛起的花色转到了YYModel上。利用runtime
大家可以落成json数据的直接转换成对象模型,或者把模型通过炫耀拼接成晦涩的sql语句,直接达成了对象存储到sqlite数据库

MJExtension

YYModel中的YYClassInfo

中间ORM首要涉及到瞬间主意:
获取属性列表

objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
   const char *propertyName = property_getName(propertyList[i]);
   NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}

收获成员变量列表

Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
    Ivar myIvar = ivarList[i];
    const char *ivarName = ivar_getName(myIvar);
    NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}

得到方式列表

unsigned int count;
  Method *methodList = class_copyMethodList([self class], &count);
  for (int i = 0; i < count; i++) {
      Method method = methodList[i];
      DebugLog(@"------getRunTimeMethodList: %@",NSStringFromSelector(method_getName(method)));
  }

3 热修复(HotfixPatch)

苹果审核从来被开发者吐槽的,一是苹果审核的严刻,种种理由反反复复被打回去欲哭无泪,二是甄别周期长,在二零一七年以前苹果审核的周期一般都在三天,即使是新利用甚至须求一周以上,要是碰上圣诞节苹果放假大家那边是一般都不会付出审查,于是JSPatch
为代表的热修复技术被开发者推崇,通过逆向中国市面上有头有脸的iOS应用,我意识大概都选取JSPath或者JSPath的变种。以至于苹果发邮件禁止利用热修复时
整个JSPath的Issues被炸锅了。热修复首要做的是替换现有的法子,或者伸张新点子,要求对音讯发送和转账有肯定的精通。

3.1 音信转载_objc_msgForward

-[*** ***]:unrecognized selector sent to instance 0x*****

这些是ios开发中最广泛的crash,当前目标找不到这几个措施,实际上苹果
调用doesNotRecognizeSelector方法的时候,是给了俺们三遍机遇的。就是我们常说的消息转载,
举一个板栗,我在工作中项目出现了差错,本着挽救同志的目的,领导让自家随即立即提供一遍挽回的点子,假若本身给力那个风险到此没了,可是自己跪了搞不定,领导就问哪个人可以化解,那是老王站了出去,如若老王接盘搞定了这几个危害那也清闲了,不过老王也远非解决
领导就会找小李啊或者小张处理,如若我们都沉默不能缓解,
那就项目根本破产啦。oc中新闻转载差不离就是如此的。

+(BOOL)resolveInstanceMethod:(SEL)sel// 实例方法
+(BOOL)resolveClassMethod:(SEL)sel // 类方法

先是次机遇允许用户在此时为该Class动态增加达成。假使有落到实处了,则调用并回到。

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);

只要仍没兑现,继续下边的动作。

-(id)forwardingTargetForSelector:(SEL)aSelector 

调用forwardingTargetForSelector:方法,尝试找到一个能响应该消息的对象。假设得到到,则一直转载给它。如若回到了nil,继续上面的动作。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation

透过 -methodSignatureForSelector: 新闻得到函数的参数和再次来到值类型。假诺-methodSignatureForSelector: 再次回到 nil ,Runtime 则会时有爆发-doesNotRecognizeSelector:
信息,程序那时也就挂掉了。即使回去了一个函数签名,Runtime 就会创立一个
NSInvocation 对象并发送 -forwardInvocation: 新闻给指标对象。
NSInvocation 是一个新闻体的包装,包罗selector
以及参数等音信。因而JSPatch通过NSInvocation来创制信息

JSPatch

NSInvocation可以落成传递三个参数。

- (void)viewDidLoad {
    [super viewDidLoad];
    //创建一个函数签名,这个签名可以是任意的,但需要注意,签名函数的参数数量要和调用的一致。
    NSMethodSignature * signature = [[self class] instanceMethodSignatureForSelector:@selector(testFun:argb:)];
    NSLog(@"参数个数%lu---返回参数类型%s",signature.numberOfArguments,signature.methodReturnType);
    //通过签名初始化
    NSInvocation * invocatin = [NSInvocation invocationWithMethodSignature:signature];
    NSString *argumentOne = @"First";
    NSString *argumentTwo = @"Two";
    //atIndex的下标必须从2开始。原因为:0 1 两个参数已经被target和selector占用
    [invocatin setArgument:&argumentOne atIndex:2];
    [invocatin setArgument:&argumentTwo atIndex:3];
    [invocatin setTarget:self];//设置target
    [invocatin setSelector:@selector(testFun:argb:)];//设置selecteor
    [invocatin invoke];//消息调用
}
-(NSString*)testFun:(NSString*)argc argb:(NSString*)argb{
    //实现 [argc stringByAppendingString:argb];
    NSString* string = argc;
    NSString* aString;
    NSString* stringToAppend = argb;
    NSInvocation* inv = [NSInvocation invocationWithMethodSignature:[NSString instanceMethodSignatureForSelector:@selector(stringByAppendingString:)]];
    [inv setTarget: string];
    [inv setSelector:@selector(stringByAppendingString:)];
    [inv setArgument:&stringToAppend atIndex:2];
    [inv retainArguments];
    [inv invoke];
    // 获取返回值
    [inv getReturnValue:&aString];
    return aString;
}

4 私有变量的修改

第一是运用class_copyIvarList获取当前类的所有属性,首要为了取得个人变量然后使用KVC修改对象的品质。
透过打印UITextField的习性,获取到变量名称为_placeholderLabel,可以修改placeholder字体颜色。

unsigned int outCount = 0;
   Ivar *ivars  = class_copyIvarList([UITextField class], &outCount);
    for (NSInteger i = 0; i < outCount; ++i) {
        // 遍历取出该类成员变量
        Ivar ivar = *(ivars + i);
        NSLog(@"\n name = %s  \n type = %s", ivar_getName(ivar),ivar_getTypeEncoding(ivar));
    }

KVC 修改属性值

[_textView setValue: [UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];

貌似地方写法用的很少,尽快替换了章程仍然有无数坑等着
相似我们的用法直接KVC 替换系统原本的变量

UITextField *textFiled = [[UITextField alloc] initWithFrame:CGRectMake(20, 100, 100, 50)];
    [self.view addSubview:textFiled];
    UILabel *placeholderLabel = [UILabel new];
    placeholderLabel.textColor = [UIColor redColor];
    [placeholderLabel sizeToFit];
    placeholderLabel.text = @"请输入密码";
    [textFiled addSubview:placeholderLabel];
    [textFiled setValue:placeholderLabel forKey:@"_placeholderLabel"];

5 面向切面编程(AOP)

最首要行使Method Swizzling
在不破话原有的代码,将单身的作用模块剥离出去,落成代码注入。

5.1 Method Swizzling

+ (BOOL)swizzleInstanceMethod:(SEL)originalSel with:(SEL)newSel {
    Method originalMethod = class_getInstanceMethod(self, originalSel);
    Method newMethod = class_getInstanceMethod(self, newSel);
    if (!originalMethod || !newMethod) return NO;

    class_addMethod(self,
                    originalSel,
                    class_getMethodImplementation(self, originalSel),
                    method_getTypeEncoding(originalMethod));
    class_addMethod(self,
                    newSel,
                    class_getMethodImplementation(self, newSel),
                    method_getTypeEncoding(newMethod));

    method_exchangeImplementations(class_getInstanceMethod(self, originalSel),
                                   class_getInstanceMethod(self, newSel));
    return YES;
}

+ (BOOL)swizzleClassMethod:(SEL)originalSel with:(SEL)newSel {
    Class class = object_getClass(self);
    Method originalMethod = class_getInstanceMethod(class, originalSel);
    Method newMethod = class_getInstanceMethod(class, newSel);
    if (!originalMethod || !newMethod) return NO;
    method_exchangeImplementations(originalMethod, newMethod);
    return YES;
}

最关键的是须求明白selector, method, implementation
三者之间关系:在运行时,类(Class)维护了一个信息分发列表来解决新闻的科学发送。每一个音信列表的进口是一个艺术(Method),这么些点子映射了一对键值对,其中键值是那些主意的名字
selector(SEL),值是指向这么些措施落成的函数指针 implementation(IMP)。
Method swizzling 修改了类的消息分发列表使得曾经存在的 selector
映射了另一个达成 implementation,同时重命名了原生方法的兑现为一个新的
selector。

NSPipster的Method Swizzling

Method Swizzling需求留意的是:
(1)应该总在+load中执行,+load会在类初叶加载时调用,和+initialize比较+load能确保在类的开端化进度中被加载。
(2)
dispatch_once中举办:swizzling会改变全局状态,所以在运作时行使一些预防措施,使用dispatch_once就可见确保代码不管有些许线程都只被实施三次。那将改为method
swizzling的超级实践。

5.2日记打印 快捷熟谙项目。

程序猿是跳槽率偏高的职业,即使去新公司做新类型还好说,一旦须求接手老项目标护卫,商业项目可不是大家平日写的Demo的代码量,那代码中的逻辑结构弹指间会让新入职的同伙们懵逼,通过通过拦截点击事件,可以飞快的熟习代码的逻辑。

image.png

5.3拍卖通用逻辑

譬如在有的界面大家须要用户登录才能查看,最笨的不二法门实在实在必要的ViewController
添加判断登录的逻辑。下边那张截图是从Github的找到的选取AOP处理用户登录的代码,当然那个用持续基础类去写也是未可厚非的,暂且不要在意写法的好坏
最起码我们先后支付提供了新的思绪。

处理用户登录

5.4Crash的防范

OC中容器类在空值nil 和数组越界都会直接促成我们app 的crash
大家一种处理格局是运用Category增添新措施中判断值是还是不是为空或者越界,对于新工程大家运用我们约定使用容器的category还好说,

- (id)objectOrNilAtIndex:(NSUInteger)index {
    return index < self.count ? self[index] : nil;
}

然则对于老品种
大家难道要求修改所有的器皿类措施?大家得以接纳切面来修改。

+(void)load{

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        Class __NSPlaceholderArray = NSClassFromString(@"__NSPlaceholderArray");
        [NSArray swizzleInstance:__NSPlaceholderArray origMethod:@selector(initWithObjects:count:) withMethod:@selector(RBSafe_initWithObjects:count:)];

        Class __NSArray = NSClassFromString(@"NSArray");
        Class __NSArrayI = NSClassFromString(@"__NSArrayI");//数组有内容obj类型才是__NSArrayI
        Class __NSSingleObjectArrayI = NSClassFromString(@"__NSSingleObjectArrayI");//iOS10 以上,单个内容类型是__NSArraySingleObjectI
        Class __NSArray0 = NSClassFromString(@"__NSArray0");//iOS9 以上,没内容类型是__NSArray0

        [NSArray swizzleInstance:__NSArray origMethod:@selector(subarrayWithRange:) withMethod:@selector(RBSafe_subarrayWithRange:)];

        [NSArray swizzleInstance:__NSArray origMethod:@selector(objectsAtIndexes:) withMethod:@selector(RBSafe_objectsAtIndexes:)];

        [NSArray swizzleInstance:__NSArrayI origMethod:@selector(objectAtIndex:) withMethod:@selector(RBSafe_NSArrayIobjectAtIndex:)];

        [NSArray swizzleInstance:__NSSingleObjectArrayI origMethod:@selector(objectAtIndex:) withMethod:@selector(RBSafe_NSSingleObjectArrayIobjectAtIndex:)];

        [NSArray swizzleInstance:__NSArray0 origMethod:@selector(objectAtIndex:) withMethod:@selector(RBSafe_NSArray0ObjectAtIndex:)];

    });
}

本来那种用法
我个人是持中立态度的,因为可以刹那间把大家代码所犯的错误处理的安宁,可是让自家有一种一叶障目的痛感,大家的标题和不当根源还在的,不断的谬误叠加只会让我们代码变得风险重重,同时AOP的crash处理是无痛无感知的,一旦大家应用在第三方的静态库实际上大家就会入侵被人工程的代码,被人的代码被曲解都不知情的,这一个必要谨慎使用。

6 逆向开发

逆向开发重点集中在iOS越狱方面,逆向开发可以让我们在iOS开发中开辟另一扇门,对于大部门开发者来说很少接触那几个小圈子,我也是在工作中才接触到iOS的越狱,逆向开发的底子就是行使Method
Swizzling,不管是当今紧俏的THEOS如故iOSOpenDev都是Method
Swizzling的包装,点击iOSOpenDev使用的CaptainHook就足以观察都是Method
Swizzling 各样格局。

#import <Cycript/Cycript.h>
#import <CaptainHook/CaptainHook.h>

#define CYCRIPT_PORT 8888

CHDeclareClass(AppDelegate);
CHDeclareClass(UIApplication);

CHOptimizedMethod2(self, void, AppDelegate, application, UIApplication *, application, didFinishLaunchingWithOptions, NSDictionary *, options)
{
    CHSuper2(AppDelegate, application, application, didFinishLaunchingWithOptions, options);

    NSLog(@"## Start Cycript ##");
    CYListenServer(CYCRIPT_PORT);
}

__attribute__((constructor)) static void entry() {
    CHLoadLateClass(AppDelegate);
    CHHook2(AppDelegate, application, didFinishLaunchingWithOptions);
}
网站地图xml地图