Index

Xcode中的warning设置

Xcode中的warning警告设置(推荐)

转载请注明出处: http://elijahdou.github.io/

作为一名有节操的程序猿,应该有严谨的coding态度。Xcode的warning设置,能给我们很好的帮助,强制约束我们严谨coding:0 warning 0 error。下面直接上干货。



扯外篇,本blog来自这里并与时俱进了一下下。 以下几条在常规的iOS开发中,几乎没用,所以关闭。

  • 没用的,如 “‘Effective C++’“ Violations 等,不写C++代码的话,用不到,所以关闭。
  • 没有帮助的,如 “Four Character Literals” 等,这个选项是涉及四字节编码类型 如OSType,弊大于利,所以关闭。
  • 不可修复的,如 “Multiple Definition Types for Selector” 等,Cocoa抛出这类警告会遍布它所有的头文件,我们不能fix这种情况。


正篇,我们开启以下警告,会使我们的项目更清爽,并帮我们排除潜在的bug。


Check Switch Statements

当一个switch语句的index索引值为一个枚举类型时,如果这时缺少了一个或者几个枚举值,会抛出此类警告。
solution:使用default语句可以fix it。 但是在编程规范我中,我们处理枚举类型的switch,我们推荐不使用default。 这样可以提示我们漏掉的情况,并且以后对枚举值做扩展时,也会提示我们处理新的枚举值。所以推荐的解决方案是:写全每个枚举值的处理,并且不使用default。

Hidden Local Variables

当一个局部变量shadow另外一局部变量、参数、全局变量或者内建的函数名时,会抛出次警告。
solution:命名时注意规范,这种情况,局部变量会屏蔽掉上一级的同名变量,这可能不是我们想要的。还有一种情况就是命名一个变量名为index,index还是标准C库函数,这种代码myArray[index]不要不要的。一旦index变量声明失败,运行结果就全凌乱了。so,never name a variable index

Implicit Conversion to 32 Bit Type

当一个数据值隐式地从64位转换为32位时,会抛出此警告
solution:当想要将一份旧代码转到一个64位的平台上并正确运行,开启这警告很有用。它会提示我们潜在的指针错误。

Initializer Not Fully Bracketed

当初始化不对等时,会抛出这个警告。
   int a[2][2] = { 0, 1, 2, 3 }; // wrong way
   int b[2][2] = { { 0, 1 }, { 2, 3 } }; // right way
solution:这个警告也适用于结构体,如NSRect:
   NSRect warns = { 0.0f, 0.0f, 640.0f, 480.0f };
   NSRect doesNotWarn = { {0.0f, 0.0f}, {640.0f, 480.0f} };
tips:推荐使用NSZeroPoint替代 {0.0f, 0.0f}

Mismatched Return Type

当返回值类型与函数声明的返回值类型不匹配时,会抛出警告
solution:没啥好说的

Missing Braces and Parentheses

当缺少必要的大括号或者圆括号时,会使代码结构和层级混乱,会抛出此警告
solution:使用if else一定要加大括号,不要吝啬使用大括号和圆括号。

Missing Fields in Structure Initializers

当结构体初始化 缺少了若干域时,会抛出此警告
solution:举例说明:
    struct s { int f, g, h; }; // bad way
    struct s x = { 3, 4 }; 
    struct s { int f, g, h; };  // right way
    struct s x = { .f = 3, .g = 4 }; // or
    struct s x = {3, 4, 5};

Missing Newline At End Of File

与diff文件的清洁度有关,在编译时分隔不同的头文件。
solution:每个头文件末尾加上空行。

Sign Comparison

当符号数和无符号数直接比较时,可能会出现错误的结果。
solution:避免这种情况,不能避免时,把符号书转化成无符号数。

Typecheck Calls to printf/scanf

打开这个警告会检查printf/scanf的入参合法性,如NSArray *array = [NSArray arrayWithObjects:@"foo", @"bar"]; 就会抛出警告。
solution:没啥好说的,开启这个警告 printf、NSLog 和 stringWithFormat都会检查入参。

Undeclared Selector

当"@selector(...)"表达式引用一个未声明的selector时,会抛出此异常。 未声明就是指没有对应的该方法名 在@interface 或 @protocol中显式声明 或者在 @implementation中隐式声明。
solution:没啥好说的,开启这个警告可以避免KVC, KVO, KVV 或者 Bindings method传入一个错误的key。

Unused Functions

当一个静态函数只有声明 没有定义 或者 一个non-inline静态函数没有被使用时,会抛出这个警告。
solution:建议将模块内的 对外不可见的函数声明为static。

Unused Labels

Unused Values

Unused Variables

以上三条遵循规范原则:“code you don’t have is code you don’t have to debug”,可以使代码简洁。

Treat Warnings as Errors

警告意味着潜在的bug,开启这个code的困难模式,能有效的降低这个风险,实在不能避免的warning,使用#pragma吧。

Analyze during ‘build’

在build时打开静态分析,Clang Static Analyzer能查找bug,记得要排除静态分析的警告。除非某些文件分析的时间很长,会影响编译和开发速度,否则默认打开这个选项。

iOS开发中涉及的所有warning

在这里Clang Warning找到了一篇Clang中所有warning的总结,内有解释。

这样在处理一下警告就有了指导文件了。还可以使用#pragma屏蔽某种类型的警告,如:

   #pragma clang diagnostic push
   #pragma clang diagnostic ignored"-Wdeprecated-declarations"
    //写在这个中间的代码,都不会被编译器提示-Wdeprecated-declarations类型的警告
    dispatch_queue_tcurrentQueue =dispatch_get_current_queue();
   #pragma clang diagnostic pop

具体的使用方法,可以参考怎么去掉Xcode工程中的某种类型的警告谈谈Objective-C的警告复用 Xcode 警告设置

iOS开发ARC内存管理(精)

转载自iOS开发ARC内存管理技术要点. 虽然里面的大部分知识自己都已经了解或者掌握了,但是这么系统的资料不能放过,哈哈。

本文来源于我个人的ARC学习笔记,旨在通过简明扼要的方式总结出iOS开发中ARC(Automatic Reference Counting,自动引用计数)内存管理技术的要点,所以不会涉及全部细节。这篇文章不是一篇标准的ARC使用教程,并假定读者已经对ARC有了一定了解和使用经验。详细的关于ARC的信息请参见苹果的官方文档与网上的其他教程:

本文的主要内容:

  • ARC的本质
  • ARC的开启与关闭
  • ARC的修饰符
  • ARC与Block
  • ARC与Toll-Free Bridging

ARC的本质

ARC是编译器(时)特性,而不是运行时特性,更不是垃圾回收器(GC)。

Automatic Reference Counting (ARC) is a compiler-level feature that simplifies the process of managing object lifetimes (memory management) in Cocoa applications.

ARC只是相对于MRC(Manual Reference Counting或称为非ARC,下文中我们会一直使用MRC来指代非ARC的管理方式)的一次改进,但它和之前的技术本质上没有区别。具体信息可以参考ARC编译器官方文档。

ARC的开启与关闭


不同于XCode4可以在创建工程时选择关闭ARC,XCode5在创建的工程是默认开启ARC,没有可以关闭ARC的选项。

如果需要对特定文件开启或关闭ARC,可以在工程选项中选择Targets -> Compile Phases -> Compile Sources,在里面找到对应文件,添加flag:

  • 打开ARC:-fobjc-arc
  • 关闭ARC:-fno-objc-arc

如图:

arc config

ARC的修饰符


ARC主要提供了4种修饰符,他们分别是:__strong,__weak,__autoreleasing,__unsafe_unretained。

__strong

表示引用为强引用。对应在定义property时的”strong”。所有对象只有当没有任何一个强引用指向时,才会被释放。

注意:如果在声明引用时不加修饰符,那么引用将默认是强引用。当需要释放强引用指向的对象时,需要将强引用置nil。

__weak

表示引用为弱引用。对应在定义property时用的”weak”。弱引用不会影响对象的释放,即只要对象没有任何强引用指向,即使有100个弱引用对象指向也没用,该对象依然会被释放。不过好在,对象在被释放的同时,指向它的弱引用会自动被置nil,这个技术叫zeroing weak pointer。这样有效得防止无效指针、野指针的产生。__weak一般用在delegate关系中防止循环引用或者用来修饰指向由Interface Builder编辑与生成的UI控件。

__autoreleasing

表示在autorelease pool中自动释放对象的引用,和MRC时代autorelease的用法相同。定义property时不能使用这个修饰符,任何一个对象的property都不应该是autorelease型的。

一个常见的误解是,在ARC中没有autorelease,因为这样一个“自动释放”看起来好像有点多余。这个误解可能源自于将ARC的“自动”和autorelease“自动”的混淆。其实你只要看一下每个iOS App的main.m文件就能知道,autorelease不仅好好的存在着,并且变得更fashion了:不需要再手工被创建,也不需要再显式得调用[drain]方法释放内存池。

autoreleasepool in main

以下两行代码的意义是相同的。

NSString *str = [[[NSString alloc] initWithFormat:@"hehe"] autorelease]; // MRC NSString *__autoreleasing str = [[NSString alloc] initWithFormat:@"hehe"]; // ARC

这里关于autoreleasepool就不做展开了,详细地信息可以参考官方文档或者其他文章。

__autoreleasing在ARC中主要用在参数传递返回值(out-parameters)和引用传递参数(pass-by-reference)的情况下。

__autoreleasing is used to denote arguments that are passed by reference (id *) and are autoreleased on return.

比如常用的NSError的使用:

NSError *__autoreleasing error; if (![data writeToFile:filename options:NSDataWritingAtomic error:&error]) {   NSLog(@"Error: %@", error); }

####(在上面的writeToFile方法中error参数的类型为(NSError *__autoreleasing *))

注意,如果你的error定义为了strong型,那么,编译器会帮你隐式地做如下事情,保证最终传入函数的参数依然是个__autoreleasing类型的引用。

NSError *error; 
NSError *__autoreleasing tempError = error; // 编译器添加 
if (![data writeToFile:filename options:NSDataWritingAtomic error:&tempError]) 
{ 
  error = tempError; // 编译器添加 
  NSLog(@"Error: %@", error); 
}

所以为了提高效率,避免这种情况,我们一般在定义error的时候将其(老老实实地=。=)声明为__autoreleasing类型的:

NSError *__autoreleasing error;

在这里,加上__autoreleasing之后,相当于在MRC中对返回值error做了如下事情:

*error = [[[NSError alloc] init] autorelease];

error指向的对象在创建出来后,被放入到了autoreleasing pool中,等待使用结束后的自动释放,函数外error的使用者并不需要关心error指向对象的释放。

另外一点,在ARC中,所有这种指针的指针 (NSError **)的函数参数如果不加修饰符,编译器会默认将他们认定为__autoreleasing类型。

比如下面的两段代码是等同的:

- (NSString *)doSomething:(NSNumber **)value
{
        // do something  
}
- (NSString *)doSomething:(NSNumber * __autoreleasing *)value
{
        // do something  
}

除非你显式得给value声明了__strong,否则value默认就是__autoreleasing的。

最后一点,某些类的方法会隐式地使用自己的autorelease pool,在这种时候使用__autoreleasing类型要特别小心。

比如NSDictionary的[enumerateKeysAndObjectsUsingBlock]方法:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          // do stuff  
          if (there is some error && error != nil)
          {
                *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
          }

    }];
}

会隐式地创建一个autorelease pool,上面代码实际类似于:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
    [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop){

          @autoreleasepool  // 被隐式创建
      {
              if (there is some error && error != nil)
              {
                    *error = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil];
              }
          }
    }];

    // *error 在这里已经被dict的做枚举遍历时创建的autorelease pool释放掉了 :(  
}    

为了能够正常的使用*error,我们需要一个strong型的临时引用,在dict的枚举Block中是用这个临时引用,保证引用指向的对象不会在出了dict的枚举Block后被释放,正确的方式如下:

- (void)loopThroughDictionary:(NSDictionary *)dict error:(NSError **)error
{
  __block NSError* tempError; // 加__block保证可以在Block内被修改  
  [dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop)
  { 
    if (there is some error) 
    { 
      *tempError = [NSError errorWithDomain:@"MyError" code:1 userInfo:nil]; 
    }  

  }] 

  if (error != nil) 
  { 
    *error = tempError; 
  } 
} 

__unsafe_unretained

ARC是在iOS 5引入的,而这个修饰符主要是为了在ARC刚发布时兼容iOS 4以及版本更低的设备,因为这些版本的设备没有weak pointer system,简单的理解这个系统就是我们上面讲weak时提到的,能够在weak引用指向对象被释放后,把引用值自动设为nil的系统。这个修饰符在定义property时对应的是”unsafe_unretained”,实际可以将它理解为MRC时代的assign:纯粹只是将引用指向对象,没有任何额外的操作,在指向对象被释放时依然原原本本地指向原来被释放的对象(所在的内存区域)。所以非常不安全。

现在可以完全忽略掉这个修饰符了,因为iOS 4早已退出历史舞台很多年。

使用修饰符的正确姿势(方式=。=)

这可能是很多人都不知道的一个问题,包括之前的我,但却是一个特别要注意的问题。

苹果的文档中明确地写道:

`You should decorate variables correctly. When using qualifiers in an object variable declaration,

the correct format is:

ClassName * qualifier variableName;`

按照这个说明,要定义一个weak型的NSString引用,它的写法应该是:

NSString * __weak str = @"hehe"; // 正确!

而不应该是:

__weak NSString *str = @"hehe"; // 错误!

我相信很多人都和我一样,从开始用ARC就一直用上面那种错误的写法。

那这里就有疑问了,既然文档说是错误的,为啥编译器不报错呢?文档又解释道:

Other variants are technically incorrect but are “forgiven” by the compiler. To understand the issue, seehttp://cdecl.org/.

好吧,看来是苹果考虑到很多人会用错,所以在编译器这边贴心地帮我们忽略并处理掉了这个错误:)虽然不报错,但是我们还是应该按照正确的方式去使用这些修饰符,如果你以前也常常用错误的写法,那看到这里记得以后不要这么写了,哪天编译器怒了,再不支持错误的写法,就要郁闷了。

栈中指针默认值为nil

无论是被strong,weak还是autoreleasing修饰,声明在栈中的指针默认值都会是nil。所有这类型的指针不用再初始化的时候置nil了。虽然好习惯是最重要的,但是这个特性更加降低了“野指针”出现的可能性。

在ARC中,以下代码会输出null而不是crash:

- (void)myMethod 
{
    NSString *name;
    NSLog(@"name: %@", name);
}

ARC与Block


在MRC时代,Block会隐式地对进入其作用域内的对象(或者说被Block捕获的指针指向的对象)加retain,来确保Block使用到该对象时,能够正确的访问。

这件事情在下面代码展示的情况中要更加额外小心。

MyViewController *myController = [[MyViewController alloc] init…];

// 隐式地调用[myController retain];造成循环引用
myController.completionHandler =  ^(NSInteger result) {
   [myController dismissViewControllerAnimated:YES completion:nil];
};

[self presentViewController:myController animated:YES completion:^{
   [myController release]; // 注意,这里调用[myController release];是在MRC中的一个常规写法,并不能解决上面循环引用的问题
}];

在这段代码中,myController的completionHandler调用了myController的方法[dismissViewController…],这时completionHandler会对myController做retain操作。而我们知道,myController对completionHandler也至少有一个retain(一般准确讲是copy),这时就出现了在内存管理中最糟糕的情况:循环引用!简单点说就是:myController retain了completionHandler,而completionHandler也retain了myController。循环引用导致了myController和completionHandler最终都不能被释放。我们在delegate关系中,对delegate指针用weak就是为了避免这种问题。

不过好在,编译器会及时地给我们一个警告,提醒我们可能会发生这类型的问题:

warning

对这种情况,我们一般用如下方法解决:给要进入Block的指针加一个__block修饰符。

这个__block在MRC时代有两个作用:

  • 说明变量可改
  • 说明指针指向的对象不做这个隐式的retain操作

一个变量如果不加__block,是不能在Block里面修改的,不过这里有一个例外:static的变量和全局变量不需要加__block就可以在Block中修改。

使用这种方法,我们对代码做出修改,解决了循环引用的问题:

MyViewController * __block myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
};
//之后正常的release或者retain

在ARC引入后,没有了retain和release等操作,情况也发生了改变:在任何情况下,__block修饰符的作用只有上面的第一条:说明变量可改。即使加上了__block修饰符,一个被block捕获的强引用也依然是一个强引用。这样在ARC下,如果我们还按照MRC下的写法,completionHandler对myController有一个强引用,而myController对completionHandler有一个强引用,这依然是循环引用,没有解决问题:

于是我们还需要对原代码做修改。简单的情况我们可以这样写:

__block MyViewController * myController = [[MyViewController alloc] init…];
// ...
myController.completionHandler =  ^(NSInteger result) {
    [myController dismissViewControllerAnimated:YES completion:nil];
    myController = nil;  // 注意这里,保证了block结束myController强引用的解除
};

在completionHandler之后将myController指针置nil,保证了completionHandler对myController强引用的解除,不过也同时解除了myController对myController对象的强引用。这种方法过于简单粗暴了,在大多数情况下,我们有更好的方法。

这个更好的方法就是使用weak。(或者为了考虑iOS4的兼容性用unsafe_unretained,具体用法和weak相同,考虑到现在iOS4设备可能已经绝迹了,这里就不讲这个方法了)(关于这个方法的本质我们后面会谈到)

为了保证completionHandler这个Block对myController没有强引用,我们可以定义一个临时的弱引用weakMyViewController来指向原myController的对象,并把这个弱引用传入到Block内,这样就保证了Block对myController持有的是一个弱引用,而不是一个强引用。如此,我们继续修改代码:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyViewController = myController;
myController.completionHandler =  ^(NSInteger result) {
    [weakMyViewController dismissViewControllerAnimated:YES completion:nil];
};

这样循环引用的问题就解决了,但是却不幸地引入了一个新的问题:由于传入completionHandler的是一个弱引用,那么当myController指向的对象在completionHandler被调用前释放,那么completionHandler就不能正常的运作了。在一般的单线程环境中,这种问题出现的可能性不大,但是到了多线程环境,就很不好说了,所以我们需要继续完善这个方法。

为了保证在Block内能够访问到正确的myController,我们在block内新定义一个强引用strongMyController来指向weakMyController指向的对象,这样多了一个强引用,就能保证这个myController对象不会在completionHandler被调用前释放掉了。于是,我们对代码再次做出修改:

MyViewController *myController = [[MyViewController alloc] init…];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler =  ^(NSInteger result) {
    MyViewController *strongMyController = weakMyController;

  if (strongMyController) {
        // ...
        [strongMyController dismissViewControllerAnimated:YES completion:nil];
        // ...
    }
    else {
        // Probably nothing...
    }
};

到此,一个完善的解决方案就完成了:)

官方文档对这个问题的说明到这里就结束了,但是可能很多朋友会有疑问,不是说不希望Block对原myController对象增加强引用么,这里为啥堂而皇之地在Block内新定义了一个强引用,这个强引用不会造成循环引用么?理解这个问题的关键在于理解被Block捕获的引用和在Block内定义的引用的区别。为了搞得明白这个问题,这里需要了解一些Block的实现原理,但由于篇幅的缘故,本文在这里就不展开了,详细的内容可以参考其他的文章,这里特别推荐唐巧的文章和另外2位作者的博文:这个和这个,讲的都比较清楚。

这里假设大家已经对Block的实现原理有所了解了。我们就直入主题了!注意前方高能(=。=)

为了更清楚地说明问题,这里用一个简单的程序举例。比如我们有如下程序:

#include <stdio.h>

int main()
{
    int b = 10;
    
    int *a = &b;
    
    void (^blockFunc)() = ^(){
    
        int *c = a;

    };
    
    blockFunc();
    
    return 1;
}

程序中,同为int型的指针,a是被Block捕获的变量,而c是在Block内定义的变量。我们用clang -rewrite-objc处理后,可以看到如下代码:

原main函数:

int main()
{
    int b = 10;

    int *a = &b;

    void (*blockFunc)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);

    ((void (*)(__block_impl *))((__block_impl *)blockFunc)->FuncPtr)((__block_impl *)blockFunc);

    return 1;
}

Block的结构:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  int *a; // 被捕获的引用 a 出现在了block的结构体里面
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

实际执行的函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *a = __cself->a; // bound by copy


        int *c = a; // 在block中声明的引用 c 在函数中声明,存在于函数栈上

    }

我们可以清楚得看到,a和c存在的位置完全不同,如果Block存在于堆上(在ARC下Block默认在堆上),那么a作为Block结构体的一个成员,也自然会存在于堆上,而c无论如何,永远位于Block内实际执行代码的函数栈内。这也导致了两个变量生命周期的完全不同:c在Block的函数运行完毕,即会被释放,而a呢,只有在Block被从堆上释放的时候才会释放。

回到我们的MyViewController的例子中,同上理,如果我们直接让Block捕获我们的myController引用,那么这个引用会被复制后(引用类型也会被复制)作为Block的成员变量存在于其所在的堆空间中,也就是为Block增加了一个指向myController对象的强引用,这就是造成循环引用的本质原因。对于MyViewController的例子,Block的结构体可以理解是这个样子:(准确的结构体肯定和以下这个有区别,但也肯定是如下这种形式:)

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  MyViewController * __strong myController;  // 被捕获的强引用myController
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

而反观我们给Block传入一个弱引用weakMyController,这时我们Block的结构:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  
  MyViewController * __weak weakMyController;  // 被捕获的弱引用weakMyController
  
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

再看在Block内声明的强引用strongMyController,它虽然是强引用,但存在于函数栈中,在函数执行期间,它一直存在,所以myController对象也一直存在,但是当函数执行完毕,strongMyController即被销毁,于是它对myController对象的强引用也被解除,这时Block对myController对象就不存在强引用关系了!加入了strongMyController的函数大体会是这个样子:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  MyViewController * __strong strongMyController = __cself->weakMyController; 

    // ....

    }

综上所述,在ARC下(在MRC下会略有不同),Block捕获的引用和Block内声明的引用无论是存在空间与生命周期都是截然不同的,也正是这种不同,造成了我们对他们使用方式的区别。

以上就解释了之前提到的所有问题,希望大家能看明白:

好的,最后再提一点,在ARC中,对Block捕获对象的内存管理已经简化了很多,由于没有了retain和release等操作,实际只需要考虑循环引用的问题就行了。比如下面这种,是没有内存泄露的问题的:

TestObject *aObject = [[TestObject alloc] init];
    
aObject.name = @"hehe";

self.aBlock = ^(){
    
    NSLog(@"aObject's name = %@",aObject.name);
        
};

我们上面提到的解决方案,只是针对Block产生循环引用的问题,而不是说所有的Block捕获引用都要这么处理,一定要注意!

ARC与Toll-Free Bridging


There are a number of data types in the Core Foundation framework and the Foundation framework that can be used interchangeably. This capability, called toll-free bridging, means that you can use the same data type as the parameter to a Core Foundation function call or as the receiver of an Objective-C message.

Toll-Free Briding保证了在程序中,可以方便和谐的使用Core Foundation类型的对象和Objective-C类型的对象。详细的内容可参考官方文档。以下是官方文档中给出的一些例子:

NSLocale *gbNSLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_GB"];
CFLocaleRef gbCFLocale = (CFLocaleRef) gbNSLocale;
CFStringRef cfIdentifier = CFLocaleGetIdentifier (gbCFLocale);
NSLog(@"cfIdentifier: %@", (NSString *)cfIdentifier);
// logs: "cfIdentifier: en_GB"
CFRelease((CFLocaleRef) gbNSLocale);
 
CFLocaleRef myCFLocale = CFLocaleCopyCurrent();
NSLocale * myNSLocale = (NSLocale *) myCFLocale;
[myNSLocale autorelease];
NSString *nsIdentifier = [myNSLocale localeIdentifier];
CFShow((CFStringRef) [@"nsIdentifier: " stringByAppendingString:nsIdentifier]);
// logs identifier for current locale

在MRC时代,由于Objective-C类型的对象和Core Foundation类型的对象都是相同的release和retain操作规则,所以Toll-Free Bridging的使用比较简单,但是自从ARC加入后,Objective-C类型的对象内存管理规则改变了,而Core Foundation依然是之前的机制,换句话说,Core Foundation不支持ARC。

这个时候就必须要要考虑一个问题了,在做Core Foundation与Objective-C类型转换的时候,用哪一种规则来管理对象的内存。显然,对于同一个对象,我们不能够同时用两种规则来管理,所以这里就必须要确定一件事情:哪些对象用Objective-C(也就是ARC)的规则,哪些对象用Core Foundation的规则(也就是MRC)的规则。或者说要确定对象类型转换了之后,内存管理的ownership的改变。

If you cast between Objective-C and Core Foundation-style objects, you need to tell the compiler about the ownership semantics of the object using either a cast (defined in objc/runtime.h) or a Core Foundation-style macro (defined inNSObject.h)

于是苹果在引入ARC之后对Toll-Free Bridging的操作也加入了对应的方法与修饰符,用来指明用哪种规则管理内存,或者说是内存管理权的归属。

这些方法和修饰符分别是:

__bridge(修饰符)

只是声明类型转变,但是不做内存管理规则的转变。

比如:

CFStringRef s1 = (__bridge CFStringRef) [[NSString alloc] initWithFormat:@"Hello, %@!", name];

只是做了NSString到CFStringRef的转化,但管理规则未变,依然要用Objective-C类型的ARC来管理s1,你不能用CFRelease()去释放s1。

__bridge_retained(修饰符) or CFBridgingRetain(函数)

表示将指针类型转变的同时,将内存管理的责任由原来的Objective-C交给Core Foundation来处理,也就是,将ARC转变为MRC。

比如,还是上面那个例子

NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name];
CFStringRef s2 = (__bridge_retained CFStringRef)s1;
// do something with s2
//...
CFRelease(s2); // 注意要在使用结束后加这个

我们在第二行做了转化,这时内存管理规则由ARC变为了MRC,我们需要手动的来管理s2的内存,而对于s1,我们即使将其置为nil,也不能释放内存。

等同的,我们的程序也可以写成:

NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name];
CFStringRef s2 = (CFStringRef)CFBridgingRetain(s1);
// do something with s2
//...
CFRelease(s2); // 注意要在使用结束后加这个

__bridge_transfer(修饰符) or CFBridgingRelease(函数)

这个修饰符和函数的功能和上面那个__bridge_retained相反,它表示将管理的责任由Core Foundation转交给Objective-C,即将管理方式由MRC转变为ARC。

比如:

CFStringRef result = CFURLCreateStringByAddingPercentEscapes(. . .);
NSString *s = (__bridge_transfer NSString *)result;
//or NSString *s = (NSString *)CFBridgingRelease(result);
return s;

这里我们将result的管理责任交给了ARC来处理,我们就不需要再显式地将CFRelease()了。

对了,这里你可能会注意到一个细节,和ARC中那个4个主要的修饰符(__strong,__weak,…)不同,这里修饰符的位置是放在类型前面的,虽然官方文档中没有说明,但看官方的头文件可以知道。小伙伴们,记得别把位置写错哦:)

文章中如果有任何错误或者问题,可以在下面留言或者给我发信,期待你们的回复。

原创文章,转载请注明出处,谢谢:

iOS Class Guard github译文及使用经验总结

iOS Class Guard github 译文

iOS-Class-Guard是一个用于OC类、协议、属性和方法名混淆的命令行工具。它是class-dump的扩展。这个工具会生成一个symbol table,这个table在编译期间会包含进工程中。iOS-Class-Guard能有效的隐藏绝大多数的类、协议、方法、属性和 实例变量 名。

iOS-Class-Guard不是应用安全的最终解决方案,但是它绝对能让攻击者更难读懂你的程序。


iOS-Class-Guard会加大代码分析和runtime检查的难度,这个工具可以认为是一个简单基础的混淆方法。由于OC的架构决定了iOS应用程序的剖析相当简单,check out一下链接就知晓了:


工作原理

这个工具只对应用程序的编译版本起作用(工具的脚本文件会首先编译项目源码,得到应用文件,之后使用class-dump处理应用文件)。它会读取Mach—O对象文件的OC部分(工具只对mach-o和fat类型的文件有用,如果是想混淆自定义的静态,需要稍微转换一下策略),并解析其中所有的类、属性、方法、实例变量,之后添加所有的symbols到列表中。然后它会读取所有的依赖框架,并做相同的解析OC代码结构的处理,不同的是,此时是把symbol添加到禁止列表中。之后 所有的并且不在禁止列表中的symbols会被混淆处理。每一个symbol由随机生成的 子母和数字 组成。每次执行混淆操作,都会生成一个唯一的symbol map。之后这个map会格式化成一个C的宏定义 头文件,并包含到 .pch文件中。 然后,它会找出XIB和storyboard并更新里面的名字(即IB文件也会被有效的混淆掉)。 这个工具还会查找工程内的xcdatamodel文件并添加其中的类和属性名到禁止列表。 在编译期间内,所有定义在头文件内的symbol都会用对应的生成的不同的符号替换并编译。
iOS-Class-Guard也提供了对cocoapod库的混淆。这个工具会 根据用户提供的pods路径 自动遍历所有列出的target 并 查找 .xcconfig文件和要修改的预编译头文件路径。然后添加预先生成的头文件到库 .pch头文件,并更新target的.xcconfig文件中的头文件的search path参数。
iOS-Class-Guard还会生成一个json格式的symbol映射。这个映射可以用来处理crash报告是的逆向处理。注意 iOS-Class-Guard不混淆system symbol,所有如果在自定义类中的某些属性和方法与system symbol有相同的名字,则不会被混淆。

安装

如果没有安装brew 先安装之,在终端内执行这 curl -LsSf http://github.com/mxcl/homebrew/tarball/master | sudo tar xvz -C/usr/local --strip 1,若已经安装则跳过,直接执行 sudo brew install https://raw.githubusercontent.com/Polidea/homebrew/ios-class-guard/Library/Formula/ios-class-guard.rb. 工具的安装目录为/usr/local/bin。若刚想安装最新的版本可执行brew install --HEAD https://raw.githubusercontent.com/Polidea/homebrew/ios-class-guard/Library/Formula/ios-class-guard.rb

用法

集成iOS-Class-Guard到项目中需要以下几步:
  1. 下载 obfuscate_project 到工程的根目录。 终端内执行 curl -o obfuscate_project https://raw.githubusercontent.com/Polidea/ios-class-guard/master/contrib/obfuscate_project && chmod +x obfuscate_project
  2. 更新 obfuscate_project 内的project file、scheme 和 configuration name
  3. 执行 bash obfuscate_project。每一次release都应该执行一次该操作。 保存包含symbol映射的json文件 以便于在crash时能逆向得到原来的symbol
  4. 用Xcode或其他工具 Build、test archive工程
上面是基本步骤,也可以添加 额外的target,这些target会在编译期间自动 重新生成symbol map。


命令行选项

`ios-class-guard 0.7 (64 bit)

Usage: ios-class-guard [options]

where options are:

    -F <class>     specify class filter for symbols obfuscator (also protocol))
    
    -i <symbol>    ignore obfuscation of specific symbol)
    
    --arch <arch>  choose a specific architecture from a universal binary (ppc, ppc64, i386, x86_64, armv6, armv7, armv7s, arm64)
    
    --list-arches  list the arches in the file, then exit
    
    --sdk-ios      specify iOS SDK version (will look for /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk  or /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS<version>.sdk)
    
    --sdk-mac      specify Mac OS X version (will look for /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX<version>.sdk  or /Developer/SDKs/MacOSX<version>.sdk)
    
    --sdk-root     specify the full SDK root path (or use --sdk-ios/--sdk-mac for a shortcut)
    
    -X <directory> base directory for XIB, storyboards (will be searched recursively)
    
    -P <path>      path to project.pbxproj of Pods project (located inside Pods.xcodeproj)
    
    -O <path>      path to file where obfuscated symbols are written
    
    -m <path>      path to symbol file map (default value symbols.json)
    
    -c <path>      path to symbolicated crash dump`

要熟悉以下几个命令


输出头文件路径

ios-class-guard输出头文件路径,使用 -O参数, 如 -O SWTableView/symbols.h

class过滤

ios-class-guard能够过滤出 不希望混淆的类。比如,预编译的静态库。 iOS编码风格假定每个类都使用2-3个字符的前缀,可以利用这一点过滤进 或者 过滤出整个命名空间。举例,过滤出所有APH MC的命名空间 -F '!APH*' -F '!MC*'

忽略symbol

有些情况是我们不希望混淆,但是一些symbol仍然被混淆了,比如,使用C函数 且 OC的方法也使用了相同的名字,这会导致一个ld 连接错误(unresolved external)。此时,必须要找出该symbol 添加到忽略symbol list中。 举例,不混淆名为defalte 和 以curl_*开头的symbol -i 'deflate' -i 'curl_*'


CocoaPods

如果项目中使用了CocoaPods,也可以混淆这些 外部库内的symbol。用户需要做的是指定Pods PBX工程文件的路径。它在.xcodeproj目录内。ios-class-guard会修改配置和预编译头文件,这样pod内的库也可以被混淆了。 用法 -P Pods/Pods.xcodeproj/project.pbxproj


其他选线

Xib目录

这是一个可选项。工具默认会从可执行目录(绝大多数情况下是工程的根目录)递归的搜索所有的 XIB/Storyboard文件。如果 XIB/Storyboard文件存储在其他路径,用法如下 -X SWTableView/Xib

symbol映射文件

可以指定工具保存symbol映射的路径,默认名为 symbols.json。 用法 -m release/symbols_1.0.0.json

逆向crash dump中的混淆

iOS Class Guard支持对自动崩溃报告工具的逆向处理,如Crashlytics, Fabric, BugSense/Splunk Mint, Crittercism or HockeyApp。使用--dsym参数,iOS Class Guard会替换提供的dSYM文件内的原符号和混淆符号。强烈推荐 在Build Phases/Run script

一开始 添加如下所示的脚本来完成dSYM的自动转换处理,该功能已在上述的工具中测试通过。

`if [ -f “$PROJECT_DIR/symbols.json” ]; then /usr/local/bin/ios-class-guard -m $PROJECT_DIR/symbols.json –dsym $DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME –dsym-out $DWARF_DSYM_FOLDER_PATH/$DWARF_DSYM_FILE_NAME fi

# Another invocations eg.: ./Crashlytics.framework/run <Crashlytics secret #1> <Crashlytics secret #2>`

手动使用方法如下 ios-class-guard -m symbols.json --dsym MyProject_obfuscated.app.dSYM --dsym-out MyProject_unobfuscated.app.dSYM


局限性,OC的工作方式决定了这个工具的局限性

XIB and Storyboards

ios-class-guard处理XIB 和 Storyboard文件的效果很好,但是当使用外部库,且库内包含了IB文件的bundle,一定要忽略这些symbol,否则你在启动app时,他们不会再有效。处理这种情况就要使用class filter了。

KVO

使用混淆可能会导致KVO停止工作。大部分开发者使用硬编码字符串指定KeyPath。
- (void)registerObserver {
    [self.otherObject addObserver:self
                       forKeyPath:@"isFinished"
                          options:NSKeyValueObservingOptionNew
                          context:nil];
}

- (void)unregisterObserver {
    [otherObject removeObserver:self
                     forKeyPath:@"isFinished"
                        context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
              ofObject:(id)object
                change:(NSDictionary *)change
               context:(void *)context
{
  if ([keyPath isEqualToString:@"isFinished"]) {
    // ...
  }
}
这样写无效,属性isFinished将会被重新命名,而硬编码字符串不会反映这种变化。 处理方法是 移除全部的KeyPath 并 改为 NSStringFromSelector(@selector(keyPath))
修改后的代码如下
- (void)registerObserver {
    [self.otherObject addObserver:self
                       forKeyPath:NSStringFromSelector(@selector(isFinished))
                          options:NSKeyValueObservingOptionNew
                          context:nil];
}

- (void)unregisterObserver {
    [otherObject removeObserver:self
                     forKeyPath:NSStringFromSelector(@selector(isFinished))
                        context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
              ofObject:(id)object
                change:(NSDictionary *)change
               context:(void *)context
{
  if ([keyPath isEqualToString:NSStringFromSelector(@selector(isFinished))]) {
    // ...
  }
}

串行化

如果使用保存在磁盘上的类 或者 用户默认使用了NSCoding协议,那么就不能混淆他们了。否则,再次生成symbol后,APP在启动时会crash,不能从串行化数据中读取该类。

未定义symbol

使用iOS-Class-Guard时,很可能会遇到类似与下面的问题:

Undefined symbols for architecture i386: "_OBJC_CLASS_$_n9z", referenced from: objc-class-ref in GRAppDelegate.o

解决方法,复制n9z并在symbols.h中查找,n9z很可能是个类,就要把它从混淆中排除,使用-F '!UnresolvedClassName'参数并重新测试。

note

iOS-Class-Guard与LLVM Obfuscator一起使用还没有经过测试。

Pro版

pro版包含更多的功能:
  • Encryption of strings and constants
  • Tamper detection mechanism
  • Anti-debug mechanism
  • Methods inlining
  • Assets encryption
  • Control flow obfuscation
  • Code virtualization with encryption
  • API method execution hiding

Apple Watch 开发初级

本文转自 猛戳这里

#Apple Watch 开发初级

##运行机制 (此图说明了一切) 运行机制图

在iOS App和WatchKit Extension中,可以看到除了Resources之外,还有Code,但是在WatchKit App中却没有!这说明什么?这说明Watch App没有运算能力, 也就是说它本身并不能处理任何事物,做任何决定,这些事情都是Extension代之完成的。 他是不过是Extension的傀儡而已。 简言之: Watch应用 只包含与应用程序的用户界面有关的storyboards和资源文件。 WatchKit应用扩展 则包含了用于管理、监听应用程序的用户界面以及响应用户交互的代码。

##UI组成

Apple watch只有这三种界面:watch主界面、Glance界面、自定义通知界面

watch主界面:Watch应用拥有iOS应用的完整用户界面。用户从主界面启动手表应用,来查看或处理数据。

Glance界面:使用glance界面以便在Watch应用上显示即时、相关的信息,该界面是可选的只读界面。并不是所有的Watch应用都需要使用glance界面,但是如果使用了它的话就可以让用户方便地访问iOS应用的数据。

自定义通知界面:自定义通知界面可以让您修改默认的本地或远程通知界面,并可以添加自定义图形,内容以及设置格式。自定义通知界面是可选的。

##简单说一下项目搭建 首先,上面部分已经了解到appWatch 是捆绑到iOS App中的,APP watch必须依附于iOS app 才能启动。写完这个demo,大家就可以深刻体会到这一点。

first. 创建一个iOS app project.

second. 选择Xcode–>File–>new–>target(选择Apple watch) next(如图) 步骤图

此处,选项框部分要根据项目需要添加相关界面。

顾名思义,图中 notification Scene 就是三种界面中的通知界面,Glance Scene 就是三种界面中的Glance界面

OK,到此,Apple watch 工程已初步创建。 ##project架构组成 project架构图

前面讲解运行机制有提到 Apple watch的实际运行 包含两个部件,一个是 WatchKit应用扩展(WKDemo WatchKit Extension),一个是Watch应用(图中WKDemo WatchKit App), 从图中 可以清晰的看出,WatchKit App 实际只包含一个storyboard 文件和一个image set集 而,WatchKit Extension 却囊括了 所有的watch 视图类。再次印证了前面的运行机制原理图所说的所有的数据 都是由捆绑到iOS APP中的WatchKit Extension完成处理并分别与iPhone、watch App 进行通信的。

WatchKit app:

WatchKit App只包含一个storyboard 文件和一个image set集 ,之前似乎是喵神的某一篇文章中说:苹果的这一举动暗示了一种趋势—-用storyboard 代替手写代码布局的一种趋势,忽略这一信息的可信度,单就目前如今关于Apple watch的界面布局 确实只有storyboard这一种方式,如果你去确认,发现帮助文档上没有任何关于size,point,frame等一些列字段的痕迹。实际上,仅仅story board 去布局,苹果也设置了条条框框去限制,举个栗子,watch 的布局是不存在像iPhone 那样view与view的叠加,简单说就是类似于网页那样平面布局的风格,先看一下效果图吧。

下面以对主界面的布局为例,其他两种界面都大同小异。具体区别,实际应用体会更深刻。

所有的WKcontroller 都是用Group来布局的,相当于iOS的view层,区别在于,Group 只是个容器,不像view一样可以对自身的layer等属性做设置,使用Group可以对其内部控件按水平或者竖直方向放置控件(一个Group 只能按一个方向布局),Group内部控件是按由左向右或者由上至下,用一张图来说明一下group的所有属性吧(如下)。

关于watch app storyboard的基本使用,了解这些就可以做基本开发了。 其他,诸如动画的使用等情况,就项目要求具体情况具体对待吧。

WatchKit Extension :

先看一下WatchKit Extension 的文件组成:(如图)

Extension的文件组成

前面提到watch的UI组成只包括三种界面,所以从字面上可以清楚的看出①代表了主界面的一系列视图控制器,②代表了自定义通知界面视图控制器,③则是代表了glance界面视图控制器。在这里简单提一下,关于glance界面,苹果官方规定,一个watch APP 只能有一个glance界面,用来显示一些重要信息,方便用户预览,另外glance 界面的布局格式,苹果做了严格规范,开发者只能套用苹果给出的几套模板,不可自定义样式。关于通知界面,需要在 通知界面tip 这个文件中做一些相关设置,大家课余研究吧,本人对此界面研究不多。

下面着重来说一下 主界面视图控制器:

主界面视图控制器

如图,常用的就这三个方法

① - (void)awakeWithContext:(id)context;

② - (void)willActivate;

③ - (void)didDeactivate;

①方法类似于UIViewController 中的viewdidload 方法 ②方法类似于UIViewController 中的viewDidAppear 方法 ③方法类似于UIViewController 中的viewDidDisappear 方法

①方法 只在watch APP启动时调用一次,app 启动后不会再次调起,当视图控制器之间相互切换时会调起新视图控制器②方法和老视图控制器③方法。

##iPhone 与watch 之间的消息传递 因为watch的数据处理都是捆绑在iPhone 端,如果不唤醒watch 捆绑的ios app ,那么空有wantch app在运行就如同是图形预览,完全失去了app 的意义。所以,watch app启动的时候 都是由watch Extension强制启动了捆绑的ios APP程序,只有当iPhone 端与watch Extension 产生了数据交互,在watch app界面上有所体现,才称的上是一个app。

###watch是如何唤醒iPhone app的 很简单,苹果提供了这么几个方法

一个是watchkit 提供的唤醒父应用程序方法:

  • (BOOL)openParentApplication:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo, NSError *error)) reply; // launches containing iOS application on the phone. userInfo must be non-nil

一个是ios8.2之后UIKit新加的响应watch请求的一个方法:

  • (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply NS_AVAILABLE_IOS(8_2); 你只需要在iPhone 工程的AppDelegate 里重写该方法,外加一些相关配置,即可。

上几张图作下说明:

首先在watch Extension 的主视图控制器的启动方法(awakeWithContext方法)中,调用openParentApplication:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo, NSError *error)) reply 方法,此处发起打开父程序请求,附代码如下:

代码1

其次,在appDelegate里添加application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply 方法,此处接收watch 发送过来的请求。附代码如下: 代码2

完成这些代码之后,需要在工程做一点小小配置:

在plist文件中,注册对外接口 找到appname-info.plist文件,点击打开它,在列表中找到URL types(如果没有,可添加一个URL types )里面的内容设置看下图: 注册对外接口

关键Key和Value是 URL identifier => com.future.wkDemo;URL Schemes => wkDemo

###watch与iPhone app之间是怎么通知彼此的

####watch向iPhone 端发送数据更新的通知方法 代码1 代码2

####iPhone向watch 端发送数据更新的通知方法(此处用的是达尔文通知,具体原理可搜索关键字–达尔文通知或者CFNotificationCenterGetDarwinNotifyCenter)

代码1 代码2

##数据共享相关配置 首先:在Target中,先选择iOS App,在General中的Team选项中选择现有的组(非None选项) 对Extension和WatchKit App也重复上面的步骤

其次:在Capabilities中,打开App Groups选项,增加一个Group并选中 对于Extension也做同样的操作并选中刚才创建的那个Group。如果在此过程中出现红色感叹号就点击“Fix issue”,直到全部正确为止。

完成了设置之后我们就可以设计WatchKit App的界面了, 找到Interface.storyborad,加入需要的元素。

关于iPhone 与 watch之间的数据共享,Apple 建议还是在 iOS app 中完成,并通过 App Groups 进行数据共享,从而在 Watch Extension 中拿到这些数据。