美文网首页
小技巧四:iOS开发遇到的问题及解决办法

小技巧四:iOS开发遇到的问题及解决办法

作者: 小霍同学 | 来源:发表于2016-08-02 10:43 被阅读584次

    一.统计应用中代码的总行数

    每一个程序员都想在开发完成之后知道总共写了多少行的代码,这里有一个比较简单的方法。

    1、打开终端

    2、cd 进入项目根目录,这里的根目录可以是你这个项目的根目录,也可以是你想统计那个文件夹的根目录。这里以yykit为例子进行测试。

    3、输入命令 find . "(" -name "*.m" -or -name "*.mm" -or -name "*.cpp" -or -name "*.h" -or -name "*.rss" ")" -print | xargs wc -l  这是可以统计每个文件的行数和总行数

    find . -name "*.m" -or -name "*.h" -or -name "*.xib" -or -name "*.c" |xargs grep -v "^$"|wc -l  这是可以直接统计总共的行数,不显示每个文件的行数

    4、回车

    图01 图02

    说明:这里统计的行数包括系统头文件里面的代码,系统框架里面的代码,所以比你自己写的代码要多一点。如果想统计自己写了多少,只需要cd到自己代码的文件夹下,输入find . "(" -name "*.m" -or -name "*.mm" -or -name "*.cpp" -or -name "*.h" -or -name "*.rss" ")" -print | xargs wc -l,回车即可。

    二、release版本禁止输出NSLog内容

    前提:在XCode做开发调试时往往需要打印一些调试信息做debug用,大家知道当打印信息的地方多了之后在模拟器上跑可能不会有什么问题,因为模拟器用的是电脑的硬件但是当应用跑在设备上时这些输出语句会在很大程度上影响应用的性能,针对这种问题可以写一些宏来控制这些调试信息的输出。

    在release版本禁止输出NSLog内容

    因为NSLog的输出还是比较消耗系统资源的,而且输出的数据也可能会暴露出App里的保密数据,所以发布正式版时需要把这些输出全部屏蔽掉。

    我们可以在发布版本前先把所有NSLog语句注释掉,等以后要调试时,再取消这些注释,这实在是一件无趣而耗时的事!还好,还有更优雅的解决方法,就是在项目的prefix.pch文件里加入下面一段代码,加入后,NSLog就只在Debug下有输出,Release下不输出了。

    如何实现:

    在-Prefix.pch(pch全称是“precompiled header”,也就是预编译头文件,该文件里存放的工程中一些不常被修改的代码,比如常用的框架头文件,这样做的目的提高编译器编译速度。我们知道当我们修改一个工程中某个文件代码时候,编译器并不是重新编译所有所有文件,而是编译改动过文件的,假如pch中某个文件修改了,那么pch整个文件里包含的的其他文件也会重新编译一次,这样就会消耗大量时间,所以它里面添加的文件最好是是很少变动或不变动的头文件或者是预编译的代码片段;)文件中添加

    1#ifdef DEBUG

    2#define NSLog(...) NSLog(__VA_ARGS__)

    3#define debugMethod() NSLog(@"%s",__func__)

    4#else

    5#define NSLog(...)

    6#define debugMethod()

    7#endif

    上段代码的意思就是用宏指令做一个判断,如果DEBUG为真,则编译#ifdef到#endif宏定义,否则编译器就不编译;这个DEBUG在哪设置呢,在"Target > Build Settings >

    Preprocessor Macros > Debug"里有一个"DEBUG=1"。设置为Debug模式下,Product-->Scheme-->SchemeEdit Scheme

    设置Release,发布app版本的时候就不会打印了,提高了性能

    三、修改项目APP名字后,在真机运行报错:

    错误提醒:The provisioning profile specified in yourbuild settings (“haotian”) has an AppID of “com.basecom.vipose” which does notmatch your bundle identifier “com.baseus.iTemperature”.  Xcode can resolvethis issue by downloading a new provisioning profile from the Member Center.

    原因:是我在xxx-info.plist中Bundle display name的值(用来改变APP在左边显示的名字),修改后再在真机运行就报这个错误!,描述文件不对,设置好相应的描述文件就行。

    解决办法:参考地址:http://stackoverflow.com/questions/1760518/codesign-error-provisioning-profile-cannot-be-found-after-deleting-expired-prof

    这里所说的就是要通过修改你的项目的.xcodeproj文件来解决上述的错误。

    1.找到项目中的**.xcodeproj文件,点击右键,showpackage contents(打开包内容)。

    2.打开后找到project.pbxproj文件,用文本编辑器打开。其实就是右键,点击open就好了。

    3.打开这个文件后,按command+F,在这个文件中查找“PROVISIONING_PROFILE",找到和这个“

    四、UUID和UDID 区别

    UUID(Universally Unique IDentifier)是基于iOS设备上面某个单个的应用程序,只要用户没有完全删除应用程序,则这个UUID在用户使用该应用程序的时候一直保持不变。如果用户删除了这个应用程序,然后再重新安装,那么这个UUID已经发生了改变。通过调用[[UIDevice currentDevice]identifierForVendor];方法可以获取UUID。UUID不好的地方就是用户删除了你开发的程序以后,基本上你就不可能获取之前的数据了。

    UDID(Unique Device Identifier)是一串由40位16进制数组成的字符串,用以标识唯一的设备,现在想通过代码获取是不可能的了,如果你想看看你设备的UDID,可以通过iTunes来查看。苹果从iOS5开始就移除了通过代码访问UDID的权限,所以码农啊,想知道用户设备的UDID,是不行的喽。

    五、Xcode6+环境下,对iPhone5或iPhone5s模拟器,在iOS7或iOS7.1下运行,屏幕上下有黑边

    问题描述:

    Xcode6环境下,对iPhone5或iPhone5s模拟器,在iOS7或iOS7.1下运行,屏幕上下有黑边。在iOS8下没问题。

    问题分析:

    其实可以发现,不只是上下留白的问题,在这种状态下LaunchScreen其实根本没有加载。

    原因可想而知了。没有相对应的启动图。

    Xcode6在Xcode5的基础上,做出的一些调整,其实并不是向下兼容的。Xcode5在启动页通过images asset进行管理,而到了Xcode6,苹果引入了一种新的启动页机制:LaunchScreen.xib,而这种机制恰恰是不向下兼容的。Xcode6已经自动为iPhone6以上的模拟器强制使用iOS8+系统,iPhone 6 (7.1)这种模拟器是不存在的(至少我没有找到),因此对于iPhone 6以上的模拟器,不存在这个问题,对于iPhone 4s模拟器,在iOS7.1下也仅仅是没有加载LaunchScreen.xib,这种苹果最为古老的屏幕尺寸当然不存在留白的状况。但对于iPhone5/5s +

    iOS7/7.1,由于iOS7无法兼容LaunchScreen.xib这种机制,而又找不到对应的default-568h.png文件,问题自然就出现了。

    问题解决:

    在项目配置页,General下面有一栏App Icons and Launch Images,其中有一项Launch Images Source,如果你遇到了问题,那么旁边显示的可能是一个按钮,Use Asset

    Catalog,点一下然后确定就可以了,会在项目中的Images.xcassets中生成LaunchImage,就像Xcode5中那样。此时再运行程序,已经不会再有上下的黑边问题了。但为了更好的用户体验,做张图片放进去吧。当然,在iOS8中依然会加载LaunchScreen.xib,看来至少二者是可以共存的。

    六、iOS开发6-Xcode使用第三方字体

    Xcode自带中文字体:PingFang

    HK(香港)PingFang TC(繁体)PingFang SC(简体)。但是我们有时候还是需要其他的中文字体,这时候就要将字体库加入到工程中。

    目前网上有很多字体资源,这里推荐一个:http://font.knowsky.com/

    1、将字体加入到工程中

    检查一下字体是否被加入到了project中,可去Build Phases下的Copy Bundle Resources中找一下。如果没有,点击+来添加。

    01

    2、设置info.plist文件

    在info.plist文件中添加"Fonts provided by application"选项.选择+号,将字体文件名添加上。

    02 03

    3、使用字体

    1.NSMutableArray*familyNames = [UIFontfamilyNames].mutableCopy;

    2.[familyNamessortUsingSelector:@selector(compare:)];

    3.for(NSString*familyName in familyNames ){

    4.printf("Family: %s \n", [familyNameUTF8String] );

    5.NSArray*fontNames = [UIFontfontNamesForFamilyName:familyName];

    6.for(NSString*fontName in fontNames ){

    7.printf("\tFont: %s \n", [fontNameUTF8String] );

    8.}

    9.}//打印出所有的字体

    10./*

    11.

    12.Family: FZCaiYun-M09//方正彩云

    13.Font: FZCYK--GBK1-0

    14.Family: FZKai-Z03//方正楷体

    15.Font: FZKTK--GBK1-0

    16.Family: Bradley Gratis

    17.Font: BradleyGratis

    18.

    19.*/

    20.

    21.self.label1.text=@"hello";

    22.self.label2.text=@"你好";

    23.self.label3.text=@"你好";

    24.//使用字体

    25.self.label1.font=[UIFontfontWithName:@"BradleyGratis"size:20];

    26.self.label2.font=[UIFontfontWithName:@"FZCaiYun-M09"size:20];

    27.self.label3.font=[UIFontfontWithName:@"FZKTK--GBK1-0"size:20];

    4、效果

    04

    七、用instancetype代替id作返回类型有什么好处?

    苹果在iOS 8中全面使用instancetype代替id

    对于简易构造函数(convenience constructor),应该总是用instancetype。编译器不会自动将id转化为instancetype。id是通用对象,但如果你用instancetype,编译器就知道方法返回什么类型的对象。initializer的情况更复杂,当你输入

    - (id)initWithBar:(NSInteger)bar

    编译器会假设你输入了

    - (instancetype)initWithBar:(NSInteger)bar

    对于ARC而言,这是必须的。Clang Language Extensions的相关结果类型(Related result types)也讲到了这一点。也许别人会据此告诉你不必使用instancetype,但我建议你用它。下面解释我为什么如此建议。

    使用instancetype有三点好处:

    1、明确性。代码只做你让它做的事,而不是其他。

    2、程式化。你会养成好习惯,这些习惯在某些时候会很有用,而且肯定有用武之地。

    3、一致性。让代码可读性更好。

    明确性

    用instancetype代替id作为返回值的确没有技术上的好处。但这是因为编译器自动将id转化成了instancetype。你以为init返回的值类型是id,其实编译器返回了instancetype。

    这两行代码对于编译器来说是一样的:

    - (id)initWithBar:(NSInteger)bar; - (instancetype)initWithBar:(NSInteger)bar;

    但在你眼里,这两行代码却不同。你不该学着忽视它。

    模式化

    在使用init等方法时的确没有区别,但在定义简易构造函数时就区别了。

    这两行代码并不等价:

    + (id)fooWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar;

    如果用instancetype作为函数的返回类型,就不会出错。

    一致性:

    最后,想象把所有东西放到一起时的情景:你想要一个init方法和一个简易构造函数。

    如果你用id来作为init函数的返回类型,最终代码如下:

    - (id)initWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar;

    但如果你用instancetype,代码如下:

    - (instancetype)initWithBar:(NSInteger)bar; + (instancetype)fooWithBar:(NSInteger)bar;

    代码更加一致,可读性更强。它们返回相同的东西,这一点一目了然。

    结论

    除非你有意为旧编译器写代码,不然你在合适的时候都应该用instancetype。

    在写一条返回id的消息前,问自己:这个类返回实例吗?如果返回,用instancetype。

    肯定有需要返回id的时候,但你用instancetype的频率应该会更高。

    八 NSbundle

    bundle是一个目录,其中包含了程序会使用到的资源.这些资源包含了如图像,声音,编译好的代码,nib文件(用户也会把bundle称为plug-in).对应bundle,cocoa提供了类NSBundle.

    我们的程序是一个bundle.在Finder中,一个应用程序看上去和其他文件没有什么区别.但是实际上它是一个包含了nib文件,编译代码,以及其他资源的目录.我们把这个目录叫做程序的main bundle

    bundle中的有些资源可以本地化.例如,对于foo.nib,我们可以有两个版本:一个针对英语用户,一个针对法语用户.在bundle中就会有两个子目录:English.lproj和French.lproj,我们把各自版本的foo.nib文件放到其中.当程序需要加载foo.nib文件时,bundle会自动根据所设置的语言来加载.我们会在16章再详细讨论本地化

    通过使用下面的方法得到程序的main bundle

    NSBundle *myBundle= [NSBundle mainBundle];

    一般我们通过这种方法来得到bundle.如果你需要其他目录的资源,可以指定路径来取得bundle

    NSBundle*goodBundle;

    goodBundle =[NSBundle bundleWithPath:@"~/.myApp/Good.bundle"];

    一旦我们有了NSBundle对象,那么就可以访问其中的资源了

    // Extension is optional

    NSString *path =[goodBundle pathForImageResource:@"Mom"];

    NSImage *momPhoto= [[NSImage alloc] initWithContentsOfFile:path];

    bundle中可以包含一个库.如果我们从库得到一个class, bundle会连接库,并查找该类:

    Class newClass =[goodBundle classNamed:@"Rover"];

    id newInstance =[[newClass alloc] init];

    如果不知到class名,也可以通过查找主要类来取得

    Class aClass =[goodBundle principalClass];

    id anInstance =[[aClass alloc] init];

    可以看到, NSBundle有很多的用途.在这当中, NSBundle负责(在后台)加载nib文件.我们也可以不通过NSWindowController来加载nib文件,直接使用NSBundle:

    BOOL successful =[NSBundle loadNibNamed:@"About" owner:someObject];

    注意噢,我们指定了一个对象someObject作为nib的File's Owner

    使用initWithContentsOfFile时,文件路径的写法使用initWithContentsOfFile方法可以通过读取一个文件的内容来初始化对象。但文件的路径应该怎么确定呢?可以使用NSBundle的对象来获取。例如当前程序所在目录下有个文件re.xml,我们要将该文件的内容做为NSData的数据源来初始化一个NSData对象,可以用下面的方法来实现: 

    NSString *filePath= [[NSBundle mainBundle] pathForResouse:@"re" ofType:@"xml"];NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];

    读取plist中的内容:

    NSString *dataPath= [[NSBundle mainBundle] pathForResource:@"Data"ofType:@"plist"]; self.data = [NSArrayarrayWithContentsOfFile:dataPath];

    删除本地文件

    NSString * thePath=[selfgetUserDocumentDirectoryPath];

    NSMutableString *fullPath=[[[NSMutableString alloc]init]autorelease];

    [fullPathappendString:thePath];

    NSString *idString=[idArray objectAtIndex:indexPath.row];

    NSString *coverName=[NSString stringWithFormat:@"/%@.jpg",idString];

    [fullPathappendString:coverName];

    NSFileManager*defaultManager;

    defaultManager =[NSFileManager defaultManager];

    -(BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error BOOLboolValue=[defaultManager removeItemAtPath: fullPath error: nil];

    if (boolValue) {

    NSLog(@"removecover image ok");

    }

    -(NSString*)getUserDocumentDirectoryPath {

    NSArray* array =NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask,YES);

    if([array count])

    return [arrayobjectAtIndex: 0];

    else return@"";

    }                                                                                                                                      

    九、数组和链表的区别

    二者都属于一种数据结构

    从逻辑结构来看

    1.数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费;数组可以根据下标直接存取。

    2.链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项,非常繁琐)链表必须根据next指针找到下一个元素

    从内存存储来看

    1. (静态)数组从栈中分配空间,对于程序员方便快速,但是自由度小

    2.链表从堆中分配空间,自由度大但是申请管理比较麻烦

    从上面的比较可以看出,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反,如果需要经常插入和删除元素就需要用链表数据结构了。

    十、objectForKey:和valueForKey区别

    从NSDictionary取值的时候有两个方法,objectForKey:和valueForKey:,这两个方法具体有什么不同呢?

    先从NSDictionary文档中来看这两个方法的定义:

    objectForKey: returns the value associated

    with aKey, or nil if no value is associated with aKey.返回指定key的value,若没有这个key返回nil.

    valueForKey: returns the value associated

    with a given key.同样是返回指定key的value。

    直观上看这两个方法好像没有什么区别,但文档里valueForKey:有额外一点:

    If key does not start with “@”, invokesobjectForKey:. If key does start with “@”, strips the “@” and invokes [supervalueForKey:] with the rest of the key. via Discussion

    一般来说key可以是任意字符串组合,如果key不是以@符号开头,这时候valueForKey:等同于objectForKey:,如果是以@开头,去掉key里的@然后用剩下部分作为key执行[super valueForKey:]。

    比如:

    NSDictionary*dict = [NSDictionary dictionaryWithObject:@"theValue"

    forKey:@"theKey"];

    NSString*value1 = [dict objectForKey:@"theKey"];

    NSString*value2 = [dict valueForKey:@"theKey"];

    这时候value1和value2是一样的结果。如果是这样一个dict:

    NSDictionary*dict = [NSDictionary dictionaryWithObject:@"theValue"

    forKey:@"@theKey"];//注意这个key是以@开头

    NSString*value1 = [dict objectForKey:@"@theKey"];

    NSString*value2 = [dict valueForKey:@"@theKey"];

    value1可以正确取值,但是value2取值会直接crash掉,报错信息:

    Terminating app due to uncaught exception‘NSUnknownKeyException’, reason: ‘[<__NSCFDictionary 0x892fd80>valueForUndefinedKey:]: this class is not key value coding-compliant for thekey theKey.’

    这是因为valueForKey:是KVC(NSKeyValueCoding)的方法,在KVC里可以通过property同名字符串来获取对应的值。比如:

    @interface Person : NSObject

    @property (nonatomic, retain) NSString*name;

    @end

    Person *person= [[Person alloc] init];

    person.name = @"fannheyward";

    NSLog(@"name:%@",[person name]);

    NSLog(@"name:%@",[person valueForKey:@"name"]);

    valueForKey:取值是找和指定key同名的property

    accessor,没有的时候执行valueForUndefinedKey:,而valueForUndefinedKey:的默认实现是抛出NSUndefinedKeyException异常。

    回过头来看刚才crash的例子,[dict valueForKey:@"@theKey"];会把key里的@去掉,也就变成了[dict valueForKey:@"theKey"];,而dict不存在theKey这样的property,转而执行[dict

    valueForUndefinedKey:@"theKey"];,抛出NSUndefinedKeyException异常后crash掉。

    objectForKey:和valueForKey:在多数情况下都是一样的结果返回,但是如果key是以@开头,valueForKey:就成了一个大坑,建议在NSDictionary下只用objectForKey:来取值。

    相关文章

      网友评论

          本文标题:小技巧四:iOS开发遇到的问题及解决办法

          本文链接:https://www.haomeiwen.com/subject/vtxfsttx.html