iOS疑难杂症汇总

iOS疑难杂症,记录曾作为iOS开发者时踩过的坑。

iOS疑难杂症汇总

目录

  1. EXC_BREAKPOINT(code=EXC_XXX_BPT,subcode=0x0) 非法指令
  2. Terminating app due to uncaught exception 'NSUnknownKeyException'
  3. push完成后子页面布局再下沉
  4. 禁用某个指定的页面系统自带的滑动返回
  5. 去除字符串html样式及标签
  6. clang: error: linker command failed with exit code 1 (use -v to see invocation)
  7. CoreData多表联查问题
  8. You don’t have permission
  9. 改变UITabBar上面字体的颜色
  10. 使UITabBar上面的图片不渲染,保持图片原有的颜色
  11. FMDB插入大量数据耗时太久
  12. 获取一个view在window上面的位置
  13. 去除一个已弃用的低版本API警告
  14. 设置自定义的按钮文字在左,图片在右
  15. Xcode中打印字典或数组中的中文内容,输出Unicode编码。
  16. 在UITableView加载完成后获取到tableView的contentSize。
  17. 在ViewController的view上添加背景图片。
  18. 动态获取对象属性的名称、类型、值,并对类型进行识别。
  19. 镂空视图
  20. 在类别中添加属性
  21. 计算源码行数
  22. 持续更新中...

问题1

EXC_BREAKPOINT(code=EXC_XXX_BPT,subcode=0x0) 非法指令
打开Zombie Objects 后输出:

-[XXXXXXViewContoller respondsToSelector:]: message sent to deallocated instance 0x7fcb83cef240
原因:

很明显这是因为一个UIViewController释放后,又再次向这个VC调用了某些请求导致。而且根据log发现该VC是执行了dealloc方法,这就说明可能是在VC中设置了 xxx.delegate=self;VC释放后,这个xx还没有被释放,所以xx的回调方法还在调用delegate即这里的VC, 所以崩溃就发生了。

解决办法:

解决办法是在dealloc中设置xx.delegate = nil;即可。(吐槽一句:为毛apple的工程师不判断一下delegate是否为nil再去调用方法)


问题2

Terminating app due to uncaught exception 'NSUnknownKeyException'

Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<UIViewController 0x7f861c40a750> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key tableView.'
*** First throw call stack:
原因:

这个问题在自定义的xib或者stroybord中都会出现,原因是因为某些原因,(从别的地方复制了xib过来、将xib进行了删除然后又撤销回来等)这个时候虽然在xib上面或者单单从代码上面看,是没有问题的,但是因为复制或者还原是某些数据并不能恢复,这将就导致了xib和代码里的东西不一致,像上面这个错误明细就是说在ViewController里面找不到tableView,其它的情况基本类似

解决办法:

检查outlets连接器,看看有没有已经被删除,却还存在的链接,如果这个办法不能解决,可以去查看xib的Source Code,检查有没有多余的连接,最后如果还找不出来的话,删掉xib,重新写肯定能行。


问题3

在带有tabBar的页面使用NavigationController push到子页面去,子页面push过程中保留Tabbar的高度,push完成后子页面布局再下沉

原因:

这个问题在新版的xcode8中出现,原因是使用stroybord创建约束的时候,view上面的子视图对照的是Bottom Layout Guide(列如:self.view上面放一个按钮,建立和self.view的上下左右约束,上下约束默认是参照Top Layout Guide/Bottom Layout Guide),而不是self.view (注:此方法不能兼容iPhone X,iOS11下应该为系统的Bug,iOS11可以直接与Top Layout Guide/Bottom Layout Guide建立约束不会出现下沉的问题)

解决办法:

将子视图原来的buttom约束删掉,再选择子视图和self.view,在Align中选择Bottom edges 设置为想要设置的值就ok了。


问题4

在使用appearance设置了自定义的导航栏返回按钮后,想禁用某个指定的页面系统自带的滑动返回,使用self.navigationController.interactivePopGestureRecognizer.enabled = NO 并没有上面卵用,或者设置interactivePopGestureRecognizer代理,或者再写一个滑动事件加在这个页面上,你会发现都没有什么用

原因:

其实在self.navigationController.view.gestureRecognizers中的手势有两个,interactivePopGestureRecognizer只是其中的一个,另外还有一个拖动手势并没有被禁用掉,所有针对interactivePopGestureRecognizer方法统统没有效果

解决办法:

遍历self.navigationController.view.gestureRecognizers,将所有的手势都禁用掉,代码如下

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    //关闭navigationController上面的所有手势
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        for (UIGestureRecognizer *gr in self.navigationController.view.gestureRecognizers) {
            gr.enabled = NO;
        }
    }
}
-(void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    //打开navigationController上面的所有手势
    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        for (UIGestureRecognizer *gr in self.navigationController.view.gestureRecognizers) {
            gr.enabled = YES;
        }
    }
}

问题5

去除字符串html样式及标签

-(NSString *)filterHTML:(NSString *)html
{
    NSScanner * scanner = [NSScanner scannerWithString:html];
    NSString * text = nil;
    while([scanner isAtEnd]==NO)
    {
        [scanner scanUpToString:@"<" intoString:nil];
        [scanner scanUpToString:@">" intoString:&text];
        html = [html stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"%@>",text] withString:@""];
    }
    return html;
}

问题6

clang: error: linker command failed with exit code 1 (use -v to see invocation)

编译时链接器器工作时出现了错误,由此可见是文件没有被链接索引到。这种问题一般出现在导入SDK中的.a或者.o文件时,或者在项目中添加了多个重复类名的问题,导致链接器抛出错误。

解决办法:

一般在这个错误的详细信息里面会有linker时出问题的类名或者文件名字,找到相关的文件排查一下,

  • 可以将错误日志中包含的文件先删除掉,再重新导入了;
  • 检查文件库的引用路径是否正确,
  • 检查新添加的文件是否在工程配置汇总添加目录索引;
  • 如果项目中有多个target,可以看看这个文件是否有包含在当前编译的target中;
  • 检查是否有重复命名的类文件。
  • 检查引入的文件中是否有重复定义的常量、宏、结构体
  • 检查是否已经将引入的第三方SDK所依赖的系统库文件添加到项目中
  • shift+cmd+K清除一下再重新编译(Xcode 可能抽风了)

问题7

【在CoreData中】,使用分组查询想要的字段,但是并不想group by这个字段,列如:select A,B from table group by A 是做不到的,这尼玛就尴尬了,这会报出以下异常错误

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'SELECT clauses in queries with GROUP BY components can only contain properties named in the GROUP BY or aggregate functions ((<NSAttributeDescription: 0x6080002e3b80>), name guidanceprice, isOptional 1, isTransient 0, entity Cars, renamingIdentifier guidanceprice, validation predicates (
), warnings (
), versionHashModifier (null)
 userInfo {
}, attributeType 700 , attributeValueClassName NSString, defaultValue (null) is not in the GROUP BY)'
解决办法:

不用CoreData了,老子要换FMDB


问题8

在添加了一个库或者SDK之后,使用模拟器运行项目没有警告,没有错误,却一直报 You don’t have permission

原因:

存在两个都以上的info.plist文件,多半是刚刚添加的库或者SDK中也有一个info.plist问题

解决办法:

将导入的库或者SDK中的info.plist删除掉就好了。


问题9

改变UITabBar上面字体的颜色

解决办法:

[[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor], NSForegroundColorAttributeName, nil] forState:UIControlStateNormal];
    [[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor orangeColor], NSForegroundColorAttributeName, nil] forState:UIControlStateSelected];

问题10

使UITabBar上面的图片不渲染,保持图片原有的颜色

解决办法:
for (UITabBarItem * item  in self.tabBar.items) {
        item.selectedImage = [item.selectedImage imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
        item.image = [item.image imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]; 
    }

问题11

FMDB插入大量数据耗时太久

原因:

FMDB执行插入语句每次只能执行一条,多余如果有很多数据,必须要使用循环来一条一条插入,如果使用传统的方式去执行sql语句一条一条插入会非常慢,插入的本身并不会耗费很长的时间,但是每次插入都需要向数据库提交,如果有几万条就意外着需要提交几万次,所以这会变得非常非常慢。

解决办法:

使用事务来插入数据,等所有插入操作完成无误后,再一次性提交,这样不管插入多少条,都只会提交一次,这会极高的减少耗时。

  [self.dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        @try {
            for (NSString *sql in array) {
                if ( sql == nil ||[sql stringByReplacingOccurrencesOfString:@" " withString:@""].length == 0) {
                    continue;
                }
                bool ret = [db executeUpdate:sql];
                if (!ret) {
                    MBLog(@"SQL ERROR :%@",sql);
                }
            }
        } @catch (NSException *exception) {
            rollback[0] = YES;//回滚事务
            MBLog(@"exception %@",exception);
        } @finally {
            rollback[0] = NO;//提交事务
            MBLog(@"Transaction commit");
        }
    }];

问题12

获取一个view在window上面的位置

原因:

有很多是时候需要获取到一个View在window上面的位置,如果通过递归计算的方式,每次去取值都会比较麻烦。

解决办法:

其实系统的UIKit里面的API有提供获将某个视图位置转换为在另一个视图上面的位置


//返回在基准视图上面这个视图的实际位置,Return  CGRect
[取值的基准视图 convertRect:需要转换的视图 fromView:需要转换的视图父视图]


问题13

去除一个已弃用的低版本API警告

原因:

虽然这个API在现版本中已经被弃用,甚至有新的API替代,但是很多时候为了实现某一个功能却不得不用这些API,但是这些抛出来的警告会让我们这样的强迫症患者难以接受。

解决办法:

使用一个宏定义包裹已弃用的API代码,这个宏的作用是:【忽略了已弃用的声明】,这样警告就不会在编译的时候出现那么扎眼了

#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Wdeprecated-declarations"
//这里是抛出警告的已经弃用API代码
#pragma clang diagnostic pop


#pragma unused (foo)
//明确定义错误和警告
#error Whoa, buddy, you need to check for zero here!
#warning Dude, don't compare floating point numbers like this!


问题14

设置自定义的按钮文字在左,图片在右(默认文字在右,图片在左)

原因

按钮文字在左,图片在右是web的下拉选择按钮的风格,但是在移动端,有时候UI设计的就是抄袭web端的下拉菜单和选择按钮,这时候就需要对默认的自定义按钮文字和图片位置做一些调整了。

解决办法:

写一个UIButton的扩展

#import "UIButton+Other.h"

@implementation UIButton (Other)
/**
 设置文字在左,图片在右
 */
-(void)setTitleLeftImageRight{
    if (self) {
        
        NSParameterAssert(self.imageView.image);
        //设置按钮文字在左,图片在右
        CGSize imgsz = self.imageView.image.size;
        [self setTitleEdgeInsets:UIEdgeInsetsMake(0, -imgsz.width-12, 0, imgsz.height)];//这里设置12为文字和图片的间隙,可以调整
        [self setImageEdgeInsets:UIEdgeInsetsMake(0, self.titleLabel.bounds.size.width, 0, -self.titleLabel.bounds.size.width)];
    }
}
@end

//使用
[btn setTitleLeftImageRight];//文字在左,图片在右

PS

这种方式虽然可以使用按钮上面的图片和标题位置互换,但博主在实际测试中发现比较消耗性能(圆角,边框,产生了离屏渲染),如果一个页面存在几个以上这种按钮的时候,就会在进入页面时会产生零点几秒的卡顿,并且这部分是UI部分的调整,不好放在异步子线程中去执行,所以如果真影响到性能,还是建议使用背景图片代替。


问题15

Xcode中打印字典或数组中的中文内容,输出Unicode编码。

原因

apple在NSDictionary和NSArray的description方法中并未支持utf-8格式的编码,所有的中文都被输出为Unicode编码字符

解决办法:

重写系统NSDictionary和NSArray的description方法,在新的description将NSDictionary和NSArray原来的description返回的内容进行编码转换,返回可以在Xcode中输出中文的格式,推荐使用gitHub开源库ZXPUnicode
将三方分类文件拖入工程中,无需做任何更改即可在输出NSDictionary和NSArray对象时打印出中文。


问题16

在UITableView加载完成后获取到tableView的contentSize。

原因

tableView在加载数据的过程中是异步执行,如果直接在reloadData后面获取contentSize并不能拿到正确的值。

解决办法:

tableView虽然加载数据的过程是异步的,但是最后更新UI还是需要回到主线程,所以添加一个在加载方法后的主线程异步队列就能在tableView加载完数据后获取到tableView准确的contentSize。

[self.tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
     //刷新完成
     NSLog(@"contentSize:%lf",self.tableView.contentSize.height);
});

问题17

在ViewController的view上添加背景图片。

来由

如果需要需要将一个页面做成如下图所示的样子,就会需要在self.view上面加上一张背景图片,很多人的想法是用一个UIImageView加在self.view上面,然后把输入框按钮都在ImageView的上面,这样这样也能达到效果,但是其实有更好的做法;

解决办法:

将self.view.layer的contents赋值为CGImage,再将self.view.layer的背景颜色设置为透明,这样就可以直接将self.view显示一张图片;关键在于CALayer的contents属性

@property(strong) id contents;

提供图层内容的对象。可动画;此属性的默认值为nil。如果使用图层显示静态图像,则可以将此属性设置为包含要显示的图像。you can set this property to the CGImageRef containing the image you want to display. (In macOS 10.6 and later, you can also set the property to an NSImage object.) (来自苹果官方的文档说明)

由此可见,layer的contents属性就是用来显示东西的,并且这个东西可以是图像,类型需要是CGImageRef

    //获取图片路径
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"bg" ofType:@"png"];
    //
    UIImage *image = [UIImage imageWithContentsOfFile:filePath];
    self.view.layer.contents = (id)image.CGImage;
    self.view.layer.backgroundColor = [UIColor clearColor].CGColor;

另外说一说这里用的加载图片的方式使用的是imageWithContentsOfFile,而不是imageNamed,原因在于使用imageNamed方式加载图片会使图片一直存在缓存中,这个页面是一个登录页面,在登录过后就用不到这张图片了,所以不需要缓存这张图片,使用imageWithContentsOfFile方式就不会将这张图片一直存在(缓存)内存中,但是imageWithContentsOfFile不能获取到images.xcassets中的图片,那么这张图片就只能放在Bundle中了,虽然images.xcassets比较方便管理图片资源,但也这时候也并不是所有的图片都要放在images.xcassets中。


问题18

动态获取对象属性的名称、类型、值,并对类型进行识别。

    unsigned int propsCount;
    objc_property_t *props = class_copyPropertyList([obj class], &propsCount);
    for(int  i = 0;i < propsCount; i++)
    {
        objc_property_t  prop = props[i];
        NSString  *propName = [NSString stringWithUTF8String:property_getName(prop)];//获取属性名称
        const char * type = property_getAttributes(prop);//获取属性类型
        NSString *typeStr = [NSString stringWithFormat:@"%s",type];//类型转NSString
        id objValue = [obj valueForKey:propName];//获取值
        NSArray *typeArray = [typeStr componentsSeparatedByString:@","];
        
        if (typeArray.count>0) {
            //类型判断
            if ([typeArray[0] isEqualToString:@"T@\"NSString\""]) {//NSString
                
            }
            else if([typeArray[0] isEqualToString:@"Td"])//double
            {
                
            }
            else if([typeArray[0] isEqualToString:@"Tq"])//long
            {
                
            }
            else if([typeArray[0] isEqualToString:@"Tf"])//float
            {
                
            }
        }

    }
获取属性类型输出示例
T@"NSString",C,N,V_属性名称//NSString
Tq,N,V_属性名称//long
Td,N,V_属性名称//double
Tf,N,V_属性名称//float
附Objective-C类型编码

image


问题19

镂空视图。

如何把一个视图挖空一部分,做成一种镂空的效果?

需要用到的东西
  • layer.mask
  • UIBezierPath
首先

在window上面添加一个背景为半透明的视图(也可以加在其他视图上面)

     UIWindow *keyWindow = [UIApplication sharedApplication].keyWindow;
     UIView *backgroundView = [[UIView alloc] init];
     backgroundView.frame = keyWindow.bounds;
     backgroundView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.7];
     [keyWindow addSubview:backgroundView];

创建两个贝塞尔路径,两个路径画出来的内容需要有重叠部分

      // 创建一个全屏大的path
      UIBezierPath *path = [UIBezierPath bezierPathWithRect:keyWindow.bounds];
      // 创建一个圆形path
      UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:pt
      radius:ct.size.width*0.65
      startAngle:0
      endAngle:2 * M_PI
      clockwise:NO];

重点来了

 //叠加path,重叠的部分将会镂空透明
      [path appendPath:circlePath];

最后

//将path赋值给一个CAShapeLayer,再将这个CAShapeLayer赋值个之前创建的背景视图layer的mask
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    backgroundView.layer.mask = shapeLayer;

问题20

20、在扩展分类中添加属性

前段时间在做UIDelegate代码简化的时候,因为在原来的UIDelegate类中加了很多全局的属性,在简化的时候想移动到分类文件中去,结果发现直接将属性放入分类文件中会报错,而将属性放在主类文件中又违背了简化的初衷。

这样做:

扩展分类头文件中添加属性,写法与一般.h文件一致

//扩展分类的.h文件
@property(nonatomic,copy)NSString *str;

扩展分类.m文件需要重写属性的Getter,Setter方法

//一个固定的值代表这个属性的标记
static NSString *strKey = @"_strKey";

//getter方法
-(NSString *)str
{
    return objc_getAssociatedObject(self, &strKey);
}
//setter方法
-(void)setStr:(NSString *)str
{
    objc_setAssociatedObject(self, &strKey, str, OBJC_ASSOCIATION_RETAIN);
}

OBJC_ASSOCIATION_RETAIN

在setter方法中,objc_setAssociatedObject中有一个参数为OBJC_ASSOCIATION_RETAIN,这个参数代表什么呢?查看它的声明之后,可以发现

/**
 * Policies related to associative references.
 * These are options to objc_setAssociatedObject()
 */
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

没错,它是属性的修饰符,那么在传入这个参数时,跟.h文件中的属性声明修饰符对应即可。


问题21

计算源码行数

打开终端,进入工程的根目录,运行一下命令。

-name "*.m"代表着需要扫描的文件后缀,可根据自己的需求添加或删除。

find . "(" -name "*.m" -or -name "*.mm" -or -name "*.cpp" -or -name "*.h" -or -name "*.rss" ")" -print | xargs wc -l

未完待续...