Method Swizzle的危机

在同时使用RAC和Aspects的时候,遇到了一个Crash,栈溢出了。

看了一下,是之前在项目中使用了RAC的rac_singalForSelector

1
@weakify(self);
[[viewController rac_signalForSelector:@selector(viewDidAppear:)] subscribeNext
:^(id x) {
      @strongify(self);
      self.shouldIgnorePushingViewControllers = NO;
}];

后来他又使用了Aspects库中的aspect_hookSelector

1
[UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions
:AspectPositionAfter usingBlock:^(id<AspectInfo> info, BOOL animated) {
    NSLog(@"AOP: %@ - %d", [info.instance class], animated);
} error:NULL];

后来就仔细看了一下两者的实现,从接口上来说,RAC的rac_singalForSelector的实例对象方法Aspects的aspect_hookSelector类对象方法。

第一感觉是前者针对是实例对象,后者针对的是类对象。主要涉及的其实还是对象模型里的一些知识,操作了类对象和实例对象中的数据如果对对象模型不太熟悉,可以看一下我之前写的一篇Blog:对象模型

现在来分析下原因,从源码入手,先来大致看RAC相关的源码:

1
static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, 
                                               Protocol *protocol) {
  //aliasSelector是为了区分原Selector,RAC加了自己的前缀
	SEL aliasSelector = RACAliasForSelector(selector);

	@synchronized (self) {
         //这里是根据aliasSelector获得相应的冷信号,这个冷信号主要是用于通知业务层的回调
         //也就是subscribeNext后面那一段block
		RACSubject *subject = objc_getAssociatedObject(self
                                                       , aliasSelector);
		if (subject != nil) return subject;

         //这个地方是基于当前self类创建了一个新类型,有很多hook的逻辑都放在这个类型中
         //具体逻辑在RACSwizzleClass函数中再分析
		Class class = RACSwizzleClass(self);
		NSCAssert(class != nil, @"Could not swizzle class of %@", self);

         //创建一个用于通知的冷信号
		subject = [[RACSubject subject] setNameWithFormat:@"%@ 
                   -rac_signalForSelector: %s", 
                   self.rac_description, 
                   sel_getName(selector)];

         //通过关联对象的方式存储冷信号
		objc_setAssociatedObject(self, 
                                 aliasSelector, 
                                 subject, 
                                 OBJC_ASSOCIATION_RETAIN);
		[self.rac_deallocDisposable addDisposable: 
                             [RACDisposable  disposableWithBlock:^{
			                      [subject sendCompleted];
		}]];

         //targetMethod是"viewDidApear"这个Selector的方法对象
		Method targetMethod = class_getInstanceMethod(class, selector);
		if (targetMethod == NULL) {
          //按照现有逻辑,这里应该是走不到的,先不去看它
			const char *typeEncoding;
			if (protocol == NULL) {
			typeEncoding = RACSignatureForUndefinedSelector(selector);
			} else {
		// Look for the selector as an optional instance method.
				struct objc_method_description methodDescription =
                  protocol_getMethodDescription(protocol, selector, NO, YES);

				if (methodDescription.name == NULL) {
				// Then fall back to looking for a required 
                  //instance method.
					methodDescription = 
                      protocol_getMethodDescription(protocol
                                                    , selector
                                                    , YES
                                                    , YES);
					NSCAssert(methodDescription.name != NULL
                              , @"Selector %@ does not exist in <%s>"
                              ,NSStringFromSelector(selector)
                              , protocol_getName(protocol));
				}

				typeEncoding = methodDescription.types;
			}

			RACCheckTypeEncoding(typeEncoding);

			// Define the selector to call -forwardInvocation:.
			if (!class_addMethod(class, 
                                 selector,
                                 _objc_msgForward, 
                                 typeEncoding)) {
				NSDictionary *userInfo = @{
					NSLocalizedDescriptionKey: 
                                         [NSString stringWithFormat:
                                                NSLocalizedString(
                                                  @"A race condition occurred 
                                                  implementing %@ on class %@"
                                                  , nil)
                                                ,NSStringFromSelector(selector)
                                                , class],
					NSLocalizedRecoverySuggestionErrorKey: 
                   NSLocalizedString(@"Invoke -rac_signalForSelector: 
                                     again to override the implementation.", nil)
				};

				return [RACSignal error:[NSError errorWithDomain:
                                         RACSelectorSignalErrorDomain
                                code:RACSelectorSignalErrorMethodSwizzlingRace
                                         userInfo:userInfo]];
			}
          //_objc_msgForward是消息进行转发的开始,这里主要是判断原始的
          //方法对象有没有被hook成_objc_msgForward,因为下面要开始进行方法实现的替换了
		} else if (method_getImplementation(targetMethod) != 
                   _objc_msgForward) {
			// Make a method alias for the existing 
             //method implementation.
			const char *typeEncoding =
              method_getTypeEncoding(targetMethod);
			RACCheckTypeEncoding(typeEncoding);
             //class_addMethod只是在当前类型对象(不会去基类中查找)中
             //去找有没有aliasSelector的方法,如果有就直接返回原来的方法实现
            //如果没有就添加一个
			BOOL addedAlias __attribute__((unused)) = 
              class_addMethod(class, aliasSelector, 
                              method_getImplementation(targetMethod), 
                              typeEncoding);
			NSCAssert(addedAlias, @"Original implementation 
                      for %@ is already copied to %@ on %@", 
                      NSStringFromSelector(selector), 
                      NSStringFromSelector(aliasSelector), class);
			// Redefine the selector to call -forwardInvocation:.
             // 这里相当于对原始方法进行方法实现的替换,
             //只要调用原始方法,直接会开始方法转发的过程
			class_replaceMethod(class
                                , selector
                                , _objc_msgForward,
                                method_getTypeEncoding(targetMethod));
		}

		return subject;
	}
}

通过原始英文注释加上后来的中文注释应该能比较好的理解大致的过程了

分为2部分

1是创建用于通知业务层回调的冷信号

2是Hook了当前类,创建了新的子类型,并将这个子类型的原始selector进行了方法实现的替换

既将原始selector实现替换成_objc_msgForward,增加了一个新的selector prefix_selector,

并将其实现为原始selector。另外个人觉得命名上最好是不要叫class了,可以改为subClass或者

swizzleClass,容易跟原始class搞混

接着就是RACSwizzleCalss函数了:

1
static Class RACSwizzleClass(NSObject *self) {
  //self.class和object_getClass返回的都是当前实例的类对象,好像没什么区别???
	Class statedClass = self.class;
	Class baseClass = object_getClass(self);

	// The "known dynamic subclass" is the subclass generated by RAC.
	// It's stored as an associated object on every instance that's already
	// been swizzled, so that even if something else swizzles the class of
	// this instance, we can still access the RAC generated subclass.
    //这里官方说明了子类的用途,如果有其他地方swizzle了当前类对象,那也不太影响RAC自己的逻辑
    //因为它所有的信息都保存在自己创建的子类中。
	Class knownDynamicSubclass = 
      objc_getAssociatedObject(self, RACSubclassAssociationKey);
	if (knownDynamicSubclass != Nil) return knownDynamicSubclass;

	NSString *className = NSStringFromClass(baseClass);

	if (statedClass != baseClass) {
         //这部分逻辑没走到,先不看
		@synchronized (swizzledClasses()) {
			if (![swizzledClasses() containsObject:className]) {
				RACSwizzleForwardInvocation(baseClass);
				RACSwizzleRespondsToSelector(baseClass);
				RACSwizzleGetClass(baseClass, statedClass);
				RACSwizzleGetClass(object_getClass(baseClass), 
                                   statedClass);
				RACSwizzleMethodSignatureForSelector(baseClass);
				[swizzledClasses() addObject:className];
			}
		}

		return baseClass;
	}
     //加上RAC自己的前缀,给新的子类命名
	const char *subclassName = [className stringByAppendingString:
                                RACSubclassSuffix].UTF8String;
	Class subclass = objc_getClass(subclassName);

	if (subclass == nil) {
        //createClass里面调用的是objc_allocateClassPair函数用于创建新的类型对象,
        //它和下面的objc_registerClassPair函数是配对使用的
		subclass = [RACObjCRuntime createClass:subclassName 
                    inheritingFromClass:baseClass];
		if (subclass == nil) return nil;
         //这里是对新的子类的forwardInvocation方法hook
		RACSwizzleForwardInvocation(subclass);
         //hookRespondsToSelector方法
		RACSwizzleRespondsToSelector(subclass);
         //hook了Class方法
		RACSwizzleGetClass(subclass, statedClass);
         //这里object_getClass得到的还是statedClass,既原始的类。
		RACSwizzleGetClass(object_getClass(subclass), statedClass);
         //hook了方法签名
		RACSwizzleMethodSignatureForSelector(subclass);
		//结束子类的创建
		objc_registerClassPair(subclass);
	}
	//这里是将当前实例对象的类型信息改写成新的子类
	object_setClass(self, subclass);
    //保存子类信息
	objc_setAssociatedObject(self, RACSubclassAssociationKey, subclass, 
                             OBJC_ASSOCIATION_ASSIGN);
	return subclass;
}

RACSwizzleCalss函数里主要干的事情就是创建子类型:

1.给新的子类型命名

2.hook几个重要的消息转发方法,1是为了得到方法调用的入口 2是将子类伪装成原始的类型

3.改变了实例对象的isa指针为新的子类,当前实例对象的类型信息为新的子类信息

这里有必要看一下RACSwizzleGetClass函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
static void RACSwizzleGetClass(Class class, Class statedClass) {
SEL selector = @selector(class);
Method method = class_getInstanceMethod(class, selector);
IMP newIMP = imp_implementationWithBlock(^(id self) {
//当调用子类的class方法的时候,返回的还是原始类型对象
return statedClass;
});
class_replaceMethod(class
, selector
, newIMP
, method_getTypeEncoding(method));
}

这样一来,使用者感觉不到有什么异样,不知道hook的存在了

最后重点看一下RACSwizzleForwardInvocation函数:

1
static void RACSwizzleForwardInvocation(Class class) {
	SEL forwardInvocationSEL = @selector(forwardInvocation:);
	Method forwardInvocationMethod = 
      class_getInstanceMethod(class
                              , forwardInvocationSEL);

	// Preserve any existing implementation of -forwardInvocation:.
	void (*originalForwardInvocation)(id, SEL, NSInvocation *) = NULL;
	if (forwardInvocationMethod != NULL) {
		originalForwardInvocation =
(__typeof__(originalForwardInvocation))method_getImplementation(
          forwardInvocationMethod);
	}

	// Set up a new version of -forwardInvocation:.
	//
	// If the selector has been passed to -rac_signalForSelector:, invoke
	// the aliased method, and forward the arguments to any attached signals.
	//
	// If the selector has not been passed to -rac_signalForSelector:,
	// invoke any existing implementation of -forwardInvocation:. If there
	// was no existing implementation, throw an unrecognized selector
	// exception.
	id newForwardInvocation = ^(id self, NSInvocation *invocation) {
		BOOL matched = RACForwardInvocation(self, invocation);
		if (matched) return;

		if (originalForwardInvocation == NULL) {
			[self doesNotRecognizeSelector:invocation.selector];
		} else {
             //如果走到这里,基本上也要抛出异常了,一搬hook的都是必有的方法,已经绕过了
             //原来的方法查找的过程到了消息转发这一步
			originalForwardInvocation(self, forwardInvocationSEL, 
                                      invocation);
		}
	};

	class_replaceMethod(class, forwardInvocationSEL,
                        imp_implementationWithBlock(newForwardInvocation)
                        , "v@:@");
}

用newForwardInvocation替换了原来的forwardInvocation实现,里面又调用了RACForwardInvocation函数:

1
static BOOL RACForwardInvocation(id self, NSInvocation *invocation) {
    //这里的invocation还是原始的方法调用信息,手动改成了带前缀的selector
	SEL aliasSelector = RACAliasForSelector(invocation.selector);
    //获取一下之前存储的冷信号
	RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
    //这里的class得到的是RAC创建的子类,因为之前已经改写了实例对象的ISA指针
	Class class = object_getClass(invocation.target);
	BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector];
	if (respondsToAlias) {
        //去改变selector为hook的新的selector,不然要死循环了
		invocation.selector = aliasSelector;
         //这一步就是真正原始的方法调用了,还记得之前已经把aliasSelector的实现改为
         //原始Selector的实现了吧
		[invocation invoke];
	}

	if (subject == nil) return respondsToAlias;
    //通知业务层,要监听的selector已经被调用了,这个发生在原始方法调用之后
	[subject sendNext:invocation.rac_argumentsTuple];
	return YES;
}

前面几个函数都是为这一步打基础的,到了真正方法调用的时候,原始方法的调用以及通知业务层回调都在这里完成。

通过以上分析,现在通过一张图总结一下:

RAC Swizzle Class

现在看一下Aspectsaspect_hookSelector的实现,主要是aspect_prepareClassAndHookSelector函数

1
static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector
                                               , NSError **error) {
    NSCParameterAssert(selector);
   //这里等看aspect_hookClass具体分析其过程,暂时来看是生成了新子类
    Class klass = aspect_hookClass(self, error);
   //获得原始方法对象
    Method targetMethod = class_getInstanceMethod(klass, selector);
    IMP targetMethodIMP = method_getImplementation(targetMethod);
    if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
        // Make a method alias for the existing method implementation, 
        //it not already copied.
        const char *typeEncoding = method_getTypeEncoding(targetMethod);
        SEL aliasSelector = aspect_aliasForSelector(selector);
        if (![klass instancesRespondToSelector:aliasSelector]) {
          //添加alias方法,实现为原方法实现
            __unused BOOL addedAlias = 
              class_addMethod(klass            
                              , aliasSelector                
                              ,method_getImplementation(targetMethod)         
                              ,typeEncoding);
            NSCAssert(addedAlias, @"Original implementation for %@ is already 
                      copied to %@ on %@", NSStringFromSelector(selector),
                      NSStringFromSelector(aliasSelector), klass);
        }

        // We use forwardInvocation to hook in.
        //这里跟RAC一样的做法,把原方法实现换成了_objc_msgForward
        //调原方法会直接开始消息转发的过程
        class_replaceMethod(klass
                            , selector
                            , aspect_getMsgForwardIMP(self, selector)
                            ,typeEncoding);
        AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass,
                  NSStringFromSelector(selector));
    }
}

大致上来看,在方法实现的层面的处理和RAC很类似.实际调试过程中,aspect_hookClass最终只是调用了aspect_swizzleClassInPlace,然后aspect_swizzleClassInPlace其实调用了aspect_swizzleForwardInvocation现在来看一下aspect_swizzleForwardInvocation函数:

1
static void aspect_swizzleForwardInvocation(Class klass) {
    NSCParameterAssert(klass);
    // If there is no method, replace will act like class_addMethod.
    //将forwardInvocation方法实现替换成了__ASPECTS_ARE_BEING_CALLED__
    IMP originalImplementation = class_replaceMethod(klass
                                          ,@selector(forwardInvocation:)
                                          ,(IMP)__ASPECTS_ARE_BEING_CALLED__
                                           ,"v@:@");
    if (originalImplementation) {
     //alias的forwardInvocation换成了原始实现
        class_addMethod(klass, 
                        NSSelectorFromString(AspectsForwardInvocationSelectorName),
                        originalImplementation, "v@:@");
    }
    AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
}

在消息转发的最后关头,调用ASPECTS_ARE_BEING_CALLED

1
static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, 
                                         SEL selector, 
                                         NSInvocation *invocation) {
    NSCParameterAssert(self);
    NSCParameterAssert(invocation);
    SEL originalSelector = invocation.selector;
    //得到相应的aliasSelector
	SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
    //替换selector,因为invocation中的selector还是原始的selector
    //不然递归死循环跟RAC一样
    invocation.selector = aliasSelector;
    //通过aliasSelector得到hook的用户回调
    AspectsContainer *objectContainer = objc_getAssociatedObject(self, 
                                                                 aliasSelector);
    AspectsContainer *classContainer = aspect_getContainerForClass(
      object_getClass(self), 
      aliasSelector);
    AspectInfo *info = [[AspectInfo alloc] initWithInstance:self 
                        invocation:invocation];
    NSArray *aspectsToRemove = nil;

    // Before hooks.
   //触发那些在原始逻辑调用之前的回调
    aspect_invoke(classContainer.beforeAspects, info);
    aspect_invoke(objectContainer.beforeAspects, info);

    // Instead hooks.
    BOOL respondsToAlias = YES;
    if (objectContainer.insteadAspects.count || 
        classContainer.insteadAspects.count) {
          //直接是回调替换原始实现了
        aspect_invoke(classContainer.insteadAspects, info);
        aspect_invoke(objectContainer.insteadAspects, info);
    }else {
        Class klass = object_getClass(invocation.target);
        do {
            if ((respondsToAlias = [klass instancesRespondToSelector:
                                    aliasSelector])) {
                //这里是触发原始方法的实现
                [invocation invoke];
                break;
            }
        }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
    }

    // After hooks.
    //触发那些在原始方法调用之后的用户回调
    aspect_invoke(classContainer.afterAspects, info);
    aspect_invoke(objectContainer.afterAspects, info);

    // If no hooks are installed, call original implementation (usually 
    //to throw an exception)
    if (!respondsToAlias) {
        invocation.selector = originalSelector;
        SEL originalForwardInvocationSEL =
          NSSelectorFromString(AspectsForwardInvocationSelectorName);
        if ([self respondsToSelector:originalForwardInvocationSEL]) {
            ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, 
                                           originalForwardInvocationSEL,
                                                              invocation);
        }else {
            [self doesNotRecognizeSelector:invocation.selector];
        }
    }

    // Remove any hooks that are queued for deregistration.
    [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
}

看样子,在这种情况下A并没有想RAC一样创建新的子类型,它是直接更改了原始类型的方法信息。

用图来表示一下它当前的实现:

Aspects Hook

和RAC比要简单一些,那么当同时使用两个库Hook的时候会出现什么情况呢?按照文章开头的场景,RAC相当于Hook了某个继承自UIViewController的子类,并基于该子类创建了新的子类型Aspects相当于直接Hook了UIViewController,对UIViewController本身的方法实现进行了更改替换现在假设继承自UIViewController业务子类型叫做BusinessVieweController

还是用一张简略图来表示两个库Hook之后的场景吧:

RAC和Aspects一起Hook

在类的继承结构下,Hook之后的结果,Aspects在UIViewController这一层,RAC在subClass这一层当有外界调用A方法时,subClass会最终首先调用原始A方法的实现。如果说在BusisnessViewController类型中定义了A方法的实现,并且在A方法中调用了super的A方法,类似:

1
- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    //业务代码
    //.....
}

那这个时候会继续调用到UIViewController这一层,这个时候就会开始走UIViewController这一层的Hook流程了,还是一样最终会调用到forwardInvocation这个方法,但是这个时候subClass已经实现了这个方法,所以调用流程会回到子类subClass的Hook流程中,见上图中右上角的红色虚线部分。后面的逻辑就是无限循环,在两个类对象中往返直到栈溢出。

通过上面的分析,如果说在BusinessViewController中没有调用super的方法,那么不会引起死循环的,因为不会触发UIViewController类对象的Hook流程。并且当触发了死循环之后,会发现RAC订阅的业务层的回调和BusinessViewController的A方法中除了Super那一句,剩下的业务代码也没有被执行。另外,RAC这种Hook相对来说会更安全一些(并不是说Aspects就没有Hook Class 的情况,只是当前是这样):它完全子类化了业务类,并且将Hook全都保留在了新建的子类型中,对其他类型包括原始业务类型没有干扰并且这种Hook只针对了某个具体的实例对象,其他势力对象如果没有通过调用rac_singalForSelector并不会受到任何影响。

如果是直接Hook原始类型,那么影响的面将是非常广的,包括所有的实例对象和所有的子类对象。

Git Hook

git hook 通过在代码commit和push之前执行一个脚本,来实现一些检查性工作😎。

在.git/hooks目录下的脚本文件,默认情况下该目录下有以下文件

applypatch-msg.sample

commit-msg:sample

pre-applypatch.sample

pre-commit.sample

pre-push.sample

pre-rebase.sample

prepare-commit-msg.sample


update.sample

pre-receive.sample

post-update:sample

分割线前面几种是客户端钩子,后面几种是服务端钩子

pre-receive可以用来在服务端设置一些代码检查的功能,通过它可以拒绝一些不满足条件的push,无情的推行代

码检查。

post-receive它发生在push代码之后,可以通过CI来触发一些测试代码。

若要相应的钩子生效,编辑脚本文件的内容,去掉.sample后缀即可。脚本若返回非0值,则相应的git操作不再

往后执行。

通过pre-push来举个🌰

现在我想在每次push文件之时,检查项目中有没有重复图片,来减少不必要的包体积。假设检查重复图片的

脚本叫做duplicate_image_detector.py

现在通过客户端钩子来实现,因为./.git/hooks下的文件是无法通过git来进行管理,所以只能在客户端自己将

hook脚本拷贝在./.git/hoos目录下了,hook脚本本身可以通过git来管理。

hook脚本如下:

1
#!/bin/bash
# 图片检测
if [ ! -f "./Checkers/duplicate_image_detector.py" ]; then
  echo "未集成图片检测脚本"
  exit 1
fi

如果有多个任务需要处理,下一个任务需要在上一个任务成功之后再执行,则需要通过:

1
$? -eq 0

条件来判断上个任务是否以0结束,以0结束表示执行成功,反之返回非0就代表任务结束,不再继续执行了,
整体类似:

1
#前置任务
#./abc/xyz/xx.rb

if [ $? -eq 0 ]; then
	echo "***********xx.rb检测脚本通过****************"
else
	echo "***********xx.rb检测脚本未通过****************"
	exit 1
fi

# 图片检测
if [ ! -f "./Checkers/duplicate_image_detector.py" ]; then
  echo "未集成图片检测脚本"
  exit 1
fi

如果为了进一步方便,再写一个脚本来检查当前环境是否准备好了hook脚本,并进行本地部署:

1
#!/bin/bash

if ![-f ./shell/pre-push]; then
	echo "没有找到hook的脚本文件"
	exit(1)
fi

if [-f ./.git/hooks/pre-push.sample]; then
	rm ./.git/hooks/pre-push.sample
fi

cp  ./shell/pre-push ./.git/hooks/pre-push

echo "hook设置成功"

每次有新人就让他手动调用一下这个脚本,最好的方式是希望能够将.git/hooks/下的脚本纳入git管理。暂时没有找到解决方案😫,或者部署在服务端,但是需要管理员权限。

针对于上述问题,最后在同事提示下找到一个替换的方案。在Xcode的Build Phases中添加一个”Run Script”功能,在Xcode每次编译的时候都会执行这个脚本。

首先setupHook.sh需要修改一下:

1
#!/bin/bash

if [ -f ./shell/pre-push ]; then
	echo "hook文件存在"
else
	echo "hook文件不存在"
	exit 1
fi

if [ -f ./.git/hooks/pre-push.sample ]; then
	rm ./.git/hooks/pre-push.sample
fi

if [ -f ./.git/hooks/pre-push ]; then
	echo "hook已经设置过"
else
	echo "hook未设置,开始设置"
	cp  ./shell/pre-push ./.git/hooks/pre-push
	echo "hook设置成功"
fi

然后再将setupHook.sh脚本添加进去如下所示:

Run Script

还需要修改一下脚本执行权限,在cmd中执行:

1
chmod u+x setupHook.sh

再Build一下,最后会看到如下输出:

log

就说明设置生效了。

这样一来,就不用太担心某个同学因为忘记设置hook脚本而提交代码没有触发脚本的问题了,因为提交代码之前不太可能不会Build代码,但是还是需要在README中显示的提醒一下,最后一步只是以防万一。

参考

Method Swizzle中的对象模型

通常通过method swizzle可以交换两个方法的实现(不限于同一个类型),先看一段代码:

People类

1
- (void)talk
{
    NSLog(@"%@", self.class);
}

Student类继承People

Student

1
+ (void)load
{
    static dispatch_once_t onceToken;
 
    dispatch_once(&onceToken, ^{
 
        SEL originalSelector = NSSelectorFromString(@"talk");
 
        SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk");
 
        Method originalMethod = class_getInstanceMethod(self.class, originalSelector);
        Method swizzleMethod = class_getInstanceMethod(self.class,
         swizzleSelector);
        method_exchangeImplementations(originalMethod, swizzleMethod);
    });
}
 
- (void)swizzle_talk
{
    NSLog(@"swizzle_talk: %@", self.class);
}

Method在objc-private.h中有如下定义:

1
typedef struct old_method *Method;

old_method是结构体,它定义在objc-runtime-old.h中:

1
struct old_method {
    SEL method_name;
    char *method_types;
    IMP method_imp;
}

Method中包含了3个部分,第一部分是函数名,通常可以通过@selector()获取,第二部分是函数声明, 第三部分是函数实现,理解成函数指针。

class_getInstanceMethod有两个参数,第一个参数是class,第二个参数是selector。这个函数是以class开头的,第一个参数也是传的class对象,所以可以理解为从所传递的类对象中查找指定的数据,类对象可以通过实例对象的class方法活的,类对象全局只有一个。

Class对象的定义如下:

1
typedef struct objc_class *Class;

也就是说Class对象其实是objc_class结构体,平时使用的self.class得到的是一个objc_class的结构体指针。

objc_class定义如下:

1
struct objc_class : objc_object {
 
    Class superclass;
 
    const char *name;
 
    uint32_t version;
 
    uint32_t info;
 
    uint32_t instance_size;
 
    struct old_ivar_list *ivars;
 
    struct old_method_list **methodLists;
 
    Cache cache;
 
    struct old_protocol_list *protocols;
 
    // CLS_EXT only
 
    const uint8_t *ivar_layout;
 
    struct old_class_ext *ext;
}

这里只列出了字段,函数并没有列出。可以看到一个类对象里面包含了以下比较重要的信息:

1.它的基类对象字段superclass

2.它的实例对象有哪些字段 ivars

3.它的实例对象有哪些方法,存储在方法列表中 **methodLists, 这里为什么是指针的指针,就是它可能包含多个方法列表。

4.它属于什么类型的类对象:info,比如CLS_CLASS还是CLS_META,相当于类对象自己的元数据信息。通过它可以判断出一个类对象是否是元类对象。

以下是class_getInstanceMethod的源码:

1
Method class_getInstanceMethod(Class cls, SEL sel)
 
{
 
    if (!cls  ||  !sel) return nil;
 
 
 
 
    // This deliberately avoids +initialize because it historically did so.
 
 
 
 
    // This implementation is a bit weird because it's the only place that
 
    // wants a Method instead of an IMP.
 
 
 
 
    Method meth;
 
    meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
 
    if (meth == (Method)1) {
 
        // Cache contains forward:: . Stop searching.
 
        return nil;
 
    } else if (meth) {
 
        return meth;
 
    }
 
         
 
    // Search method lists, try method resolver, etc.
 
    lookUpImpOrNil(cls, sel, nil,
 
                   NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
 
 
 
 
    meth = _cache_getMethod(cls, sel, _objc_msgForward_impcache);
 
    if (meth == (Method)1) {
 
        // Cache contains forward:: . Stop searching.
 
        return nil;
 
    } else if (meth) {
 
        return meth;
 
    }
 
 
 
 
    return _class_getMethod(cls, sel);
 
}

这一部分主要是先从方法缓存里取方法,主要看下_class_getMethod

1
static Method _class_getMethod(Class cls, SEL sel)
{
    mutex_locker_t lock(methodListLock);
 
    return (Method)_getMethod(cls, sel);
}

在_class_getMethod中调用了_getMethod函数:

1
static inline old_method * _getMethod(Class cls, SEL sel) {
 
    for (; cls; cls = cls->superclass) {
 
        old_method *m;
 
        m = _findMethodInClass(cls, sel);
 
        if (m) return m;
 
    }
 
    return nil;
 
}

_getMethod是主要的实现了,这里通过_findMethodInClass函数来查找类对象的方法,并且便利了父类对象。也就是说,基类中的方法也会被遍历到。

继续再看下_findMethodInClass函数的代码:

1
static inline old_method * _findMethodInClass(Class cls, SEL sel) {
 
    // Flattened version of nextMethodList(). The optimizer doesn't
 
    // do a good job with hoisting the conditionals out of the loop.
 
    // Conceptually, this looks like:
 
    // while ((mlist = nextMethodList(cls, &iterator))) {
 
    //     old_method *m = _findMethodInList(mlist, sel);
 
    //     if (m) return m;
 
    // }
 
 
 
 
    if (!cls->methodLists) {
 
        // No method lists.
 
        return nil;
 
    }
 
    else if (cls->info & CLS_NO_METHOD_ARRAY) {
 
        // One method list.
 
        old_method_list **mlistp;
 
        mlistp = (old_method_list **)&cls->methodLists;
 
        *mlistp = fixupSelectorsInMethodList(cls, *mlistp);
 
        return _findMethodInList(*mlistp, sel);
 
    }
 
    else {
 
        // Multiple method lists.
 
        old_method_list **mlistp;
 
        for (mlistp = cls->methodLists;
 
             *mlistp != nil  &&  *mlistp != END_OF_METHODS_LIST;
 
             mlistp++)
 
        {
 
            old_method *m;
 
            *mlistp = fixupSelectorsInMethodList(cls, *mlistp);
 
            m = _findMethodInList(*mlistp, sel);
 
            if (m) return m;
 
        }
 
        return nil;
 
    }
 
}
  
static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) {
 
    int i;
 
    if (!mlist) return nil;
 
    for (i = 0; i < mlist->method_count; i++) {
 
        old_method *m = &mlist->method_list[i];
 
        if (m->method_name == sel) {
 
            return m;
 
        }
 
    }
 
    return nil;
 
}

这个方法主要是通过遍历类对象的方法列表字段,来查找某个方法。

在_findMethodInList函数中,它其实是比较了方法列表中方法的Selector和要找的Selector是不是同一个来查找这个方法。所以通过selector就可以定位到一个method,也就是可以得到它的IMP和Type了。

所以可以很好理解一下2个方法:

method_getTypeEncoding

method_getImplementation

通过以上分析,可以知道class_getInstanceMethod是获得某个类对象中的方法对象,这个过程中会遍历到父类中。也就是当前类没有实现的方法,父类来抵,也符合面向对象的设计。

总的说来,class_getxxxxxxx是通过查找类对象内部数据来得到一些消息,类似的还有

class_getClassMethod,它是获取类方法的函数:

看看它的源码:

1
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
} 
  
Class getMeta() {
 
   if (isMetaClass()) return (Class)this;
 
   else return this->ISA();
}
  
bool isMetaClass() {
   return info & CLS_META;
}
  
#define CLS_CLASS 0X1
#define CLS_META 0x2

可以知道如果当前类就是元类对象,就返回它自己反之返回this→ISA();

objc_class继承自objc_object,函数ISA是objc_object中定义的:

1
truct objc_object {
 
private:
 
    isa_t isa; 
}
  
uion isa_t {
  
  Class clas;
}

相当于取出objc_class对象的cls信息,也就是元类对象了。

然后通过cls_getInstanceMethod来去到Method信息,跟之前取类对象中的Method一样,只是多了一步取元类对象的步骤。

在理解了class_getInstanceMethod函数之后,再来看一下class_addMethod函数:

1
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
 
{
 
    IMP old;
 
    if (!cls) return NO;
 
    old = _class_addMethod(cls, name, imp, types, NO);
 
    return !old;
 
}
static IMP _class_addMethod(Class cls, SEL name, IMP imp,
 
                            const char *types, bool replace)
{
 
    old_method *m;
 
    IMP result = nil;
 
 
 
 
    if (!types) types = "";
 
 
 
 
    mutex_locker_t lock(methodListLock);
 
 
 
 
    if ((m = _findMethodInClass(cls, name))) {
 
        // already exists
 
        // fixme atomic
 
        result = method_getImplementation((Method)m);
 
        if (replace) {
 
            method_setImplementation((Method)m, imp);
 
        }
 
    } else {
 
        // fixme could be faster
 
        old_method_list *mlist =
 
            (old_method_list *)calloc(sizeof(old_method_list), 1);
 
        mlist->obsolete = fixed_up_method_list;
 
        mlist->method_count = 1;
 
        mlist->method_list[0].method_name = name;
 
        mlist->method_list[0].method_types = strdup(types);
 
        mlist->method_list[0].method_imp = imp;
 
         
 
        _objc_insertMethods(cls, mlist, nil);
 
        if (!(cls->info & CLS_CONSTRUCTING)) {
 
            flush_caches(cls, NO);
 
        } else {
 
            // in-construction class has no subclasses
 
            flush_cache(cls);
 
        }
 
        result = nil;
 
    }
 
    return result;
 
}

相当于当前类对象中存在这个方法的时候(包括父类的),什么都不会处理返回NO。如果不存在那么会添加一个,并且返回YES。

接着是class_replaceMethod

1
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
    if (!cls) return nil;
    return _class_addMethod(cls, name, imp, types, YES);
}

该方法和class_addMethod的区别是,如果发现已经存在sel对应的Method,前者会直接通过新的imp覆盖原来的method,后者则不会做任何处理。

最后method_exchangeImplementations交换两个method的实现。

现在分析一下文章开头那段代码,当当前类本身没有实现original_selector方法的时候,但是它的基类实现了。那么最后交换的就是基类中的original_selector方法,这将会影响基类和其他继承子类的行为。现在通过一个简单的demo来验证:

1
@interface People : NSObject
 
- (void)talk;
 
@end
@implementation People
 
- (void)talk
 
{
 
    NSLog(@"%@", self.class);
 
}
 
 
  
  
@interface Student : People
  
@end
  
@implemention Student
  
@end
  
  
  
@interface Teacher : People
 
@end
 
 
@implemention Teacher
 
@end
@interface Student (Tracking)
 
@end
 
 
@implemention Student
 
+ (void)load
 
{
 
    static dispatch_once_t onceToken;
 
    dispatch_once(&onceToken, ^{
 
        SEL originalSelector = NSSelectorFromString(@"talk");
 
        SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk");
 
        Method originalMethod = class_getInstanceMethod(self.class, 
            originalSelector);
 
        Method swizzleMethod = class_getInstanceMethod(self.class, 
            swizzleSelector);
         
        method_exchangeImplementations(originalMethod, swizzleMethod);
    });
}
 
 
 
- (void)swizzle_talk
 
{
 
    NSLog(@"zwizzle_talk: %@", self.class);
 
}
 
@end
  
- (void)viewDidLoad {
 
    [super viewDidLoad];
 
 
    Teacher *t = [[Teacher alloc] init];
 
    [t talk];
 
    Student *stu = [[Student alloc] init];
 
    [stu talk];
 
}
 
@end

输出是:

1
20:15:35.432 abc[87901:2148310] zwizzle_talk: Teacher
 
20:15:35.433 abc[87901:2148310] zwizzle_talk: Student

说明 Teacher类也收到了student swizzle的影响。

Student(Tracking)换一种写法:

1
+ (void)load
{
    static dispatch_once_t onceToken;
 
    dispatch_once(&onceToken, ^{
 
        SEL originalSelector = NSSelectorFromString(@"talk");
 
        SEL swizzleSelector = NSSelectorFromString(@"swizzle_talk");
 
        Method originalMethod = class_getInstanceMethod(self.class, 
            originalSelector);
 
        Method swizzleMethod = class_getInstanceMethod(self.class, 
            swizzleSelector);
 
        BOOL addMethod = class_addMethod(self.class, originalSelector, 
            method_getImplementation(swizzleMethod), method_getTypeEncoding(
                swizzleMethod));
 
        if (addMethod) {
            class_replaceMethod(self.class, swizzleSelector, 
                method_getImplementation(originalMethod), 
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzleMethod);
        }
    });
}

输出是:

1
20:19:50.683 abc[87966:2152486] Teacher
 
20:19:50.684 abc[87966:2152486] zwizzle_talk: Student

可以看到,Teacher类并没有收到影响,虽然是基类中实现了talk方法,但是通过class_addMethod给当前类Student动态增加了talk的实现,然后进行交换。没有影响到原来People类中的talk方法。

可以看出,第二种方法实现起来更好,影响范围更小一些。

Xcode Debug Toolset

Xcode Debug Toolset

print

  • 简写成p, pri, ptin
  • po(print object)可以打印对象的description方法的结果
  • 打印不同格式可以用p/x number打印十六进制,p/t number打印二进制,p/c char打印字符。这里是完整清单https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html
  • po [0x1234 _ivarDescription]
  • po [0x1234 _shortMethodDescription]
  • po [0x1234 _methodDescription]
  • po [[UIWindow keyWindow] recursiveDescription]打印window所有的视图层级
  • po [[[UIWindow keyWindow] rootViewController] _printHierarchy]打印controller的视图层级
调试寄存器中的变量:
  • register read 打印寄存器中的值
platform recriver SEL Arg1 Arg2 return value
x86_64 rdi或者$rbp+16 rsi或者$rbp+24 rdx或者$rbp+32 rcx或者$rbp+40 rax
arm64 x0 x1 x2 x3
arm r0 r1 r2 r3

erpression

  • 简写成 e,执行代码
  • e [((UIView *)0x06ae2) setBackgroundColor:[UIColor redColor]]更改一个view的背景颜色
  • e class_getInstanceMethod([MyViewController class], @selector(layoutSubviews))查看Method
  • e BOOL $a = YES创建一个变量,变量名要以$作前缀

Breakpoint

命令行添加断点

  • br set -a 0x01234
  • br set -r "UIView"

通用断点

  • All Exceptions 异常断点
  • UIViewAlertForUnsatisfiableConstraints :
    在遇到Autolayout约束冲突时会触发该断点

  • UIApplicationMain:

    Debugger Command:e @import UIKit

    这样在控制台中可以直接打印view的frame:
    p [view bounds]

符号断点

将断点添加到User Breakpoints中,可以跨工程共享

s

watchpoint

监视内存地址发生读写

  • watchpoint s e read 0x7f8c519b4600

  • watchpoint s e read_write 0x7f8c519b4600

监视vMain变量什么时候被重写了,监视这个地址什么时候被写入

1
2
3
4
5
(lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyView class], "vMain"))
(ptrdiff_t) $0 = 8
(lldb) watchpoint set expression -- (int *)$myView + 8
Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = w
new value: 0x0000000000000000

image lookup

  • image lookup -s UIView 打印UIView所在的映像文件
  • image lookup -n setCenterPlaceholder打印某个方法所在的映像文件
  • 更多用法参考 help image lookup

xcode 环境变量

  • OBJC_PRINT_REPLACED_METHODS YES 打印重名方法
  • DYLD_PRINT_STATISTICS 1 打印App启动时DYLD加载时长

参考

RN中的Style应用

RN中的Style应用

基础理论篇:

设备尺寸和分辨率

Dimensions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* iOS 和 android 的window
* scale是逻辑坐标转换成物理坐标时使用的变幻系数(device pixel density)
* fontScale (android only)
* 【注】若写到style中, 需要在render时更新一次
*/

const {height, width, scale, fontScale} = Dimensions.get('window');
//android有 Screen 和 window的区别; iOS无
const {height, width, scale, fontScale} = Dimensions.get('screen');

//按比例使用 size (@mtfe/react-native-css)
const ratio = Dimensions.get('window').width / 320;
function r (size) {
return Math.floor(size * ratio);
}

PixelRatio

1
2
3
4
5
6
7
//同上
const {scale, fontScale} = {scale: PixelRatio.get(), fontScale: PixelRatio.getFontScale()};
//把 point 转成 pixel,即: point * scale
const pixelSize = PixelRatio.getPixelSizeForLayoutSize(pointSize);

//一个pixel 对应的 point大小
const px = 1/PixelRatio.get();

FlexBox

背景介绍:跟传统的Box模型对比, Flexbox Layout目标是一个container实现更有效的布局、对齐、自动填充, 即使在子元素大小或个数没有指定的情况下。 RN的flex布局是Web的子集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//====================== 修饰子元素在当前元素中的布局 ==========================
/**
* 子元素的排列方向 row: 从左到右横向排 column:从上到下竖向排
* 当前元素有flexDirect后, 才能使用flex layout
*/

flexDirection enum('row', 'column')
/**
* 子元素无法在单行(one line)中展示完全, 是否多行展示
*/

flexWrap enum('wrap', 'nowrap')
/**
* 子元素在当前元素的横轴(main axis)对齐方式
* flex-start: 依次排列, 左对齐startline
* flex-end: 依次排列, 右对齐endline
* center: 依次排泄, 居中
* space-between左右子元素分别对齐startline和endline, 元素间的space相等, 元素占满父元素空间
* space-around ***
*/

justifyContent enum('flex-start', 'flex-end', 'center', 'space-between', 'space-around')
/**
* 子元素在当前元素的纵轴(cross axis)对齐方式
* flex-start: 子元素的上side对齐当前元素纵轴的startline
* flex-end: 子元素的下side对齐当前元素纵轴的endline
* center: 子元素的中心跟当前元素纵轴的中心对齐
* strech 拉伸子元素, 上side对齐startline, 下side对齐endline, 即高度跟当前元素纵轴相同
*/

alignItems enum('flex-start', 'flex-end', 'center', 'stretch')

//====================== 修饰当前元素在父元素中的布局 ==========================
/**
* 当前元素在父元素中的flex空间占比, 即 =当前flex值/同级所有子元素的flex值的和
* 建议默认父元素flex空间为1, 子元素根据1算出自己的flex数值
*/

flex number
/**
* 当前元素可以自选在父元素的纵轴(cross axis)对齐方式
* flex-start: 当前元素的上side对齐父元素纵轴的startline
* flex-end: 当前元素的下side对齐父元素纵轴的endline
* center: 当前元素的中心跟父元素纵轴的中心对齐
* strech 拉伸当前元素, 上side对齐startline, 下side对齐endline, 即高度跟父元素纵轴相同
*/

alignSelf enum('auto', 'flex-start', 'flex-end', 'center', 'strech')

============================== 图示如下 ==========================================

flexDirection

Alt text

flexWrap

Alt text

justifyContent

Alt text

alignItems

Alt text

alignSelf

Alt text

Box Model

Alt text

1
2
3
4
5
6
7
top、right、bottom、left
marginTop、marginRight、marginBottom、marginLeft
marginHorizontal、marginVertical
paddingTop、paddingRight、paddingBottom、paddingLeft
paddingHorizontal、paddingVertical
borderTopWidth、borderRightWidth、borderBottomWidth、borderLeftWidth
width、height
1
2
3
4
5
6
/**
* 当前元素在父元素中的定位样式, 使用 top、right、bottom、left + width、height 实现准确布局
* absolute: 从父元素的排列中脱离, 不占据父元素的空间, 其他子元素flex时可以认为当前元素不存在
* relative: 占据父元素空间
*/

position enum{'absolute', 'relative'}

图片使用篇

SVG | AI | PNG | JPEG

SVG: (Scalable Vector Graphics) 基于XML, 描述二维矢量图形的图形格式。无限放大而不影响质量

AI: (Adobe illustrator)矢量图, 使用Adobe illustrator图形制作软件做的矢量图

PNG: (Portable Network Graphics) 一种无损压缩的位图(Bitmap)图形格式, 文件大

JPEG: (Joint Photographic Experts Group)一种针对照片视频而广泛使用的一种有损压缩标准方法

目前项目中使用的是PNG, 每个icon需要多张不同分辨率的PNG图。可以考虑转向SVG阵营

另外根据PM的设计, 我们的图标转向使用Google提供的一套图标库, 相比于手动下载这些图标, 推荐使用

下面介绍的icon工具, 不仅自带了Google图标, 易用性也大大提高

好用的icon工具

  1. react-native-icons
  2. react-native-vector-icons

JavaScriptBridge

跟Native开发相比, 网页开发虽然在用户体验上跟Native有不小的差距, 但其在开发周期、更新方式上却有不可比拟的优势。有时为了快速上线新的业务或动态支持第三方的业务,App中不可避免的会使用webview。一般情况下, webview或多或少的都会用到cookie、定位、支付、分享等信息或功能,而这些功能却是Native; 另一方面,统一UI交互:选择列表、信息提示等对提升用户体验也非常有帮助,直接使用Native显然又要比使用web来模拟Native的UI要简单的多。综合来看,webview调用Native模块完成部分交互是必不可少的,那么,如何做到呢?

Native vs Web

App开发的同学们都知道从产品体验的角度出发,webview绝不是App的第一选择,跟Native相比webview在产品体验上有下面几个方面的不足:

  1. 页面渲染慢。网页的资源加载、UI位置计算、渲染、逻辑运算等都是单线程,无法比拟Native多线程
  2. 流畅性不够好。eg. 上下滑动页面时, Native的滚动流畅性要远高于WebView,可以拿两个列表页试试
  3. 手势的支持比较少。webview支持的手势要比Native少,比如多点触控、ShakeGesture等
  4. 很难做到UI的一致。用户首先对系统原生的UI控件比较熟悉,App的开发大都基于这些基本的控件。使用webview时,由于不能直接使用很多Native原生控件,而用JS实现的控件跟原生又有差异,所以UI的一致性是大有折扣的。即使Web控件的样式跟原生差别很小,iOS 每年的系统更新带来的UI跳转就会破坏这些花大力气实现的UI

但从业务的角度出发,有时我们不得不选择使用webview,这主要基于以下几个方面的考虑:

  1. webview开发周期短,可以随时上线更新
  2. 出于发版的考量,一些业务功能Native开发的周期过长,需要webview接入快速上线
  3. 有些服务来自第三方,App开发者本身不需要支持这些服务, 使用webview展示第三方服务

CFRunLoop

数据结构

__CFRunLoopMode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking
this */

//mode名
CFStringRef _name;
Boolean _stopped;
char _padding[3];
//source0 源
CFMutableSetRef _sources0;
//source1 源
CFMutableSetRef _sources1;
//observer 源
CFMutableArrayRef _observers;
//timer 源
CFMutableArrayRef _timers;

//mach port 到 mode的映射,为了在runloop主逻辑中过滤runloop自己的port消息。
CFMutableDictionaryRef _portToV1SourceMap;

//记录了所有当前mode中需要监听的port,作为调用监听消息函数的参数。
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
//使用 mk timer, 用到的mach port,和source1类似,都依赖于mach port
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
//timer触发的理想时间
uint64_t _timerSoftDeadline; /* TSR */
//timer触发的实际时间,理想时间加上tolerance(偏差)
uint64_t _timerHardDeadline; /* TSR */
};

RunLoop源码分析

数据结构

__CFRunLoopMode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
//mode名
CFStringRef _name;
Boolean _stopped;
char _padding[3];
//source0 源
CFMutableSetRef _sources0;
//source1 源
CFMutableSetRef _sources1;
//observer 源
CFMutableArrayRef _observers;
//timer 源
CFMutableArrayRef _timers;

//mach port 到 mode的映射,为了在runloop主逻辑中过滤runloop自己的port消息。
CFMutableDictionaryRef _portToV1SourceMap;

//记录了所有当前mode中需要监听的port,作为调用监听消息函数的参数。
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
//使用 mk timer, 用到的mach port,和source1类似,都依赖于mach port
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
//timer触发的理想时间
uint64_t _timerSoftDeadline; /* TSR */
//timer触发的实际时间,理想时间加上tolerance(偏差)
uint64_t _timerHardDeadline; /* TSR */
};