GithubHelp home page GithubHelp logo

lsmakethebest / lssafeprotector Goto Github PK

View Code? Open in Web Editor NEW
666.0 13.0 128.0 6.95 MB

强大的防止crash框架,不改变原代码支持KVO自释放,可以检测到dealloc时未释放的kvo,等19种crash

License: MIT License

Objective-C 99.18% Ruby 0.82%

lssafeprotector's Introduction

中文说明

LSSafeProtector 是一个可快速集成但功能强大的防止crash库,不改变原代码支持KVO自释放,可以检测到dealloc时未释放的kvo,等19种crash,使用Objective-C编写.

须知

LSSafeProtector 基于 "Xcode 7.3 , iOS 7+ 和ARC ,请使用最新正式版来编译LSSafeProtector,旧版本的Xcode可能有效,但不保证会出现一些兼容性问题。

CocoaPods

推荐使用 CocoaPods 安装。

  1. 在 Podfile 中添加 pod 'LSSafeProtector'
  2. 执行 pod installpod update。(如搜索不到,请更新pod仓库pod repo update)
  3. 导入 "LSSafeProtector.h"

手动安装

通过 Clone or download 下载 LSSafeProtector 文件夹内的所有内容。 将 LSSafeProtector 内的源文件添加(拖放)到你的工程。 将 NSMutableArray+MRCSafe.m文件 设置为 -fno-objc-arc 导入 LSSafeProtector.h

使用

切记 切记 切记!!!

[LSSafeProtector openSafeProtectorWithIsDebug]一定要在其他SDK之前调用

  • 通过如下方式开启防止闪退功能(不包含KVO防护,想要开启KVO防护可以使用下面的方法),debug模式会打印crash日志,同时会利用断言来让程序闪退,也会回调block,达到测试环境及时发现及时修改,Release模式既不打印也不会断言闪退,会回调block,自己可以上传exception到bugly(注意线上环境isDebug一定要设置为NO)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //注意线上环境isDebug一定要设置为NO)
    [LSSafeProtector openSafeProtectorWithIsDebug:YES block:^(NSException *exception, LSSafeProtectorCrashType crashType) {
    //[Bugly reportException:exception];

    //此方法相对于上面的方法,好处在于bugly后台查看bug崩溃位置时,不用点击跟踪数据,再点击crash_attach.log,查看里面的额外信息来查看崩溃位置
    [Bugly reportExceptionWithCategory:3 name:exception.name reason:[NSString stringWithFormat:@"%@  崩溃位置:%@",exception.reason,exception.userInfo[@"location"]] callStack:@[exception.userInfo[@"callStackSymbols"]] extraInfo:exception.userInfo terminateApp:NO];
    }];
    //打开KVO添加,移除的日志信息
    [LSSafeProtector setLogEnable:YES];
    [Bugly startWithAppId:@"5c825b6c8d"];
    //···调用其他SDK或初始化东西
    return YES;
}

  • 当然你也可以设置防止指定类型的crash,但还是建议直接使用上面方法,防止所有类型的crash来防止闪退
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [LSSafeProtector openSafeProtectorWithIsDebug:isDebug types:LSSafeProtectorCrashTypeNSArrayContainer|LSSafeProtectorCrashTypeNSDictionaryContainer block:^(NSException *exception, LSSafeProtectorCrashType crashType) {
    //[Bugly reportException:exception];
    //此方法方便在bugly后台查看bug崩溃位置,而不用点击跟踪数据,再点击crash_attach.log来查看崩溃位置
    [Bugly reportExceptionWithCategory:3 name:exception.name reason:[NSString stringWithFormat:@"%@  崩溃位置:%@",exception.reason,exception.userInfo[@"location"]] callStack:@[exception.userInfo[@"callStackSymbols"]] extraInfo:exception.userInfo terminateApp:NO];
    }];
    //打开KVO添加,移除的日志信息
    [LSSafeProtector setLogEnable:YES];
    [Bugly startWithAppId:@"5c825b6c8d"];
    //···调用其他SDK或初始化东西
    return YES;
}

下面是防止崩溃的效果

  • 可导致崩溃的代码
NSMutableArray *a1=[NSMutableArray array];
a1[10];
  • 若没有防止崩溃,则会直接崩溃,如下图所示 image

  • 用本框架来防止崩溃,则会捕获到崩溃信息并打印出来(测试环境会利用断言闪退达到及时发现及时修改),如下图 image

  • 来看看block回调回来的信息都有哪些 image

  • KVO 检测到dealloc时有没remove的keyPath image

  • 更多的使用用例可以看Demo工程演示

目前支持以下类型crash

  • 1、LSSafeProtectorCrashTypeSelector
1.捕获到未实现方法时,自动将消息转发,避免crash
  • 2、LSSafeProtectorCrashTypeKVO
1.移除未注册的观察者 会crash
2.重复移除观察者 会crash
3.添加了观察者但没有实现observeValueForKeyPath:ofObject:change:context:方法
4.添加移除keypath=nil;
5.添加移除observer=nil;
6.dealloc时自动移除观察者,俗称自释放KVO
  • 3、LSSafeProtectorCrashTypeNSArray
1. NSArray的快速创建方式 NSArray *array = @[@"chenfanfang", @"AvoidCrash"];//调用的是3的方法
2. + (instancetype)arrayWithObjects:(const ObjectType _Nonnull [_Nonnull])objects count:(NSUInteger)cnt;调用的也是3的方法
3. - (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects count
4. - (id)objectAtIndex:(NSUInteger)index
******  注意 *****
[__NSCFArray objectAtIndex]不能防止crash,如果交换了会导致其他crash,所以这里不做交换

  • 4、LSSafeProtectorCrashTypeNSMutableArray
1. - (void)addObject:(ObjectType)anObject(实际调用insertObject:)
2. - (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;
3. - (id)objectAtIndex:(NSUInteger)index( 包含   array[index] 形式)
4. - (void)removeObjectAtIndex:(NSUInteger)index
5. - (void)replaceObjectAtIndex:(NSUInteger)index
6. - (void)removeObjectsInRange:(NSRange)range
7. - (void)replaceObjectsInRange:(NSRange)range withObjectsFromArray:(NSArray*)otherArray;
  • 5、LSSafeProtectorCrashTypeNSDictionary
1.+ (instancetype)dictionaryWithObjects:(const ObjectType _Nonnull [_Nullable])objects forKeys:(const KeyType <NSCopying> _Nonnull [_Nullable])keys count:(NSUInteger)cnt会调用2中的方法
2.- (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects forKeys:(const KeyType _Nonnull [_Nullable])keys count:(NSUInteger)cnt;
3. @{@"key1":@"value1",@"key2":@"value2"}也会调用2中的方法
4. - (instancetype)initWithObjects:(NSArray<ObjectType> *)objects forKeys:(NSArray<KeyType <NSCopying>> *)keys;
  • 6、LSSafeProtectorCrashTypeNSMutableDictionary
1.直接调用 setObject:forKey
2.通过下标方式赋值的时候,value为nil不会崩溃
iOS11之前会调用 setObject:forKey
iOS11之后(含11)  setObject:forKeyedSubscript:
3.removeObjectForKey
  • 7、LSSafeProtectorCrashTypeNSStirng
1. initWithString
2. hasPrefix
3. hasSuffix
4. substringFromIndex:(NSUInteger)from
5. substringToIndex:(NSUInteger)to {
6. substringWithRange:(NSRange)range {
7. characterAtIndex:(NSUInteger)index
8. stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement 实际上调用的是9方法
9. stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange
10. stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement
  • 8、LSSafeProtectorCrashTypeNSMutableString
//除NSString的一些方法外又额外避免了一些方法crash
1.- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString;
2.- (NSUInteger)replaceOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange;
3.- (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc;
4.- (void)deleteCharactersInRange:(NSRange)range;
5.- (void)appendString:(NSString *)aString;
6.- (void)setString:(NSString *)aString;
  • 9、LSSafeProtectorCrashTypeNSAttributedString
1.- (instancetype)initWithString:(NSString *)str;
2.- (instancetype)initWithString:(NSString *)str attributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attrs;
3.- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr;
  • 10、LSSafeProtectorCrashTypeNSMutableAttributedString
1.- (instancetype)initWithString:(NSString *)str;
2.- (instancetype)initWithString:(NSString *)str attributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attrs;
3.- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr;

4. - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)str;
5.- (void)setAttributes:(nullable NSDictionary<NSAttributedStringKey, id> *)attrs range:(NSRange)range;

6.- (void)addAttribute:(NSAttributedStringKey)name value:(id)value range:(NSRange)range;
7.- (void)addAttributes:(NSDictionary<NSAttributedStringKey, id> *)attrs range:(NSRange)range;
8.- (void)removeAttribute:(NSAttributedStringKey)name range:(NSRange)range;

9.- (void)replaceCharactersInRange:(NSRange)range withAttributedString:(NSAttributedString *)attrString;
10.- (void)insertAttributedString:(NSAttributedString *)attrString atIndex:(NSUInteger)loc;
11.- (void)appendAttributedString:(NSAttributedString *)attrString;
12.- (void)deleteCharactersInRange:(NSRange)range;
13.- (void)setAttributedString:(NSAttributedString *)attrString;

  • 11、LSSafeProtectorCrashTypeNSNotificationCenter
1. dealloc时自动将self从通知中心移除

  • 12、LSSafeProtectorCrashTypeNSUserDefaults
可避免以下方法  key=nil时的crash
1.objectForKey:
2.stringForKey:
3.arrayForKey:
4.dataForKey:
5.URLForKey:
6.stringArrayForKey:
7.floatForKey:
8.doubleForKey:
9.integerForKey:
10.boolForKey:
11.setObject:forKey:

  • 13、LSSafeProtectorCrashTypeNSCache
1.setObject:forKey:
2.setObject:forKey:cost:

  • 14、LSSafeProtectorCrashTypeNSSet
1.setWithObject:
2.(instancetype)initWithObjects:(ObjectType)firstObj
3.setWithObjects:(ObjectType)firstObj

  • 15、LSSafeProtectorCrashTypeNSMutableSet
1.setWithObject:
2.(instancetype)initWithObjects:(ObjectType)firstObj
3.setWithObjects:(ObjectType)firstObj
4.addObject:
5.removeObject:

  • 16、LSSafeProtectorCrashTypeNSData
1.subdataWithRange:
2.rangeOfData:options:range:

  • 17、LSSafeProtectorCrashTypeNSMutableData
1.subdataWithRange:
2.rangeOfData:options:range:
3.resetBytesInRange:
4.replaceBytesInRange:withBytes:
5.replaceBytesInRange:withBytes:length:

  • 18、LSSafeProtectorCrashTypeNSOrderedSet
1.orderedSetWithSet
2.initWithObjects:count:
3.objectAtIndex:

  • 19、LSSafeProtectorCrashTypeNSMutableOrderedSet
1. - (void)addObject:(ObjectType)anObject
2. - (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;
3. - (id)objectAtIndex:(NSUInteger)index( 包含  set[index]  形式  )
4. - (void)removeObjectAtIndex:(NSUInteger)index
5. - (void)replaceObjectAtIndex:(NSUInteger)index

更新记录

  • 2018.9.13 更新日志
添加对以下类的支持
NSUserDefaults 
NSCache
NSSet 
NSMutableSet
NSOrderedSet
NSMutableOrderedSet
NSData
NSMutableData

  • 2018.11.13 更新
将__NSCFArray的hook使用MRC编写
NSMutableArray增加以下方法的hook
- (void)replaceObjectsInRange:(NSRange)range withObjectsFromArray:(NSArray*)otherArray;

联系

  • 如果使用过程中遇到什么问题或有什么建议可以联系我,我会在收到后尽快回复您

  • QQ群 :922898729

  • email at: [email protected]

  • email at: [email protected]

注意

  • 最近好多人问我为什么线上环境崩溃位置定位不到,这是因为导出ipa包安装,崩溃位置是定位不到的,即使Debug模式导出ipa也是定位不到,和正式测试没关系,是由于ipa包安装的crash日志是非源码,无法直接分析定位,必须符号化。xcode安装是源码安装。具体符号化步骤可以参照网上,这里不做过多说明,本框架的主旨是防止crash,而不是定位crash

许可

LSSafeProtector 使用 MIT 许可证,详情可见 LICENSE 文件。

lssafeprotector's People

Contributors

lsmakethebest avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lssafeprotector's Issues

使用AVPlayer播放的时候Crash 掉了

Cannot remove an observer <AVKVODispatcher 0x600000018f60> for the key path "currentItem" from <AVPlayer 0x600000018650> because it is not registered as an observer.
[Crash Location]: -[NSObject(KVOSafe) safe_removeObserver:forKeyPath:]

调用系统分享弹窗闪退,去掉集成代码恢复正常!!!

[Crash Type]: NSCache setObject:forKey: key and value cant be nil [Crash Reason]: NSCache setObject:forKey: key and value cant be nil
[Crash Location]: -[UIView(LEEAlertExtension) lee_alert_view_layoutSubviews]

不知道是什么原因,搞得不敢用了,怕出现问题,就借鉴下代码,自己写需要的扩展吧.量级搞得太大了

存在5处僵尸对象问题,kvc赋值时候赋值为nil的时候不会崩溃,但是会按照崩溃异常来处理

-(void)safe_logKVODebugInfoWithText:(NSString*)text observer:(id)observer keyPath:(NSString*)keyPath context:(void*)context
{
NSString *method;
if ([text rangeOfString:@"添加"].length>0) {
method=@" addObserver ";存在僵尸对象)
}else{
method=@"removeObserver";存在僵尸对象)
}
NSString *emoji;
if ([text rangeOfString:@"成功"].length>0) {
emoji=@"😀😀😀😀😀";存在僵尸对象)
}else{
emoji=@"😡😡😡😡😡";(存在僵尸对象)
}

// LSKVOSafeLog(@"\n******* %@ %@: ##################\n\t%@(%p) %@ %@(%p) keyPath:%@ context:%p\n----------------------------------------",text,emoji,[self class],self,method,[observer class],observer,keyPath,context);
}

//这里也出现僵尸对象
[regularExp enumerateMatchesInString:callStackSymbolString options:NSMatchingReportProgress range:NSMakeRange(0, callStackSymbolString.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
if (result) {
mainCallStackSymbolMsg = [callStackSymbolString substringWithRange:result.range];
*stop = YES;
}
}];

什么场景下会触发 __NSPlaceholderDictionary 的 initWithObjects:forKeys: 防护

NSDictionary+Safe 里有 [self safe_exchangeInstanceMethod:NSClassFromString(@"__NSPlaceholderDictionary") originalSel:@selector(initWithObjects:forKeys:) newSel:@selector(safe_initWithObjects:forKeys:)]; 这样的防护代码,但正如 LSViewController 里的 testDictionary 所示

NSString *value=nil;
NSString *strings[3];
strings[0]=@"000";
strings[1]=value;
strings[2]=@"222";
[[NSDictionary alloc]initWithObjects:strings forKeys:strings count:3];

这代码首先会触发的是 Array 的崩溃而不是 Dictionary 的
Trapped uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[1]'

关于KVO 释放问题

你好,我在iOS 12.4上进行了测试。发现现在KVO在dealloc之前如果不将其remove的话也不会崩溃了。
不清楚是我测试代码的问题还是苹果改了KVO的逻辑。可以帮忙验证一下吗?谢谢

点击视图调试Debug View Hierachy就崩溃

以下是崩溃信息,我不写字典的crash防护是不会崩溃的。

------------------------------------  Crash START -------------------------------------
		[Crash Type]: NSInvalidArgumentException
		[Crash Reason]: *** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]
		[Crash Location]: -[DBGTargetHub performRequestWithRequestInBase64:]
函数堆栈:
(
	0   CoreFoundation                      0x000000018bb7d294 <redacted> + 252
	1   libobjc.A.dylib                     0x000000018ad579f8 objc_exception_throw + 56
	2   CoreFoundation                      0x000000018baf6ce8 _CFArgv + 0
	3   CoreFoundation                      0x000000018ba7e9a8 <redacted> + 352
	4   LSSafeProtector                     0x0000000105d30310 -[NSDictionary(Safe) safe_initWithObjects:forKeys:count:] + 96
	5   CoreFoundation                      0x000000018ba6f584 <redacted> + 64
	6   QuartzCore                          0x000000018ff12c14 CAEncodeLayerTreeWithInfo + 320
	7   libViewDebuggerSupport.dylib        0x000000010242b3b8 +[CALayer(DebugHierarchyAdditionsFallback) fallback_debugHierarchyValueForPropertyWithName:onObject:outOptions:outError:] + 144
	8   DebugHierarchyFoundation            0x0000000107e30034 +[DebugHierarchyObjectProtocolHelper debugHierarchyValueForPropertyWithName:onObject:vendingClass:outOptions:outError:] + 228
	9   DebugHierarchyFoundation            0x0000000107e3db08 +[DebugHierarchyObjectInterface valueForProperty:withOutOptions:onObject:inContext:error:] + 620
	10  DebugHierarchyFoundation            0x0000000107e3d468 +[DebugHierarchyObjectInterface valueAndOptionsForProperty:onObject:inContext:] + 124
	11  DebugHierarchyFoundation            0x0000000107e3ce10 -[DebugHierarchyPropertyAction _fetchValuesForPropertiesWithNames:onObject:inContext:] + 456
	12  DebugHierarchyFoundation            0x0000000107e3c46c -[DebugHierarchyPropertyAction performInContext:withObject:] + 1084
	13  DebugHierarchyFoundation            0x0000000107e40b10 +[DebugHierarchyRequestActionExecutor _executeObjectActions:withObject:inContext:] + 224
	14  DebugHierarchyFoundation            0x0000000107e408c0 -[DebugHierarchyRequestActionExecutor executeActionsWithObject:] + 128
	15  DebugHierarchyFoundation            0x0000000107e3b480 -[DebugHierarchyRequestExecutor _executeRequestActionsWithKnownObjects] + 400
	16  DebugHierarchyFoundation            0x0000000107e399c0 -[DebugHierarchyRequestExecutor runWithError:] + 288
	17  DebugHierarchyFoundation            0x0000000107e427b8 __48-[DebugHierarchyTargetHub performRequest:error:]_block_invoke + 80
	18  DebugHierarchyFoundation            0x0000000107e426c4 -[DebugHierarchyTargetHub performRequest:error:] + 192
	19  DebugHierarchyFoundation            0x0000000107e42568 -[DebugHierarchyTargetHub performRequestWithRequestInBase64:] + 148
	20  DebugHierarchyFoundation            0x0000000107e4222c -[DBGTargetHub performRequestWithRequestInBase64:] + 84
	21  ???                                 0x000000010f11c998 0x0 + 4547791256
	22  demo                                0x00000001011dacd0 main + 0
	23  CoreFoundation                      0x000000018bb0ea10 <redacted> + 236
	24  CoreFoundation                      0x000000018bb09920 <redacted> + 1360
	25  CoreFoundation                      0x000000018bb090b0 CFRunLoopRunSpecific + 436
	26  GraphicsServices                    0x000000018dd0979c GSEventRunModal + 104
	27  UIKitCore                           0x00000001b833f978 UIApplicationMain + 212
	28  demo                                0x00000001011dad4c main + 124
	29  libdyld.dylib                       0x000000018b5ce8e0 <redacted> + 4
)
------------------------------------   Crash EN

不知怎么的启用之后经常会无故崩溃

似乎是和我使用AVOSCloud做存储和收集反馈有关,也抓不到日志,只确定应该是在SDK内部后台传输数据的时候崩溃的。
抱歉提了这么一个含糊的Issue,不过我很看好你的想法,也算提供一个思路吧。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.