文章有点长,写的过程很有收获,但读的过程不一定有收获,慎入

【摘要】

 
悬垂指针(dangling pointer)引起的crash问题,是我们在iOS开发过程当中经常会遇到的。其中由delegate引发的此类问题更是常见。本文由一个UIActionSheet引发的delegate悬垂指针问题开始,逐步思索和尝试解决这类问题的几种方案并进行比较。
 
【正文】
 
UIActionSheet是一个常用的iOS系统控件,用法很简单,实现UIActionDelegate协议方法,然后通过showInView:等方法弹出。我们来看一段代码:(如无特殊说明,本文中的代码均在ARC条件下书写)
 
 - (void)popUpActionSheet
{
UIActionSheet* sheet = [[UIActionSheet alloc] initWithTitle:nil
delegate:self
cancelButtonTitle:NSLocalizedString(@"str_cancel", @"")
destructiveButtonTitle:NSLocalizedString(@"str_delete", @"")
otherButtonTitles:nil];
[sheet showInView:self.view];
}
 
像这样用一个局部变量弹出actionsheet的代码喜闻乐见。
 
那么这样做是否有问题呢?
 
来看某项目中的一个bug:页面X,按住区域Y点击按钮B,再点击按钮C(关闭),松开后页面X退出,但actionsheet A弹出,点击其中的按钮,程序crash。
 
从描述中不难看出问题所在:这是个dangling pointer的问题。点击按钮B,本应弹出actionsheet A,但由于某些特殊操作(具体原因各不相同,这里是按住区域Y),这个actionsheet的弹出被延迟了,当它弹出的时候,其delegate(通常是一个UIViewController或者一个UIView,这里是页面的ViewController X)已经被销毁了,于是delegate成了一个dangling pointer,点击按钮,向delegate发送消息的时候,就出现了crash。
 
为了防止retain cycle,iOS中大部分的delegate都是不增加对象(X)的引用计数的(弱引用),因而容易出现dangling pointer的问题。对于此类问题,解决方向通常有两个:
其一,在向delegate发送消息之前,判断delegate是否仍然有效;
其二,使对象X在dealloc的时候,主动设置所有指向X的delegate为nil。
 
对于方向一,看上去很美,如果能够在发消息前判断一个指针是否是dangling pointer,那么我们就有了最后一道防线,从此再不会发生此类crash问题。但是,当dangling pointer真出现的时候,我们更应反思一下代码设计上是否出现了不合理的地方,而不是简单以这种方式捕获并丢弃。
 
相比之下,方向二“销毁时置空”这个方案显得更治本,亦是一种良好的编程习惯。推而广之,不局限于delegate,所有弱引用指针都可以如此处理。
 
这正是ARC中引入的weak指针的概念,它会在所指对象dealloc的时候自动置为nil。也就是说,只要所有delegate都是weak类型的,此类dangling pointer问题就不复存在了。本文也可以到此结束了。
 
但是,现实总是残酷的。首先,weak指针只有在iOS 5.0及以上的版本中的ARC条件下才能使用,而目前很多项目依然需要支持iOS4.3。当然,随着iOS7的发布,这种情况会有所好转。但即使所有的用户都是5.0+,问题仍然没有解决。为何?
 
我们自定义的delegate,可以全部采用weak类型。但是系统控件是什么情况呢?比如UIActionSheet,看看iOS7.0版本SDK下的UIActionSheet.h:
 
 @property(nonatomic,assign) id delegate;    // weak reference
 
即使是7.0,这些系统控件的delegate仍然不是weak,而是assign,大约是为了兼容非ARC环境的原因吧。 也就是说,weak指针并不能解决系统控件delegate的dangling pointer问题。这下肿么办?
 
花开两朵,各表一支。
 
我们先回过头来看另外一个问题:为什么actionsheet会出现这个dangling pointer的问题?
 
直接原因是作为delegate的ViewController X被销毁了,而此时actionsheet A本身还在显示。但这个A明明是show在self.view上的,为什么self.view都没了,它还会存在呢?
 
我们来看下面一段代码:
 
 -(void)viewDidAppear:(BOOL)animated
{
UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"abcde" delegate:self cancelButtonTitle:@"cancel" destructiveButtonTitle:nil otherButtonTitles:nil]; NSLog(@"application windows:%@", [UIApplication sharedApplication].windows); [sheet showInView:self.view]; NSLog(@"self.window:%@", self.view.window);
NSLog(@"sheet.window:%@", sheet.window); NSLog(@"application windows:%@", [UIApplication sharedApplication].windows);
}
主要运行结果(为iphone上的,情况在iPad上略有不同)如下:

application windows:(
UIWindow ...
) // actionsheet弹出前,只有1个UIWindow self.window: UIWindow ...
sheet.window: _UIAlertOverlayWindow ... application windows:(
UIWindow ...
UITextEffectsWindow ...
_UIAlertOverlayWindow ...
) // actionsheet弹出后,有3个UIWindow
原来iOS的application并非只有一个window,actionsheet是弹在另外一个内部的window上的(iPad上情况不同,只有一个window,actionsheet是在showInView的superview的一个叫UIDimmingView的subview上),与showInView:方法中指定的view并没有持有的关系,所以能在完全不依赖于后者生命周期的情况下存在,于是出现dangling pointer delegate一点也不奇怪了。
 
那么知道了来龙去脉以后,我们可以开始着手解决文章开始时的那个bug了。按照“在一个对象X dealloc的时候,设置所有指向X的delegate为空”这个“方向二”的中心思想,weak指针是派不上用场了,我们只能另想办法。
 
通过分析,我们知道落实这个“中心思想”的要点就是:
 
怎样在X dealloc的时候获取到所有delegate指向X的actionsheet A?
  
由于文章开始时喜闻乐见的代码中,actionsheet是局部变量弹出的,在ViewController X dealloc的时候,我们已经访问不到那个局部变量,怎么办呢?
 
思路1:

改用一个实例变量V保存actionsheet。在X的dealloc方法里置V.delegate = nil。

 
这毫无疑问是最容易想到的方法,无须赘述。只是要注意一个问题:actionsheet是可以同时(或相继)弹出多个的(我们会看到背景的黑色蒙板随着弹出actionsheet的数量而叠加,越来越深。)这样一来,我们要么改用一个数组来保存actionsheet的指针(们),要么就要在每弹出一个新的时候,就把旧的处理掉(或者delegate置空,或者干脆dismiss掉)。
 
这种思路,优点有二:
          一、思路简单,代码添加量少;
          二、如果你是在写一个iPad app,那反正应付转屏重新布局actionsheet也是需要这个实例变量的,一举多得。

其缺点也有二:
          一、这种方式通用性差,我们需要针对每一个这样的X都写一遍这样的代码,如果这是一个已经存在的项目,而这个项目里几乎所有的actionsheet都是这样用局部变量弹出的,怎么办?我们需要修改多少代码?
          二、actionsheet作为一个系统控件,ViewController多数情况下只是控制弹出和实现delegate方法,并不做其他任何操作,这也就是为什么会出现前述喜闻乐见的代码,因为其他地方用不着引用这个actionsheet。只为解决dangling pointer的问题而在类中添加一个实例变量保存指针,甚至要保存一个指针数组,并且这部分代码还和类本身逻辑的代码耦合在一起,有洁癖的人看起来总觉得刺眼。
 
理想中解决dangling pointer问题的方法,应该是一个通用的基础方法,与类的业务逻辑无关,代码相对独立。 
 
思路2:

不用实例变量,想办法在delegate dealloc的时候获得actionsheet的指针。

 
系统的view树一定是保存了actionsheet的指针的,第一反应是想在actionsheet上打tag,然后利用viewWithTag:方法来获取。或者,在dealloc的时候遍历整个view树来寻找当前存在的actionsheet,这两种方法本质上是相同的。我们暂且不讨论遍历view树的开销是否值得,只讨论方法可行性。刚才我们说过,iphone上的actionsheet是从属于一个内部window的,并不在我们程序可控的window中,所以上述方法根结点的选取是关键。
 
 UIActionSheet *sheet = [[UIActionSheet alloc] initWithTitle:@"sheet" delegate:self cancelButtonTitle:@"cancel" destructiveButtonTitle:nil otherButtonTitles:nil];
[sheet showInView:self.view]; [sheet setTag:kSheetTag]; NSLog(@"root(self.view.window):%@", [self.view.window viewWithTag:kSheetTag]); // null
NSLog(@"root(internal window):%@", [[UIApplication sharedApplication].windows[2] viewWithTag:kSheetTag]); // actionsheet found!

结果情理之中,我们在当前的window上是遍历不到这个actionsheet的,需要在之前说的_UIAlertOverlayWindow上遍历才行。于是我们可以先在actionsheet创建时打个tag,然后在X dealloc方法里这样写:(不能应付多个actionsheet弹出的情况)
 
 [[UIApplication sharedApplication].windows enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if (strcmp(class_getName([obj class]),"_UIAlertOverlayWindow") == 0)
{
UIActionSheet *theSheet = (UIActionSheet *)[obj viewWithTag:kSheetTag];
[theSheet setDelegate:nil];
}
}];
 
也可以不打tag,直接采用遍历view树的方式。(如果是在ipad上,不用使用内部window,直接遍历自己的self.view.superview的subviews就行了,可自行实验)

 [[UIApplication sharedApplication].windows enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[self traverseView:obj];
}]; // 遍历view树
- (void)traverseView:(UIView *)root
{
[[root subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[UIActionSheet class]])
{
if (((UIActionSheet *)obj).delegate == self)
{
((UIActionSheet *)obj).delegate = nil;
NSLog(@"enemy spotted!");
}
}
else
{
[self traverseView:obj];
}
}];
}

这样也解决了问题,其优点有一:
          一、不用改动类X的业务逻辑部分的代码,修改范围缩小到X的dealloc方法中,相对来讲便于移植到其他类中,也可以通过一些runtime的手段实现自动化。
          二、遍历的方法,可以轻易应对弹出多个actionsheet的情况。
 
其缺点有二:
          一、提起遍历view树,开销问题是肯定要考虑的。以某项目为例,一vc在dealloc的时候,view树中共有320个view,遍历寻找UIAcionSheet并置delegate空需要约0.002s,而正常的dealloc方法只需要不到0.0001s,时间提升20倍。实话说,这个开销并不是大到无法忍受,是否划算视具体情况而定。
          二、如果想节省这个开销,那么就需要利用一些“潜规则”,例如像上面viewWithTag的方法代码那样,利用_UIAlertOverlayWindow这个类名来缩小遍历范围。潜规则这个东西,如果用,请慎用。它们是毫无文档保障的,可能下一代iOS,这个实现就被改掉了,那时我们的代码就会出问题。譬如通过hack系统imagePicker的方式实现多图选择框,算是一个比较常见且“合理”的利用“潜规则”的例子(因为常规用assetslibrary实现的多图选择框在iOS5上会有定位权限的提示问题,这是很多产品不愿意接受的事情),但是iOS7中,imagePicker的内部ViewController的名字就被改掉了,原来用这种方式实现多图选择框的代码,就需要跟进修改。
 
思路3:

从思路1和2中我们可以得到这样的启发,如果有一个集合,里面存放了所有delegate指向X的actionsheet A(甚至其它对象实例),那么,我们就能在dealloc时遍历这个集合来置A.delegate = nil。
 
上述这种集合S有如下特征:
 
1、S能够与一个对象X实现1对1的绑定或对应,并在X dealloc的时候能被访问到。
2、在合适的时机(比如设置delegate时),能够对S添加或删除元素
 
我们先按1和2抽象出一个通用的包含集合S的类结构,取名为parasite:

 @interface DelegateBaseParasite : NSObject
{
NSMutableSet *sanctuarySet_; // 集合S
} // 创建并将自己(parasite)绑定(对应)到hostObj X 上
+ (DelegateBaseParasite *)parasitizeIn:(id)hostObj; // 返回已经绑定(对应)到hostObj X上的parasite对象(或nil若未绑定)
+ (DelegateBaseParasite *)getParasiteFromHost:(id)hostObj; // 添加一个对象object到此parasite的集合S中,当object.delegate = hostObj X的时候
- (void)addToSanctuary:(id)object; // 从此parasite的集合S中移除object,当object.delegate不再=X的时候
- (void)removeFromSanctuary:(id)object; // 将所有sanctuary中对象的delegate(此时都指向hostObj)置为nil
- (void)redemptionAll;
@end
大意是:如果每一个X都与一个这样的DelegateBaseParasite P绑定(对应),在设置A.delegate = X的时候,调用addToSanctuary将A添加到P的集合S中(同时通过removeFromSanctuary方法将A从旧delegate绑定parasite的集合S中移除),并且在X dealloc的时候执行redemptionAll方法来清空集合S里的所有对象的delegate属性,那么问题就解决了。
 
对集合S操作的方法没有什么复杂的。重点关注的是如何实现对象X与parasite P一对一的绑定。
我们发现这个parasite对象有如下特点:
1、与宿主的类型和实现完全无关,没有调用宿主的任何方法或访问任何实例变量。
2、只需要在宿主dealloc的时候调用自己的一个方法,并且自己也被销毁。
 
这让我们不禁想到了一个叫做associate object(关联对象文档)的东西!不妨将DelegateBaseParasite作为一个associate object,绑定到X上。按这个思路派生一个DelegateAssociativeParasite类,实现一下绑定相关方法:

 #define kDelegateAssociativeParasiteSanctuaryKey "kDelegateAssociativeParasiteSanctuaryKey"

@implementation DelegateAssociativeParasite

#pragma mark -
#pragma public interface
+ (DelegateAssociativeParasite *)parasitizeIn:(id)hostObj
{
DelegateAssociativeParasite *parasite = [[DelegateAssociativeParasite alloc] init]; objc_setAssociatedObject(hostObj, &kDelegateAssociativeParasiteSanctuaryKey, parasite, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return parasite;
} + (DelegateAssociativeParasite *)getParasiteFromHost:(id)hostObj
{
return objc_getAssociatedObject(hostObj, &kDelegateAssociativeParasiteSanctuaryKey);
} - (void)Dealloc
{
[self redemptionAll];
} @end
不知不觉,我们已经成功了一半。也就是说,还有另一半的问题需要我们解决:
 
即我们需要在actionsheet A的setDelegate:方法中删除绑定于旧delegate的集合S中的元素A,并添加A到绑定于新delegate X的集合S中。还要在A的dealloc方法中调用[self setDelegate:nil]
 
每次调用setDelegate的时候添加代码手动修改么?这显然不是一个好办法,并且,actionsheet的dealloc时机,并不由我们控制,想手动添加代码都办不到。那么有没有办法能修改这两个方法的实现,并且这种修改还能够调用到其原有的方法实现呢?
 
继承一个DelegateAutoUnregisteredUIActionSheet出来当然可以办到,但是将所有UIActionSheet替换掉,仍然要做不少工程,而且,功能上只是在UIActionSheet上打个自动注销delegate的补丁,没必要也不应该采用继承的方式。
 
能不能用category呢?category复写主类同名方法会产生warning,属于apple强烈不推荐的方式,而且就算强行复写了主类同名方法,也无法调用原来的实现。
 
那么怎么办呢?可以用objc的runtime提供的一些方法。先用class_addMethod为类添加一个新方法,在新方法中调用原有实现,再用method_exchangeImplementation将其与原有实现做交换。(objc runtime文档
 
按这个思路我们可以写一个辅助类DelegateAutoUnregisterHelper类(代码见附件示例工程)。

 
这样一来,另一半问题也解决了,现在只需在main.m里简单调用:
 
 [DelegateAutoUnregisterHelper registerDelegateAutoUnregisterTo:[UIActionSheet class]];
就可以实现actionsheet的delegate自动置空功能了。
 
这个利用associate object和runtime相结合的解法,也有其优缺点。其优点有二:
     一、向工程中添加的DelegateAssociativeParasite和DelegateAutoUnregisterHelper两个类是完全与其它类独立的代码,与业务逻辑无关,逻辑清晰。
     二、使用简单,只需要在main.m中调用一个方法对目标类(UIActionSheet)进行注册。项目之前“喜闻乐见”的代码完全不用做任何修改。
其缺点有一:
     一、广义的dangling pointer delegate出现最多的场景其实是多线程。一个线程释放了delegate对象,而另外一个线程恰好在使用它。反观我们刚才写的代码,却完全没有考虑任何线程安全的问题。
     
我们不禁要问两个问题:
     1. 解决UIActionSheet的delegate问题为什么可以不考虑线程安全?
     2. 这种利用associate object的思路,能否通过锁/信号量等方式解决线程安全的问题?
 
问题1是由UIActionSheet的使用场景决定的,作为一个系统的UI控件,在大多数情况下,其setDelegate、dealloc、showInView等方法,都是在UI线程中调用的。而其delegate一般都是一个UIView或者UIViewController,这两种对象的销毁通常也是发生在UI线程里(实际上,假如我们发现我们的某些View或者ViewController的最后一次释放以致销毁跑到了非UI线程,我们应该停下来思考一下是不是设计上出了问题,因为View和VC的释放很有可能会涉及到一些在UI线程才能进行的操作。)当然,我说的是大多数情况,而并非绝对。因而通常正常使用actionsheet并不会涉及线程安全问题。
 
那么来到问题2,这种以associate object为核心的绑定方式,究竟有没有可能解决线程安全问题呢?
 
一推敲,天然的缺陷就暴露出来了。
 
之前我们一直刻意模糊了一个概念,即“当X dealloc的时候”。dealloc的时候是什么时候?是dealloc前还是dealloc后?
 
对于associate object,其dealloc方法,是在其宿主X的dealloc方法调用完毕以后,也就是宿主X已经被销毁之后,才调用的。也就是说,delegate的置空是在delegate被销毁之后。无论之间间隔多么短,总是有那么一瞬间,X已经被销毁了,delegate还没有被置空,dangling pointer出现,如果是在多线程的场景下,就有可能有另外的线程在此时访问到了这个dangling pointer,程序依然会crash。
 
所以,基于associate object的解决方案,归根结底是无法解决线程安全的问题的。
 
那么怎样才能做出一个线程安全的dangling pointer delegate问题的解决方案呢?
 
思路4:

既然问题出在associate object上,那我们就不用它,想想有没有其它实现X与P一对一绑定(对应)的方法。这时我们又想起了weak指针。系统是怎么做到将object与指向其的weak指针集合绑定(对应)在一起的呢?

 
关于weak指针的实现,我们可以在llvm.org上看到相关的文档内容(http://clang.llvm.org/docs/AutomaticReferenceCounting.html),但是不够详细。更直接的方式是阅读http://www.opensource.apple.com/source/objc4/里面的NSObject和runtime实现的源码。
 
简而言之,编译器实现的weak指针与我们的中心思想是一致的,即用一种方法绑定对象X和一个指向X的需要监视的指针集合,并在X dealloc之时自动将集合内元素置空。只不过与associate object的方法相比,有两点不同:
 
1. 绑定对象,用的是一个全局的hash table(SideTable),而非associate object。hash table的key对应一个对象X,value为指针集合。
2. dealloc之时,指的是X的dealloc方法调用过程之中,而非最终销毁以后,这样就不存在天然的缺陷,其线程安全问题是可以通过在hash table上加锁来解决的。
 
按照这个思路,我们来派生一个新的DelegateDictParasite类,实现另一种利用CFDictionary的绑定(对应)的方法:

 @implementation DelegateDictParasite

+ (DelegateDictParasite *)parasitizeIn:(id)hostObj
{
if (!class_getInstanceMethod([hostObj class], @selector(myHostObjDealloc)))
{
[DelegateDictParasite addNewMethodToHost:[hostObj class]];
[DelegateAutoUnregisterHelper mergeOldSEL:[DelegateAutoUnregisterHelper deallocSelector] NewSEL:@selector(myHostObjDealloc) ForClass:[hostObj class]];
[DelegateAutoUnregisterHelper mergeOldSEL:[DelegateAutoUnregisterHelper releaseSelector] NewSEL:@selector(myHostObjRelease) ForClass:[hostObj class]];
} DelegateDictParasite *parasite; @synchronized(kDelegateAssociativeParasiteLock)
{
if (!delegateHostParasiteHashTable)
{
delegateHostParasiteHashTable = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
} parasite = [[DelegateDictParasite alloc] init]; CFDictionarySetValue(delegateHostParasiteHashTable, (__bridge const void *)(hostObj), (__bridge const void *)(parasite));
} return parasite;
} + (DelegateDictParasite *)getParasiteFromHost:(id)hostObj
{
DelegateDictParasite *parasite; @synchronized(kDelegateAssociativeParasiteLock)
{
if (!delegateHostParasiteHashTable)
{
return nil;
} parasite = CFDictionaryGetValue(delegateHostParasiteHashTable, (__bridge const void *)(hostObj));
} return parasite;
} @end
这里,由于没有了associate object的帮助,X dealloc与parasite dealloc的联动需要我们自己触发,同样利用runtime,我们可以改写每一个X的dealloc方法来完成这种联动,解除hash table中对X的绑定,从而引发自动置空。
 
另外,通过锁,我们可以解决线程安全问题。从而解决多线程下delegate的dangling pointer问题。(完整代码见附录)
 
这种思路,其优点有二:
     一、没有了先天缺陷,解决了线程安全问题,从而可以推广到广义的dangling pointer delegate问题上。
     二、用法与思路三一样,比较简单。

其缺点有二:
     一、用了全局的一个hash table。一般有洁癖的人看到全局变量会不舒服。
     二、对每一个成为delegate的对象X的类,都会修改其dealloc方法,不像associate object的联动那么自然,有点不干净。
 
思路5:
 
GitHub上有一个mikeash写的开源项目MAZeroingWeakRef,目的是在不支持weak的情况下提供一个weak指针的实现,其实现思想也是与系统weak指针类似,即利用全局hash table来做。与思路4不同的是,它修改X的dealloc方法,是通过动态继承出X的一个子类,然后在子类上addMethod的方式,而不是利用method_exchangeImplementation。
 
这个项目考虑了更多的情况,比如说对于KVO的支持以及toll-free的CF对象的处理(不过用到了私有API)等等,大家有兴趣和时间的话可以研究一下,不再赘述。
 
其优点有二:
     一、考虑了KVO/CF等情况的支持,更加严谨。
     二、动态继承的方式把dealloc方法修改的范围缩小到只是使用weak的实例而不是此类的所有实例,解决了思路4的缺点二。
其缺点有二:
     一、动态继承的方式修改了类的名字。
     二、只是用来在weak不能使用的条件下实现weak指针,可以解决自定义的delegate的dangling pointer问题,并不能解决文中已经被指定为assign类型的系统控件delegate的问题。
 
注:本文由于篇幅所限,实现过程中一些坑和有意思的地方并未一一提及。例如修改方法实现的时候,需要注意修改的是父类方法还是子类方法;一些方法实现只能放在非ARC(添加-fno-objc-arc标志)文件中;等等。
 
 
【总结】
 
本文逐步思考并总结的几种解决dangling pointer问题的思路各有优缺点,并不存在哪种一定最好,要具体情况具体分析。相比之下,思路4是解决多线程delegate的dangling pointer的较为完整的解决方案。思考和实现过程当中还有很多不成熟的地方,欢迎大家一起讨论、不正确的地方也欢迎批评指正。

iOS上Delegate的悬垂指针问题的更多相关文章

  1. 转iOS中delegate、protocol的关系

    iOS中delegate.protocol的关系 分类: iOS Development2014-02-12 10:47 277人阅读 评论(0) 收藏 举报 delegateiosprocotolc ...

  2. IOS上传文件开发

    IOS上传文件开发     在移动应用开发  文件形式上传是不可缺少的,近期把IOS这块文件上传文件代码简单的整理一下.假设大家有须要安卓这边的代码,本人也能够分享给大家! QQ群:74432915 ...

  3. 【转】iOS 上常用的两个功能:点击屏幕和return退出隐藏键盘和解决虚拟键盘挡住UITextField的方法

    iOS上面对键盘的处理很不人性化,所以这些功能都需要自己来实现, 首先是点击return和屏幕隐藏键盘 这个首先引用双子座的博客 http://my.oschina.net/plumsoft/blog ...

  4. 细数iOS上的那些安全防护

    细数iOS上的那些安全防护  龙磊,黑雪,蒸米 @阿里巴巴移动安全 0x00 序 随着苹果对iOS系统多年的研发,iOS上的安全防护机制也是越来越多,越来越复杂.这对于刚接触iOS安全的研究人员来说非 ...

  5. 微信双开是定时炸弹?关于非越狱iOS上微信分身高危插件ImgNaix的分析

    作者:蒸米@阿里移动安全 序言 微信作为手机上的第一大应用,有着上亿的用户.并且很多人都不只拥有一个微信帐号,有的微信账号是用于商业的,有的是用于私人的.可惜的是官方版的微信并不支持多开的功能,并且频 ...

  6. ios上position:fixed失效问题

    手机端上的猫腻真是多啊~~~ 此起彼伏! 最近又遇到了 固定定位的底部导航在ios上被弹出去 此时内心1w+个草泥马奔过~~~~~~~~ 直接上解决方案: <div class="ma ...

  7. :active 为什么在ios上失效

    :active是针对鼠标,而手机上是没有鼠标,而是touchstart,所以早成了ios上不兼容 解决方法是: window.onload = function(){ document.body.ad ...

  8. 解决protobuf不能直接在IOS上使用,利用protobuf-net在IOS上通讯

    ---------------------------------------------------------------------------------------------------- ...

  9. iOS上简单推送通知(Push Notification)的实现

    iOS上简单推送通知(Push Notification)的实现 根据这篇很好的教程(http://www.raywenderlich.com/3443/apple-push-notification ...

随机推荐

  1. python3模块: uuid

    一. 简介 UUID是128位的全局唯一标识符,通常由32字节的字母串表示.它可以保证时间和空间的唯一性,也称为GUID. 全称为:UUID--Universally Unique IDentifie ...

  2. docker私服registry管理镜像

    前言 首先试想这样一个场景:当在自己的机器上(docker中)构建了mysql镜像,eureka镜像等等微服务镜像,这些镜像有可能需要放到其他的机器上docker环境中去运行,实行分布式架构部署.但如 ...

  3. vertical-tical

    通常我们需要垂直对齐并排的元素. CSS提供了一些可实现的方法:有时我用浮动float来解决,有时用position: absolute来解决,有时甚至是“肮脏”地手动添加的margin或paddin ...

  4. Java之集合(四)Vector和Stack

    转载请注明源出处:http://www.cnblogs.com/lighten/p/7296023.html 1.前言 本章介绍Java集合List中的Vector和其子类Stack.Vector类是 ...

  5. 【Java并发编程】:深入Java内存模型—内存操作规则总结

    主内存与工作内存 java内存模型的主要目标是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节.此处的变量主要是指共享变量,存在竞争问题的变量.Java内存模 ...

  6. 【C#小知识】C#中一些易混淆概念总结(八)---------解析接口 分类: C# 2014-02-18 00:09 2336人阅读 评论(4) 收藏

     这一篇主要来解析关于面向对象中最总要的一个概念--接口. 对于接口来说,C#是有规定使用Interface关键字来声明接口.它的声明是和类一致的.可以说接口就是一个特殊的抽象类.如下代码: cl ...

  7. Android 开发工具类 24_getHtml

    获取网页(JSP)源码 import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; impo ...

  8. SPSS学习系列之SPSS Modeler的功能特性(图文详解)

    不多说,直接上干货! Win7/8/10里如何下载并安装最新稳定版本官网IBM SPSS Modeler 18.0 X64(简体中文 / 英文版)(破解永久使用)(图文详解)   我这里,是以SPSS ...

  9. docker 使用swarm overlay网络时,报“network xx not manually attachable”错误解决

    当使用swarm的overlay网络,在该网络中运行容器时报“network xx not manually attachable”的错误 docker network create -d overl ...

  10. layer相关使用

    父子页面传参数 转自:https://blog.csdn.net/babyxue/article/details/76854106 1.父页面打开子页面并向子页面传参数 function setCho ...