iOS开发业界毒瘤 Hook
原文地址
为什么有这篇博文
不知道何时开始iOS面试开始流行起来询问什么是 Runtime,于是 iOSer 一听 Runtime 总是就提起 MethodSwizzling,开口闭口就是黑科技。但其实如果读者留意过C语言的 Hook 原理其实会发现所谓的钩子都是框架或者语言的设计者预留给我们的工具,而不是什么黑科技,MethodSwizzling 其实只是一个简单而有趣的机制罢了。然而就是这样的机制,在日常中却总能成为万能药一般的被肆无忌惮的使用。
很多 iOS 项目初期架构设计的不够健壮,后期可扩展性差。于是 iOSer 想起了 MethodSwizzling 这个武器,将项目中一个正常的方法 hook 的满天飞,导致项目的质量变得难以控制。曾经我也爱在项目中滥用 MethodSwizzling,但在踩到坑之前总是不能意识到这种糟糕的做法会让项目陷入怎样的险境。于是我才明白学习某个机制要去深入的理解机制的设计,而不是跟风滥用,带来糟糕的后果。最后就有了这篇文章。
Hook的对象
在 iOS 平台常见的 hook 的对象一般有两种:
C/C++ functions
Objective-C method
对于 C/C+ +的 hook 常见的方式可以使用 facebook 的 fishhook 框架,具体原理可以参考深入理解Mac OS X & iOS 操作系统 这本书。
对于 Objective-C Methods 可能大家更熟悉一点,本文也只讨论这个。
最常见的hook代码
相信很多人使用过 JRSwizzle 这个库,或者是看过 http://nshipster.cn/method-swizzling/ 的博文。
上述的代码简化如下。
+ (BOOL)jr_swizzleMethod:(SEL)origSel_ withMethod:(SEL)altSel_ error:(NSError**)error_ {
Method origMethod = class_getInstanceMethod(self, origSel_);
if (!origMethod) {
SetNSError(error_, @"original method %@ not found for class %@", NSStringFromSelector(origSel_), [self class]);
return NO;
}
Method altMethod = class_getInstanceMethod(self, altSel_);
if (!altMethod) {
SetNSError(error_, @"alternate method %@ not found for class %@", NSStringFromSelector(altSel_), [self class]);
return NO;
}
class_addMethod(self,
origSel_,
class_getMethodImplementation(self, origSel_),
method_getTypeEncoding(origMethod));
class_addMethod(self,
altSel_,
class_getMethodImplementation(self, altSel_),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(class_getInstanceMethod(self, origSel_), class_getInstanceMethod(self, altSel_));
return YES;
在Swizzling情况极为普通的情况下上述代码不会出现问题,但是场景复杂之后上面的代码会有很多安全隐患。
MethodSwizzling泛滥下的隐患
Github有一个很健壮的库 RSSwizzle(这也是本文推荐Swizzling的最终方式) 指出了上面代码带来的风险点。
只在 +load 中执行 swizzling 才是安全的。
被 hook 的方法必须是当前类自身的方法,如果把继承来的 IMP copy 到自身上面会存在问题。父类的方法应该在调用的时候使用,而不是 swizzling 的时候 copy 到子类。
被 Swizzled 的方法如果依赖与 cmd ,hook 之后 cmd 发送了变化,就会有问题(一般你 hook 的是系统类,也不知道系统用没用 cmd 这个参数)。
命名如果冲突导致之前 hook 的失效 或者是循环调用。
上述问题中第一条和第四条说的是通常的 MethodSwizzling 是在分类里面实现的, 而分类的 Method 是被Runtime 加载的时候追加到类的 MethodList ,如果不是在 +load 是执行的 Swizzling 一旦出现重名,那么 SEL 和 IMP 不匹配致 hook 的结果是循环调用。
第三条是一个不容易被发现的问题。
我们都知道 Objective-C Method 都会有两个隐含的参数 self, cmd,有的时候开发者在使用关联属性的适合可能懒得声明 (void *) 的 key,直接使用 cmd 变量 objc_setAssociatedObject(self, _cmd, xx, 0); 这会导致对当前IMP对 cmd 的依赖。
一旦此方法被 Swizzling,那么方法的 cmd 势必会发生变化,出现了 bug 之后想必你一定找不到,等你找到之后心里一定会问候那位 Swizzling 你的方法的开发者祖宗十八代安好的,再者如果你 Swizzling 的是系统的方法恰好系统的方法内部用到了 cmd ..._(此处后背惊起一阵冷汗)。
Copy父类的方法带来的问题
上面的第二条才是我们最容易遇见的场景,并且是99%的开发者都不会注意到的问题。下面我们来做个试验
@implementation Person
- (void)sayHello {
NSLog(@"person say hello");
}
@end
@interface Student : Person
@end
@implementation Student (swizzle)
+ (void)load {
[self jr_swizzleMethod:@selector(s_sayHello) withMethod:@selector(sayHello) error:nil];
}
- (void)s_sayHello {
[self s_sayHello];
NSLog(@"Student + swizzle say hello");
}
@end
@implementation Person (swizzle)
+ (void)load {
[self jr_swizzleMethod:@selector(p_sayHello) withMethod:@selector(sayHello) error:nil];
}
- (void)p_sayHello {
[self p_sayHello];
NSLog(@"Person + swizzle say hello");
}
@end
上面的代码中有一个 Person 类实现了 sayHello 方法,有一个 Student 继承自 Person, 有一个Student 分类 Swizzling 了原来的 sayHello, 还有一个 Person 的分类也 Swizzling 了原来的 sayhello方法。
当我们生成一个 Student 类的实例并且调用 sayHello 方法,我们期望的输出如下:
"person say hello"
"Person + swizzle say hello"
"Student + swizzle say hello"
但是输出有可能是这样的:
"person say hello"
"Student + swizzle say hello"
出现这样的场景是由于在 build Phases 的 compile Source 顺序子类分类在父类分类之前。
我们都知道在 Objective-C 的世界里父类的 +load 早于子类,但是并没有限制父类的分类加载会早于子类的分类的加载,实际上这取决于编译的顺序。最终会按照编译的顺序合并进 Mach-O 的固定 section 内。
下面会分析下为什么代码会出现这样的场景。
最开始的时候父类拥有自己的 sayHello 方法,子类拥有分类添加的 s_sayHello 方法并且在s_sayHello 方法内部调用了 sel 为 s_sayHello 方法。
但是子类的分类在使用上面提到的 MethodSwizzling 的方法会导致如下图的变化
由于调用了 class_addMethod 方法会导致重新生成一份新的Method添加到 Student 类上面 但是 sel 并没有发生变化,IMP 还是指向父类唯一的那个 IMP。
之后交换了子类两个方法的 IMP 指针。于是方法引用变成了如下结构。
其中虚线指出的是方法的调用路径。
单纯在 Swizzling 一次的时候并没有什么问题,但是我们并不能保证同事出于某种不可告人的目的的又去 Swizzling 了父类,或者是我们引入的第三库做了这样的操作。
于是我们在 Person 的分类里面 Swizzling 的时候会导致方法结构发生如下变化。
我们的代码调用路径就会是下图这样,相信你已经明白了前面的代码执行结果中为什么父类在子类之后 Swizzling 其实并没有对子类 hook 到。
这只是其中一种很常见的场景,造成的影响也只是 Hook 不到父类的派生类而已,也不会造成一些严重的 Crash 等明显现象,所以大部分开发者对此种行为是毫不知情的。
对于这种 Swizzling 方式的不确定性有一篇博文分析的更为全面玉令天下的博客Objective-C Method Swizzling
换个姿势来Swizzling
前面提到 RSSwizzle 是另外一种更加健壮的Swizzling方式。
这里使用到了如下代码
RSSwizzleInstanceMethod([Student class],
@selector(sayHello),
RSSWReturnType(void),
RSSWArguments(),
RSSWReplacement(
{
// Calling original implementation.
RSSWCallOriginal();
// Returning modified return value.
NSLog(@"Student + swizzle say hello sencod time");
}), 0, NULL);
RSSwizzleInstanceMethod([Person class],
@selector(sayHello),
RSSWReturnType(void),
RSSWArguments(),
RSSWReplacement(
{
// Calling original implementation.
RSSWCallOriginal();
// Returning modified return value.
NSLog(@"Person + swizzle say hello");
}), 0, NULL);
由于 RS 的方式需要提供一种 Swizzling 任何类型的签名的 SEL,所以 RS 使用的是宏作为代码包装的入口,并且由开发者自行保证方法的参数个数和参数类型的正确性,所以使用起来也较为晦涩。 可能这也是他为什么这么优秀但是 star 很少的原因吧 :(。
我们将宏展开
RSSwizzleImpFactoryBlock newImp = ^id(RSSwizzleInfo *swizzleInfo) {
void (*originalImplementation_)(__attribute__((objc_ownership(none))) id, SEL);
SEL selector_ = @selector(sayHello);
return ^void (__attribute__((objc_ownership(none))) id self) {
IMP xx = method_getImplementation(class_getInstanceMethod([Student class], selector_));
IMP xx1 = method_getImplementation(class_getInstanceMethod(class_getSuperclass([Student class]) , selector_));
IMP oriiMP = (IMP)[swizzleInfo getOriginalImplementation];
((__typeof(originalImplementation_))[swizzleInfo getOriginalImplementation])(self, selector_);
//只有这一行是我们的核心逻辑
NSLog(@"Student + swizzle say hello");
};
};
[RSSwizzle swizzleInstanceMethod:@selector(sayHello)
inClass:[[Student class] class]
newImpFactory:newImp
mode:0 key:((void*)0)];;
RSSwizzle核心代码其实只有一个函数
static void swizzle(Class classToSwizzle,
SEL selector,
RSSwizzleImpFactoryBlock factoryBlock)
{
Method method = class_getInstanceMethod(classToSwizzle, selector);
__block IMP originalIMP = NULL;
RSSWizzleImpProvider originalImpProvider = ^IMP{
IMP imp = originalIMP;
if (NULL == imp){
Class superclass = class_getSuperclass(classToSwizzle);
imp = method_getImplementation(class_getInstanceMethod(superclass,selector));
}
return imp;
};
RSSwizzleInfo *swizzleInfo = [RSSwizzleInfo new];
swizzleInfo.selector = selector;
swizzleInfo.impProviderBlock = originalImpProvider;
id newIMPBlock = factoryBlock(swizzleInfo);
const char *methodType = method_getTypeEncoding(method);
IMP newIMP = imp_implementationWithBlock(newIMPBlock);
originalIMP = class_replaceMethod(classToSwizzle, selector, newIMP, methodType);
}
上述代码已经删除无关的加锁,防御逻辑,简化理解。
我们可以看到 RS 的代码其实是构造了一个 Block 里面装着我们需要的执行的代码。
然后再把我们的名字叫 originalImpProviderBloc 当做参数传递到我们的block里面,这里面包含了对将要被 Swizzling 的原始 IMP 的调用。
需要注意的是使用 class_replaceMethod 的时候如果一个方法来自父类,那么就给子类 add 一个方法, 并且把这个 NewIMP 设置给他,然后返回的结果是NULL。
在 originalImpProviderBloc 里面我们注意到如果 imp 是 NULL的时候,是动态的拿到父类的 Method 然后去执行。
我们还用图来分析代码。
最开始 Swizzling 第一次的时候,由于子类不存在 sayHello 方法,再添加方法的时候由于返回的原始 IMP 是 NULL,所以对父类的调用是动态获取的,而不是通过之前的 sel 指针去调用。
如果我们再次对 Student Hook,由于 Student 已经有 sayHello 方法,这次 replace 会返回原来 IMP 的指针, 然后新的 IMP 会执被填充到 Method 的指针指向。
由此可见我们的方法引用是一个链表形状的。
同理我们在 hook 父类的时候 父类的方法引用也是一个链表样式的。
相信到了这里你已经理解 RS 来 Swizzling 方式是:
如果是父类的方法那么就动态查找,如果是自身的方法就构造方法引用链。来保证多次 Swizzling 的稳定性,并且不会和别人的 Swizzling 冲突。
而且 RS 的实现由于不是分类的方法也不用约束开发者必须在 +load 方法调用才能保证安全,并且cmd 也不会发生变化。
其他Hook方式
其实著名的 Hook 库还有一个叫 Aspect 他利用的方法是把所有的方法调用指向 _objc_msgForward然后自行实现消息转发的步骤,在里面自行处理参数列表和返回值,通过 NSInvocation 去动态调用。
国内知名的热修复库 JSPatch 就是借鉴这种方式来实现热修复的。
但是上面的库要求必须是最后执行的确保 Hook 的成功。 而且他不兼容其他 Hook 方式,所以技术选型的时候要深思熟虑。
什么时候需要Swizzling
我记得第一次学习 AO P概念的时候是当初在学习 javaWeb 的时候 Serverlet 里面的 FilterChain,开发者可以实现各种各种的过滤器然后在过滤器中插入log, 统计, 缓存等无关主业务逻辑的功能行性代码, 著名的框架 Struts2 就是这样实现的。
iOS 中由于 Swizzling 的 API 的简单易用性导致开发者肆意滥用,影响了项目的稳定性。
当我们想要 Swizzling 的时候应该思考下我们能不能利用良好的代码和架构设计来实现,或者是深入语言的特性来实现。
一个利用语言特性的例子
我们都知道在iOS8下的操作系统中通知中心会持有一个 __unsafe_unretained 的观察者指针。如果观察者在 dealloc 的时候忘记从通知中心中移除,之后如果触发相关的通知就会造成 Crash。
我在设计防 Crash 工具 XXShield 的时候最初是 Hook NSObjec 的 dealloc 方法,在里面做相应的移除观察者操作。后来一位真大佬提出这是一个非常不明智的操作,因为 dealloc 会影响全局的实例的释放,开发者并不能保证代码质量非常有保障,一旦出现问题将会引起整个 APP 运行期间大面积崩溃或异常行为。
下面我们先来看下 ObjCRuntime 源码关于一个对象释放时要做的事情,代码约在objc-runtime-new.mm第6240行。
/***********************************************************************
* objc_destructInstance
* Destroys an instance without freeing memory.
* Calls C++ destructors.
* Calls ARC ivar cleanup.
* Removes associative references.
* Returns `obj`. Does nothing if `obj` is nil.
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
/***********************************************************************
* object_dispose
* fixme
* Locking: none
**********************************************************************/
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
上面的逻辑中明确了写明了一个对象在释放的时候初了调用 dealloc 方法,还需要断开实例上绑定的观察对象, 那么我们可以在添加观察者的时候给观察者动态的绑定一个关联对象,然后关联对象可以反向持有观察者,然后在关联对象释放的时候去移除观察者,由于不能造成循环引用所以只能选择__weak 或者 __unsafe_unretained 的指针, 实验得知 __weak 的指针在 dealloc 之前就已经被清空, 所以我们只能使用 __unsafe_unretained 指针。
@interface XXObserverRemover : NSObject {
__strong NSMutableArray *_centers;
__unsafe_unretained id _obs;
}
@end
@implementation XXObserverRemover
- (instancetype)initWithObserver:(id)obs {
if (self = [super init]) {
_obs = obs;
_centers = @[].mutableCopy;
}
return self;
}
- (void)addCenter:(NSNotificationCenter*)center {
if (center) {
[_centers addObject:center];
}
}
- (void)dealloc {
@autoreleasepool {
for (NSNotificationCenter *center in _centers) {
[center removeObserver:_obs];
}
}
}
@end
void addCenterForObserver(NSNotificationCenter *center ,id obs) {
XXObserverRemover *remover = nil;
static char removerKey;
@autoreleasepool {
remover = objc_getAssociatedObject(obs, &removerKey);
if (!remover) {
remover = [[XXObserverRemover alloc] initWithObserver:obs];
objc_setAssociatedObject(obs, &removerKey, remover, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
[remover addCenter:center];
}
}
void autoHook() {
RSSwizzleInstanceMethod([NSNotificationCenter class], @selector(addObserver:selector:name:object:),
RSSWReturnType(void), RSSWArguments(id obs,SEL cmd,NSString *name,id obj),
RSSWReplacement({
RSSWCallOriginal(obs,cmd,name,obj);
addCenterForObserver(self, obs);
}), 0, NULL);
}
需要注意的是在添加关联者的时候一定要将代码包含在一个自定义的 AutoreleasePool 内。
我们都知道在 Objective-C 的世界里一个对象如果是 Autorelease 的 那么这个对象在当前方法栈结束后才会延时释放,在 ARC 环境下,一般一个 Autorelease 的对象会被放在一个系统提供的 AutoreleasePool 里面,然后AutoReleasePool drain 的时候再去释放内部持有的对象,通常情况下命令行程序是没有问题的,但是在iOS的环境中 AutoReleasePool是在 Runloop 控制下在空闲时间进行释放的,这样可以提升用户体验,避免造成卡顿,但是在我们这种场景中会有问题,我们严格依赖了观察者调用 dealloc 的时候关联对象也会去 dealloc,如果系统的 AutoReleasePool 出现了延时释放,会导致当前对象被回收之后 过段时间关联对象才会释放,这时候前文使用的 __unsafe_unretained 访问的就是非法地址。
我们在添加关联对象的时候添加一个自定义的 AutoreleasePool 保证了对关联对象引用的单一性,保证了我们依赖的释放顺序是正确的。从而正确的移除观察者。
参考
JRSwizzle
RSSwizzle
Aspect
玉令天下的博客Objective-C Method Swizzling
示例代码
相关文章:

常用rsync命令操作梳理
作为一个运维工程师,经常可能会面对几十台、几百台甚至上千台服务器,除了批量操作外,环境同步、数据同步也是必不可少的技能。说到“同步”,不得不提的利器就是rsync。rsync不但可以在本机进行文件同步,也可以作为远程…

Word英文字符间距太大 中英文输入切换都不行
在Word中输入文字时会遇到这样的情况,就是说中文字符的间距是正常的,但是英文字符间的间距却不正常,总是太宽了,如图: 。 而且这时切换中英文输入都没用,Word的字体设置也正常。后来上网查了下,…

Hadoop集群搭建(六:HBase的安装配置)
实验 目的 要求 目的: 1、HBase的高可用完全分布模式的安装和验证 要求: 完成HBase的高可用完全分布模式的安装;HBase的相关服务进程能够正常的启动;HBase控制台能够正常使用;表创建、数据查询等数据库操作能够正常…

架构师的第一阶段:准备做(Pre-Architecture)
上节说到,做任何事情都可以分为三个阶段:准备做、做、做好。本文,就将进入第一个阶段,准备做阶段。 Pre-Architecture:准备架构 准备架构阶段,最最重要的是弄清楚要做什么东西,即掌握用户需求。…
iOS动画系列之八:使用CAShapeLayer绘画动态流量图
这篇文章通过使用CAShapeLayer和UIBezierPath来画出一个动态显示剩余流量的小动画。 最终实现的效果如下: Paste_Image.png 动态效果图: shapeLayerAni.gif 1. CAShapeLayer 实际中,能够用CALayer完成的任务是比较少的,如果使用这…

hiho_1139_二分+bfs搜索
题目 给定N个点和M条边,从点1出发,到达点T。寻找路径上边的个数小于等于K的路径,求出所有满足条件的路径中最长边长度的最小值。 题目链接:二分 最小化最大值,考虑采用二分搜索。对所有的边长进行排序,二分&#x…

Hadoop集群搭建(七:MySQL的安装配置)
实验 目的 要求 目的: 1、掌握MySQL在集群平台中的安装 要求: 完成MySQL的集群版的安装;MySQL集群的相关服务进程能够正常启动;MySQL集群的SQL服务能够作为系统服务开机自动启动;MySQL客户端能够远程连接MySQL集群的…
如何在VMware虚拟机上安装Linux操作系统(Ubuntu)
作为初学者想变为计算机大牛非一朝一夕,但掌握基本的计算机操作和常识却也不是多么难的事情。所以作为一名工科男,为了把握住接近女神的机会,也为了避免当白痴,学会装系统吧!of course为避免把自己的电脑作为牺牲品&am…

cf #363 b
B. One Bombtime limit per test1 secondmemory limit per test256 megabytesinputstandard inputoutputstandard outputYou are given a description of a depot. It is a rectangular checkered field of n m size. Each cell in a field can be empty (".") or…
swift-video-generator:图片加音频生成视频及多视频合并库及演示
阅读 80收藏 92017-11-07原文链接:github.com腾讯云学生优惠套餐,985高校学习云计算的主力机型,2G2核,1M带宽系统盘(Linux 50G/Windows 50G)免费赠送50GB对象存储空间还有.cn域名一年使用权!不要…

Hadoop集群搭建(八:Hive的安装配置)
实验 目的 要求 目的: (1)掌握数据仓库工具Hive的安装和配置; 要求: 完成Hive工具的安装和配置;Hive工具能够正常启动运行;Hive控制台命令能够正常使用;能够正常操作数据库、表、…
iOS 富文本编辑工厂, 让书写更简便.
由于最近常用富文本, 在编辑一个富文本时需要操作很多的属性, 书写起来很不方便. 所以我将这些相关属性整理并使用链式方式将它简化了一下. 效果请看下面Demo. 项目工程 实现很简单, 我嘴太笨, 这里就不介绍了, 如有兴趣直接看源码吧. 同时可以通过cocoapods来使用它. pod SJAt…

ORACLE 数据的逻辑组成
数据块(block)Oracle数据块(Data Block)是一组连续的操作系统块。分配数据库块大小是在Oracle数据库创建时设置的,数据块是Oracle读写的基本单位。数据块的大小一般是操作系统块大小的整数倍,这样可以避免不…

Java 的zip压缩和解压缩
Java 的zip压缩和解压缩好久没有来这写东西了,今天中秋节,有个东西想拿出来分享,一来是工作中遇到的问题,一来是和csdn问候一下,下面就分享一个Java中的zip压缩技术,代码实现比较简单,代码如下:…

Hadoop集群搭建(九:各服务的启动)
1、查看Zookeeper服务状态,若集群中只有一个"leader"节点, 其余的均为"follower"节点,则集群的工作状态正常 $zkServer.sh status 2、在集群中所有主机上使用此命令,启动Zookeeper服务 $zkServer.sh start…

iOS 后台下载及管理库
说起下载第一个想起的就是ASI。一年前接手的新项目是核心功能是视频相关业务,在修改和解决视频下载相关的问题的时候让我体会到了ASI的下载的强大。后来新需求需要视频后台下载,使用NSURLSession的时候,更加深刻的体会到了ASI的强大好用。后来…

(转) 使用Speech SDK 5.1文字转音频
下载地址: http://www.microsoft.com/en-us/download/details.aspx?id10121 SeppchSDK51.exe 语音合成引擎 SpeechSDK51LangPack.exe 支持日语和简体中文需要这个支持。 SpeechSDK51MSM.exe 如果要将引擎作为产品的一部分发布需要这个。 Sp5TTintXP.exe XP下Mike和…

IE8下面的line-height的bug
当line-height小于正常值时,超出的部分将被剪裁掉转载于:https://www.cnblogs.com/jsingleegg/p/js_ie8.html

Hadoop集群的基本操作(一:HDFS操作及MapReduce程序练习)
实验 目的 要求 目的: 理解HDFS在Hadoop体系结构中的角色;熟练使用HDFS操作常用的Shell命令;了解Hadoop集群MapReduce程序的简单使用;(上传WordCount的jar执行程序;使用WordCount进行MapReduce计算&#x…
iOS实现动态区域裁剪图片
阅读 249收藏 322017-11-29原文链接:github.com想自己动手搭建一个 Discuz 论坛?试试腾讯云上实验室吧https://cloud.tencent.com/developer/labs 裁剪图片功能在很多上传图片的场景里都需要用到,一方面应用服务器可能对图片的尺寸大小有限制…

每天CookBook之JavaScript-062
鼠标进入事件鼠标离开事件<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>062</title> </head> <body> <div><img src"001" alt"001"><img src…

spring + Quartz定时任务配置
<bean id"exportBatchFileTask" class"com.ydcn.pts.task.ExportBatchFileTask"></bean><bean id"readBatchFileTask" class"com.ydcn.pts.task.ReadBatchFileResultTask"></bean><!-- 生成开卡档…

Hadoop集群的基本操作(二:HBase的基本操作)
实验 目的 要求 目的: 1、HBase的基本应用 要求: 完成HBase的高可用完全分布模式的安装;HBase的相关服务进程能够正常的启动;HBase控制台能够正常使用;表创建、数据查询等数据库操作能够正常进行; …

Abaqus用户子程序umat的学习
Abaqus用户子程序umat的学习 说明:在文件中,!后面的内容为注释内容。本文为学习心得,很多注释是自己摸索得到。如有不正确的地方,敬请指正。 ! —————————————————————————— ! 1、为何需要…

PHP:isset()-检测变量是否被设置
isset()-检测变量是否被设置 bool isset(mixed $var [, mixed $...]),检查变量是否被设置,并且不是NULL。var,要检测的变量,...其他变量,允许有多个变量。 返回值:如果var存在并且不是NULL,则返回TRUE&…

Android通过ShareSDK实现新浪微博分享
ShareSDK社会化分享的官方说明:是中国最大的APP内分享服务提供商,ShareSDK社会化分享,全面支持微信,微博,QQ空间,来往,易信,Facebook等国内外40个平台。 ShareSDK官方网站ÿ…

Hadoop集群的基本操作(三:HBase的基本操作)
实验 目的 要求 目的: MySQL数据库的基本命令;MySQL数据库中使用SQL语句;MySQL数据库中数据库,表,数据的操作;要求: 完成MySQL的集群版的安装;MySQL集群的相关服务进程能够正常启…
iOS通过Plist保存离线调试日志
最近需要测试APP在iPhone没连接USB情况下定位时间间隔的情况,固把nslog的日志信息保存成本地Plist文件,以便测试结束后查阅运行时的日志。 一、新建一个保存日志的方法,参数为每次定位成功的时间(作为key),…

关于变量名前面加m的问题
为什么很多人写代码会在变量名前面加一个小写的m? 上大学那会儿就对这个问题感到很好奇。于是网上到处搜,有人说是member的意思。于是后来一直就这么认为。 最近在读Android源码,发现很多系统变量命名时都加了m,而有的变量又没有加…

谷歌推出情境感知API
在 Google I/O 2016 大会上,我们宣布推出新的 Google Awareness API,让您的应用可以利用快照和围栏智能应对用户情境,并且仅需占用极少量的系统资源。 所有开发者均可以通过 Google Play 服务获取 Google Awareness API。 利用 7 种不同类型的…