详解Java多线程编程中LockSupport类的线程阻塞用法

详解Java多线程编程中LockSupport类的线程阻塞用法

LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:

  1. public native void unpark(Thread jthread);  
  2. public native void park(boolean isAbsolute, long time);  

isAbsolute参数是指明时间是*对的,还是相对的。

仅仅两个简单的接口,就为上层提供了强大的同步原语。

先来解析下两个函数是做什么的。

unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”。这个有点像信号量,但是这个“许可”是不能叠加的,“许可”是一次性的。

比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。

注意,unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行。

实际上,park函数即使没有“许可”,有时也会无理由地返回,这点等下再解析。

park和unpark的灵活之处

上面已经提到,unpark函数可以先于park调用,这个正是它们的灵活之处。

一个线程它有可能在别的线程unPark之前,或者之后,或者同时调用了park,那么因为park的特性,它可以不用担心自己的park的时序问题,否则,如果park必须要在unpark之前,那么给编程带来很大的麻烦!!

考虑一下,两个线程同步,要如何处理?

在Java5里是用wait/notify/notifyAll来同步的。wait/notify机制有个很蛋疼的地方是,比如线程B要用notify通知线程A,那么线程B要确保线程A已经在wait调用上等待了,否则线程A可能永远都在等待。编程的时候就会很蛋疼。

另外,是调用notify,还是notifyAll?

notify只会唤醒一个线程,如果错误地有两个线程在同一个对象上wait等待,那么又悲剧了。为了安全起见,貌似只能调用notifyAll了。

park/unpark模型真正解耦了线程之间的同步,线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态。

 

HotSpot里park/unpark的实现

每个java线程都有一个Parker实例,Parker类是这样定义的:

  1. class Parker : public os::PlatformParker {  
  2. private:  
  3.   volatile int _counter ;  
  4.   …
  5. public:  
  6.   void park(bool isAbsolute, jlong time);  
  7.   void unpark();  
  8.   …
  9. }
  10. class PlatformParker : public CHeapObj<mtInternal> {  
  11.   protected:  
  12.     pthread_mutex_t _mutex [1] ;
  13.     pthread_cond_t  _cond  [1] ;
  14.     …
  15. }

可以看到Parker类实际上用Posix的mutex,condition来实现的。在Parker类里的_counter字段,就是用来记录所谓的“许可”的。

当调用park时,先尝试直接能否直接拿到“许可”,即_counter>0时,如果成功,则把_counter设置为0,并返回:

  1. void Parker::park(bool isAbsolute, jlong time) {  
  2.   // Ideally we’d do something useful while spinning, such  
  3.   // as calling unpackTime().  
  4.   // Optional fast-path check:  
  5.   // Return immediately if a permit is available.  
  6.   // We depend on Atomic::xchg() having full barrier semantics  
  7.   // since we are doing a lock-free update to _counter.  
  8.   if (Atomic::xchg(0, &_counter) > 0) return;  

如果不成功,则构造一个ThreadBlockInVM,然后检查_counter是不是>0,如果是,则把_counter设置为0,unlock mutex并返回:

  1. ThreadBlockInVM tbivm(jt);
  2. if (_counter > 0)  { // no wait needed  
  3.   _counter = 0;
  4.   status = pthread_mutex_unlock(_mutex);

否则,再判断等待的时间,然后再调用pthread_cond_wait函数等待,如果等待返回,则把_counter设置为0,unlock mutex并返回:

  1. if (time == 0) {  
  2.   status = pthread_cond_wait (_cond, _mutex) ;
  3. }
  4. _counter = 0 ;
  5. status = pthread_mutex_unlock(_mutex) ;
  6. assert_status(status == 0, status, “invariant”) ;  
  7. OrderAccess::fence();

当unpark时,则简单多了,直接设置_counter为1,再unlock mutext返回。如果_counter之前的值是0,则还要调用pthread_cond_signal唤醒在park中等待的线程:

  1. void Parker::unpark() {  
  2.   int s, status ;  
  3.   status = pthread_mutex_lock(_mutex);
  4.   assert (status == 0, “invariant”) ;  
  5.   s = _counter;
  6.   _counter = 1;
  7.   if (s < 1) {  
  8.      if (WorkAroundNPTLTimedWaitHang) {  
  9.         status = pthread_cond_signal (_cond) ;
  10.         assert (status == 0, “invariant”) ;  
  11.         status = pthread_mutex_unlock(_mutex);
  12.         assert (status == 0, “invariant”) ;  
  13.      } else {  
  14.         status = pthread_mutex_unlock(_mutex);
  15.         assert (status == 0, “invariant”) ;  
  16.         status = pthread_cond_signal (_cond) ;
  17.         assert (status == 0, “invariant”) ;  
  18.      }
  19.   } else {  
  20.     pthread_mutex_unlock(_mutex);
  21.     assert (status == 0, “invariant”) ;  
  22.   }
  23. }

简而言之,是用mutex和condition保护了一个_counter的变量,当park时,这个变量置为了0,当unpark时,这个变量置为1。
值得注意的是在park函数里,调用pthread_cond_wait时,并没有用while来判断,所以posix condition里的”Spurious wakeup”一样会传递到上层Java的代码里。关于”Spurious wakeup”,参考上一篇

  1. if (time == 0) {  
  2.   status = pthread_cond_wait (_cond, _mutex) ;
  3. }

这也就是为什么Java dos里提到,当下面三种情况下park函数会返回:

  • Some other thread invokes unpark with the current thread as the target; or
  • Some other thread interrupts the current thread; or
  • The call spuriously (that is, for no reason) returns.

相关的实现代码在:

http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/81d815b05abb/src/share/vm/runtime/park.hpp
http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/81d815b05abb/src/share/vm/runtime/park.cpp
http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/81d815b05abb/src/os/linux/vm/os_linux.hpp
http://hg.openjdk.java.net/jdk7/jdk7/hotspot/file/81d815b05abb/src/os/linux/vm/os_linux.cpp

其它的一些东东:

Parker类在分配内存时,使用了一个技巧,重载了new函数来实现了cache line对齐。

  1. // We use placement-new to force ParkEvent instances to be  
  2. // aligned on 256-byte address boundaries.  This ensures that the least  
  3. // significant byte of a ParkEvent address is always 0.  
  4. void * operator new (size_t sz) ;  

Parker里使用了一个无锁的队列在分配释放Parker实例:

  1. volatile int Parker::ListLock = 0 ;  
  2. Parker * volatile Parker::FreeList = NULL ;  
  3. Parker * Parker::Allocate (JavaThread * t) {
  4.   guarantee (t != NULL, “invariant”) ;  
  5.   Parker * p ;
  6.   // Start by trying to recycle an existing but unassociated  
  7.   // Parker from the global free list.  
  8.   for (;;) {  
  9.     p = FreeList ;
  10.     if (p  == NULL) break ;  
  11.     // 1: Detach  
  12.     // Tantamount to p = Swap (&FreeList, NULL)  
  13.     if (Atomic::cmpxchg_ptr (NULL, &FreeList, p) != p) {  
  14.        continue ;  
  15.     }
  16.     // We’ve detached the list.  The list in-hand is now  
  17.     // local to this thread.   This thread can operate on the  
  18.     // list without risk of interference from other threads.  
  19.     // 2: Extract — pop the 1st element from the list.  
  20.     Parker * List = p->FreeNext ;
  21.     if (List == NULL) break ;  
  22.     for (;;) {  
  23.         // 3: Try to reattach the residual list  
  24.         guarantee (List != NULL, “invariant”) ;  
  25.         Parker * Arv =  (Parker *) Atomic::cmpxchg_ptr (List, &FreeList, NULL) ;
  26.         if (Arv == NULL) break ;  
  27.         // New nodes arrived.  Try to detach the recent arrivals.  
  28.         if (Atomic::cmpxchg_ptr (NULL, &FreeList, Arv) != Arv) {  
  29.             continue ;  
  30.         }
  31.         guarantee (Arv != NULL, “invariant”) ;  
  32.         // 4: Merge Arv into List  
  33.         Parker * Tail = List ;
  34.         while (Tail->FreeNext != NULL) Tail = Tail->FreeNext ;  
  35.         Tail->FreeNext = Arv ;
  36.     }
  37.     break ;  
  38.   }
  39.   if (p != NULL) {  
  40.     guarantee (p->AssociatedWith == NULL, “invariant”) ;  
  41.   } else {  
  42.     // Do this the hard way — materialize a new Parker..  
  43.     // In rare cases an allocating thread might detach  
  44.     // a long list — installing null into FreeList –and  
  45.     // then stall.  Another thread calling Allocate() would see  
  46.     // FreeList == null and then invoke the ctor.  In this case we  
  47.     // end up with more Parkers in circulation than we need, but  
  48.     // the race is rare and the outcome is benign.  
  49.     // Ideally, the # of extant Parkers is equal to the  
  50.     // maximum # of threads that existed at any one time.  
  51.     // Because of the race mentioned above, segments of the  
  52.     // freelist can be transiently inaccessible.  At worst  
  53.     // we may end up with the # of Parkers in circulation  
  54.     // slightly above the ideal.  
  55.     p = new Parker() ;  
  56.   }
  57.   p->AssociatedWith = t ;          // Associate p with t  
  58.   p->FreeNext       = NULL ;
  59.   return p ;  
  60. }
  61. void Parker::Release (Parker * p) {  
  62.   if (p == NULL) return ;  
  63.   guarantee (p->AssociatedWith != NULL, “invariant”) ;  
  64.   guarantee (p->FreeNext == NULL      , “invariant”) ;  
  65.   p->AssociatedWith = NULL ;
  66.   for (;;) {  
  67.     // Push p onto FreeList  
  68.     Parker * List = FreeList ;
  69.     p->FreeNext = List ;
  70.     if (Atomic::cmpxchg_ptr (p, &FreeList, List) == List) break ;  
  71.   }
  72. }

总结与扯谈

JUC(Java Util Concurrency)仅用简单的park, unpark和CAS指令就实现了各种高级同步数据结构,而且效率很高,令人惊叹。

在C++程序员各种自制轮子的时候,Java程序员则有很丰富的并发数据结构,如lock,latch,queue,map等信手拈来。

要知道像C++直到C++11才有标准的线程库,同步原语,但离高级的并发数据结构还有很远。boost库有提供一些线程,同步相关的类,但也是很简单的。Intel的tbb有一些高级的并发数据结构,但是国内boost都用得少,更别说tbb了。

*开始研究无锁算法的是C/C++程序员,但是后来很多Java程序员,或者类库开始自制各种高级的并发数据结构,经常可以看到有分析Java并发包的文章。反而C/C++程序员总是在分析无锁的队列算法。高级的并发数据结构,比如并发的HashMap,没有看到有相关的实现或者分析的文章。在C++11之后,这种情况才有好转。

因为正确高效实现一个Concurrent Hash Map是很困难的,要对内存CPU有深刻的认识,而且还要面对CPU不断升级带来的各种坑。

我认为真正值得信赖的C++并发库,只有Intel的tbb和微软的PPL。

 

详解Java多线程编程中LockSupport

详解Java多线程编程中LockSupport

LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。
因为park() 和 unpark()有许可的存在;调用 park() 的线程和另一个试图将其 unpark() 的线程之间的竞争将保持活性。

基本用法
LockSupport 很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继 续 执行;如果许可已经被占用,当前线 程阻塞,等待获取许可。

1
2
3
4
5
public static void main(String[] args)
{
   LockSupport.park();
   System.out.println("block.");
}

运行该代码,可以发现主线程一直处于阻塞状态。因为 许可默认是被占用的 ,调用park()时获取不到许可,所以进入阻塞状态。

如下代码:先释放许可,再获取许可,主线程能够正常终止。LockSupport许可的获取和释放,一般来说是对应的,如果多次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态。

1
2
3
4
5
6
7
public static void main(String[] args)
{
   Thread thread = Thread.currentThread();
   LockSupport.unpark(thread);//释放许可
   LockSupport.park();// 获取许可
   System.out.println("b");
}

LockSupport是可不重入 的,如果一个线程连续2次调用 LockSupport .park(),那么该线程一定会一直阻塞下去。

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception
{
 Thread thread = Thread.currentThread();
 
 LockSupport.unpark(thread);
 
 System.out.println("a");
 LockSupport.park();
 System.out.println("b");
 LockSupport.park();
 System.out.println("c");
}

这段代码打印出a和b,不会打印c,因为第二次调用park的时候,线程无法获取许可出现死锁。

下面我们来看下LockSupport对应中断的响应性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void t2() throws Exception
{
 Thread t = new Thread(new Runnable()
 {
  private int count = 0;
  @Override
  public void run()
  {
   long start = System.currentTimeMillis();
   long end = 0;
   while ((end - start) <= 1000)
   {
    count++;
    end = System.currentTimeMillis();
   }
   System.out.println("after 1 second.count=" + count);
  //等待或许许可
   LockSupport.park();
   System.out.println("thread over." + Thread.currentThread().isInterrupted());
  }
 });
 t.start();
 Thread.sleep(2000);
 // 中断线程
 t.interrupt();
 
 System.out.println("main over");
}

*终线程会打印出thread over.true。这说明 线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException 。

LockSupport函数列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 返回提供给*近一次尚未解除阻塞的 park 方法调用的 blocker 对象,如果该调用不受阻塞,则返回 null。
static Object getBlocker(Thread t)
// 为了线程调度,禁用当前线程,除非许可可用。
static void park()
// 为了线程调度,在许可可用之前禁用当前线程。
static void park(Object blocker)
// 为了线程调度禁用当前线程,*多等待指定的等待时间,除非许可可用。
static void parkNanos(long nanos)
// 为了线程调度,在许可可用前禁用当前线程,并*多等待指定的等待时间。
static void parkNanos(Object blocker, long nanos)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(long deadline)
// 为了线程调度,在指定的时限前禁用当前线程,除非许可可用。
static void parkUntil(Object blocker, long deadline)
// 如果给定线程的许可尚不可用,则使其可用。
static void unpark(Thread thread)


LockSupport示例
对比下面的“示例1”和“示例2”可以更清晰的了解LockSupport的用法。
示例1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class WaitTest1 {
  public static void main(String[] args) {
    ThreadA ta = new ThreadA("ta");
    synchronized(ta) { // 通过synchronized(ta)获取“对象ta的同步锁”
      try {
        System.out.println(Thread.currentThread().getName()+" start ta");
        ta.start();
        System.out.println(Thread.currentThread().getName()+" block");
        // 主线程等待
        ta.wait();
        System.out.println(Thread.currentThread().getName()+" continue");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
  static class ThreadA extends Thread{
    public ThreadA(String name) {
      super(name);
    }
    public void run() {
      synchronized (this) { // 通过synchronized(this)获取“当前对象的同步锁”
        System.out.println(Thread.currentThread().getName()+" wakup others");
        notify();  // 唤醒“当前对象上的等待线程”
      }
    }
  }
}

示例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.concurrent.locks.LockSupport;
public class LockSupportTest1 {
  private static Thread mainThread;
  public static void main(String[] args) {
    ThreadA ta = new ThreadA("ta");
    // 获取主线程
    mainThread = Thread.currentThread();
    System.out.println(Thread.currentThread().getName()+" start ta");
    ta.start();
    System.out.println(Thread.currentThread().getName()+" block");
    // 主线程阻塞
    LockSupport.park(mainThread);
    System.out.println(Thread.currentThread().getName()+" continue");
  }
  static class ThreadA extends Thread{
    public ThreadA(String name) {
      super(name);
    }
    public void run() {
      System.out.println(Thread.currentThread().getName()+" wakup others");
      // 唤醒“主线程”
      LockSupport.unpark(mainThread);
    }
  }
}

运行结果:

1
2
3
4
main start ta
main block
ta wakup others
main continue

说明:park和wait的区别。wait让线程阻塞前,必须通过synchronized获取同步锁。

LockSupport的park和unpark的基本使用,以及对线程中断的响应性

LockSupport的park和unpark的基本使用,以及对线程中断的响应性

  LockSupport是JDK中比较底层的类,用来创建锁和其他同步工具类的基本线程阻塞原语。java锁和同步器框架的核心AQS:AbstractQueuedSynchronizer,就是通过调用LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的。LockSupport很类似于二元信号量(只有1个许可证可供使用),如果这个许可还没有被占用,当前线程获取许可并继续执行;如果许可已经被占用,当前线程阻塞,等待获取许可。

  1. public static void main(String[] args)  
  2. {
  3.      LockSupport.park();
  4.      System.out.println(“block.”);  
  5. }

运行该代码,可以发现主线程一直处于阻塞状态。因为许可默认是被占用的,调用park()时获取不到许可,所以进入阻塞状态。

 

如下代码:先释放许可,再获取许可,主线程能够正常终止。LockSupport许可的获取和释放,一般来说是对应的,如果多次unpark,只有一次park也不会出现什么问题,结果是许可处于可用状态。

  1. public static void main(String[] args)  
  2. {
  3.      Thread thread = Thread.currentThread();
  4.      LockSupport.unpark(thread);//释放许可  
  5.      LockSupport.park();// 获取许可  
  6.      System.out.println(“b”);  
  7. }

 

LockSupport是不可重入的,如果一个线程连续2次调用LockSupport.park(),那么该线程一定会一直阻塞下去。

 

  1. public static void main(String[] args) throws Exception  
  2. {
  3.     Thread thread = Thread.currentThread();
  4.     LockSupport.unpark(thread);
  5.     System.out.println(“a”);  
  6.     LockSupport.park();
  7.     System.out.println(“b”);  
  8.     LockSupport.park();
  9.     System.out.println(“c”);  
  10. }

这段代码打印出a和b,不会打印c,因为第二次调用park的时候,线程无法获取许可出现死锁。

 

下面我们来看下LockSupport对应中断的响应性

 

  1. public static void t2() throws Exception  
  2. {
  3.     Thread t = new Thread(new Runnable()  
  4.     {
  5.         private int count = 0;  
  6.         @Override  
  7.         public void run()  
  8.         {
  9.             long start = System.currentTimeMillis();  
  10.             long end = 0;  
  11.             while ((end – start) <= 1000)  
  12.             {
  13.                 count++;
  14.                 end = System.currentTimeMillis();
  15.             }
  16.             System.out.println(“after 1 second.count=” + count);  
  17.         //等待或许许可  
  18.             LockSupport.park();
  19.             System.out.println(“thread over.” + Thread.currentThread().isInterrupted());  
  20.         }
  21.     });
  22.     t.start();
  23.     Thread.sleep(2000);  
  24.     // 中断线程  
  25.     t.interrupt();
  26.     System.out.println(“main over”);  
  27. }

*终线程会打印出thread over.true。这说明线程如果因为调用park而阻塞的话,能够响应中断请求(中断状态被设置成true),但是不会抛出InterruptedException

ios NSString 用法

温故知新,常用常新
//1、创建常量字符串。
NSString *astring = @"This is a String!";
//2、创建空字符串,给予赋值。
NSString *astring = [[NSString alloc] init];
 
astring = @"This is a String!";
 
[astring release];
 
NSLog(@"astring:%@",astring);

//

NSString *astring = [[NSString alloc] init];
 
NSLog(@"0x%.8x", astring);
 
astring=@"This is a String!";
 
NSLog(@"0x%.8x", astring);
 
[astring release];
 
NSLog(@"astring:%@",astring);
//3、在以上方法中,提升速度:initWithString方法
NSString *astring = [[NSString alloc] initWithString:@"This is a String!"];
 
NSLog(@"astring:%@",astring);
 
[astring release];
//4、用标准c创建字符串:initWithCString方法
char *Cstring = "This is a String!";
 
NSString *astring = [[NSString alloc] initWithCString:Cstring];
 
NSLog(@"astring:%@",astring);
 
[astring release];
//5、创建格式化字符串:占位符(由一个%加一个字符组成)
int i = 1;
 
int j = 2;
 
NSString *astring = [[NSString alloc] initWithString:[NSString stringWithFormat:@"%d.This is %i string!",i,j]];
 
NSLog(@"astring:%@",astring);
 
[astring release];
//6、创建临时字符串
NSString *astring;
 
astring = [NSString stringWithCString:"This is a temporary string"];
 
NSLog(@"astring:%@",astring);

//7、从文件创建字符串

NSString *path = [[NSBundlemainBundle] pathForResource:@"astring.text"ofType:nil];
NSString *astring = [[NSString alloc] initWithContentsOfFile:path];
NSLog(@"astring:%@",astring);
[astring release];

//8、用字符串创建字符串,并写入到文件

NSString *astring = [[NSString alloc] initWithString:@"This is a String!"];
 
NSLog(@"astring:%@",astring);
 
NSString *path = @"astring.text";    
 
[astring writeToFile: path atomically: YES];
 
[astring release];

注:此路径path只只是示意,真实路径并非如此

//9、用C比较:strcmp函数
char string1[] = "string!";
 
char string2[] = "string!";
 
if(strcmp(string1, string2) == 0)
{
 
    NSLog(@"1");
 
}

//10、isEqualToString方法

NSString *astring01 = @"This is a String!";
 
NSString *astring02 = @"This is a String!";
 
BOOL result = [astring01 isEqualToString:astring02];
 
NSLog(@"result:%d",result);

//11、compare方法(comparer返回的三种值)

//
NSString *astring01 = @"This is a String!";
 
NSString *astring02 = @"This is a String!";    
 
BOOL result = [astring01 compare:astring02] == NSOrderedSame;    //NSOrderedSame判断两者内容是否相同
 
NSLog(@"result:%d",result);    
 
//
NSString *astring01 = @"This is a String!";
 
NSString *astring02 = @"this is a String!";
 
BOOL result = [astring01 compare:astring02] == NSOrderedAscending;    //NSOrderedAscending判断两对象值的大小(按字母顺序进行比较,astring02大于astring01为真)
 
NSLog(@"result:%d",result);

//
NSString *astring01 = @"this is a String!";
 
NSString *astring02 = @"This is a String!";
 
BOOL result = [astring01 compare:astring02] == NSOrderedDescending;    //NSOrderedDescending判断两对象值的大小(按字母顺序进行比较,astring02小于astring01为真)
 
NSLog(@"result:%d",result);     

//12、不考虑大小写比较字符串

//1.
NSString *astring01 = @"this is a String!";
 
NSString *astring02 = @"This is a String!";
 
BOOL result = [astring01 caseInsensitiveCompare:astring02] == NSOrderedSame;    //NSOrderedDescending判断两对象值的大小(按字母顺序进行比较,astring02小于astring01为真)

NSLog(@"result:%d",result); 

//2.
NSString *astring01 = @"this is a String!";
 
NSString *astring02 = @"This is a String!";
 
BOOL result = [astring01 compare:astring02
 
options:NSCaseInsensitiveSearch | NSNumericSearch] == NSOrderedSame;    //NSCaseInsensitiveSearch:不区分大小写比较 NSLiteralSearch:进行完全比较,区分大小写 NSNumericSearch:比较字符串的字符个数,而不是字符值。
 
NSLog(@"result:%d",result);
//13、输出大写或者小写字符串
NSString *string1 = @"A String"; 
 
NSString *string2 = @"String"; 
 
NSLog(@"string1:%@",[string1 uppercaseString]);//大写
 
NSLog(@"string2:%@",[string2 lowercaseString]);//小写
 
NSLog(@"string2:%@",[string2 capitalizedString]);//首字母大小

//14、-rangeOfString: //查找字符串某处是否包含其它字符串

NSString *string1 = @"This is a string";
 
NSString *string2 = @"string";
 
NSRange range = [string1 rangeOfString:string2];
 
int location = range.location;
 
int leight = range.length;
 
NSString *astring = [[NSString alloc] initWithString:[NSString stringWithFormat:@"Location:%i,Leight:%i",location,leight]];
 
NSLog(@"astring:%@",astring);
 
[astring release];
复制代码

//15、-substringToIndex: 从字符串的开头一直截取到指定的位置,但不包括该位置的字符

NSString *string1 = @"This is a string";
 
NSString *string2 = [string1 substringToIndex:3];
 
NSLog(@"string2:%@",string2);

//16、-substringFromIndex: 以指定位置开始(包括指定位置的字符),并包括之后的全部字符

NSString *string1 = @"This is a string";
 
NSString *string2 = [string1 substringFromIndex:3];
 
NSLog(@"string2:%@",string2);

//17、-substringWithRange: //按照所给出的位置,长度,任意地从字符串中截取子串

NSString *string1 = @"This is a string";
 
NSString *string2 = [string1 substringWithRange:NSMakeRange(0, 4)];
 
NSLog(@"string2:%@",string2);

//18、-stringWithCapacity: //按照固定长度生成空字符串

NSMutableString *String;
 
String = [NSMutableString stringWithCapacity:40];

//19、-appendString: and -appendFormat: //把一个字符串接在另一个字符串的末尾

NSMutableString *String1 = [[NSMutableString alloc] initWithString:@"This is a NSMutableString"];
 
[String1 appendString:@", I will be adding some character"];
 
[String1 appendFormat:[NSString stringWithFormat:@", I will be adding some character"]];
 
NSLog(@"String1:%@",String1);

//20、-insertString: atIndex: //在指定位置插入字符串

NSMutableString *String1 = [[NSMutableString alloc] initWithString:@"This is a NSMutableString"];
 
[String1 insertString:@"Hi! " atIndex:0];
 
NSLog(@"String1:%@",String1);

//21、-setString:

NSMutableString *String1 = [[NSMutableString alloc] initWithString:@"This is a NSMutableString"];
 
[String1 setString:@"Hello Word!"];
 
NSLog(@"String1:%@",String1);

//22、-replaceCharactersInRange: withString: //用指定字符串替换字符串中某指定位置、长度的字符串

NSMutableString *String1 = [[NSMutableString alloc] initWithString:@"This is a NSMutableString"];
 
[String1 replaceCharactersInRange:NSMakeRange(0, 4) withString:@"That"];
 
NSLog(@"String1:%@",String1);

//23、-hasPrefix: //检查字符串是否以另一个字符串开头

NSString *String1 = @"NSStringInformation.txt";
 
[String1 hasPrefix:@"NSString"] = = 1 ?  NSLog(@"YES") : NSLog(@"NO");
 
[String1 hasSuffix:@".txt"] = = 1 ?  NSLog(@"YES") : NSLog(@"NO");
//24、扩展路径
NSString *Path = @"~/NSData.txt";
 
NSString *absolutePath = [Path stringByExpandingTildeInPath];
 
NSLog(@"absolutePath:%@",absolutePath);
 
NSLog(@"Path:%@",[absolutePath stringByAbbreviatingWithTildeInPath]);

//25、文件扩展名

NSString *Path = @"~/NSData.txt";
 
NSLog(@"Extension:%@",[Path pathExtension]);

iOS 查找字符串 相同 子字符串的位置 range

问题:解决替换同一个字符串的多个相同的字符eg.

xxx这个超级大土豪白送xxx一个!赶快来抢把!

将*个xxx换成名字

将第二个xxx换成物品

两种办法    第二种办法更灵活一点

//*种办法简单粗暴(思路获取*次xxx出现的位置然后替换成名字 替换之后string中就只有一个xxx了  然后用物品替换string中仅有的一个xxx)

//        NSRange range = [share6 rangeOfString:@”xxx”];//获取*次出现的位置

//        share6 = [share6 stringByReplacingCharactersInRange:range withString:_m_dataDic[@”nickName”]];

//        share5 = [share5 stringByReplacingCharactersInRange:range withString:_m_dataDic[@”nickName”]];

//        shareContent = [[_m_dataDic[@”gender”]integerValue] == 1?share5:share6 replace:@”xxx” withString:((!_m_dataDic[@”content”] || [_m_dataDic[@”content”] isKindOfClass:[NSNull class]])?@”顺风车”:_m_dataDic[@”content”])];

 

//第二种方法(思路 首先遍历这个字符串 然后找到所有的xxx 所在的位置的index    然后通过index将字符串进行替换)

NSMutableArray *arrayShare = [self getRangeStr:share6 findText:@”xxx”];

shareContent = [[_m_dataDic[@”gender”]integerValue] == 1?share5:share6 stringByReplacingCharactersInRange:NSMakeRange([arrayShare[1]integerValue], 3) withString:((!_m_dataDic[@”content”] || [_m_dataDic[@”content”] isKindOfClass:[NSNull class]])?@”顺风车”:_m_dataDic[@”content”])];

shareContent = [shareContent stringByReplacingCharactersInRange:NSMakeRange([arrayShare[0]integerValue], 3) withString:_m_dataDic[@”nickName”]];

 

//获取这个字符串中的所有xxx的所在的index

– (NSMutableArray *)getRangeStr:(NSString *)text findText:(NSString *)findText

{

NSMutableArray *arrayRanges = [NSMutableArray arrayWithCapacity:20];

if (findText == nil && [findText isEqualToString:@””]) {

return nil;

}

NSRange rang = [text rangeOfString:findText]; //获取*次出现的range

if (rang.location != NSNotFound && rang.length != 0) {

[arrayRanges addObject:[NSNumber numberWithInteger:rang.location]];//将*次的加入到数组中

NSRange rang1 = {0,0};

NSInteger location = 0;

NSInteger length = 0;

for (int i = 0;; i++)

{

if (0 == i) {//去掉这个xxx

location = rang.location + rang.length;

length = text.length – rang.location – rang.length;

rang1 = NSMakeRange(location, length);

}else

{

location = rang1.location + rang1.length;

length = text.length – rang1.location – rang1.length;

rang1 = NSMakeRange(location, length);

}

//在一个range范围内查找另一个字符串的range

rang1 = [text rangeOfString:findText options:NSCaseInsensitiveSearch range:rang1];

if (rang1.location == NSNotFound && rang1.length == 0) {

break;

}else//添加符合条件的location进数组

[arrayRanges addObject:[NSNumber numberWithInteger:rang1.location]];

}

return arrayRanges;

}

return nil;

}

ios 开发中获取字符串中重复的字符的rang

iOS 开发中经常会遇到处理字符串的问题,对于一个字符串经常会遇见里面包含重复的字符需要对重复的字符进行处理,下面的代码就是对重复字符进行处理的操作,具体代码如下所示:

/**
* 返回重复字符的location
*
* @param text 初始化的字符串
* @param findText 查找的字符
*
* @return 返回重复字符的location
*/
– (NSMutableArray *)getRangeStr:(NSString *)text findText:(NSString *)findText
{
NSMutableArray *arrayRanges = [NSMutableArray arrayWithCapacity:20];
if (findText == nil && [findText isEqualToString:@””]) {
return nil;
}
NSRange rang = [text rangeOfString:findText];
if (rang.location != NSNotFound && rang.length != 0) {
[arrayRanges addObject:[NSNumber numberWithInteger:rang.location]];
NSRange rang1 = {0,0};
NSInteger location = 0;
NSInteger length = 0;
for (int i = 0;; i++)
{
if (0 == i) {
location = rang.location + rang.length;
length = text.length – rang.location – rang.length;
rang1 = NSMakeRange(location, length);
}else
{
location = rang1.location + rang1.length;
length = text.length – rang1.location – rang1.length;
rang1 = NSMakeRange(location, length);
}
rang1 = [text rangeOfString:findText options:NSCaseInsensitiveSearch range:rang1];
if (rang1.location == NSNotFound && rang1.length == 0) {
break;
}else
[arrayRanges addObject:[NSNumber numberWithInteger:rang1.location]];
}
return arrayRanges;
}
return nil;
}

返回的数组对象就是重复字符在text 中的location, 使用方式是通过便利的形式,获取获取rang={array[i], findText.length}

ios(杂)代码集锦

在iphone程序中实现截屏的一种方法:

//导入头文件
#import QuartzCore/QuartzCore.h

//创建一个基于位图的图形上下文并指定大小为CGSizeMake(200,400)
UIGraphicsBeginImageContext(CGSizeMake(200,400));

//renderInContext 呈现接受者及其子范围到指定的上下文
[self.view.layer  renderInContext:UIGraphicsGetCurrentContext()];

//返回一个基于当前图形上下文的图片
UIImage *aImage =UIGraphicsGetImageFromCurrentImageContext();

//移除栈顶的基于当前位图的图形上下文
UIGraphicsEndImageContext();

//以png格式返回指定图片的数据
imageData = UIImagePNGRepresentation(aImage);

//或者将该图片保存到图片集中
UIImageWriteToSavedPhotosAlbum(image,self,nil,nil);

Objective-C 画图

1.颜色和字体
UIKit提供了UIColor和UIFont类来进行设置颜色和字体,
UIColor *redColor=【UIColor redColor】;
【redColor set】;//设置为红色
UIFont *front=【UIFont systemFontOfSize:14.0】;//获得系统字体
【myLable setFont:font】;//设置文本对象的字体
2.drawRect方法
对于画图,你首先需要重载drawRect方法,然后调用setNeedsDisplay方法让系统画图:
-(void)drawRect:(CGRect)rect;//在rect指定的区域画图
-(void)setNeedsDisplay;//让系统调用drawRect画图

延时函数和Timer的使用
延时函数:

[NSThread sleepForTimeInterval:5.0]; //暂停5s.

Timer的使用:
NSTimer *connectionTimer;  //timer对象

//实例化timer
self.connectionTimer=[NSTimer  scheduledTimerWithTimeInterval:1.5 target:selfselector:@selector(timerFired:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop]addTimer:self.connectionTimer forMode:NSDefaultRunLoopMode];

//用timer作为延时的一种方法
do{
[[NSRunLoop  currentRunLoop]runUntilDate:[NSDate  dateWithTimeIntervalSinceNow:1.0]];
}while(!done);

//timer调用函数
-(void)timerFired:(NSTimer *)timer{
done =YES;
}

启动界面的制作
iPhone开发实现splash画面非常简单,做一个全屏的欢迎页的图片,把它命名为Default.png,然后放在Xcode工程的Resource里面。
在XXXAppDelegate.m程序中,插入如下代码:
– (BOOL)application:(UIApplication*)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//–inserta delay of 5 seconds before the splash screendisappears–
[NSThread sleepForTimeInterval:5.0];
//Override point for customization after applicationlaunch.
//Add the view controller’s view to the window anddisplay.
[window  addSubview:viewController.view];
[window  makeKeyAndVisible];
return YES;
}
这样splash页面就停留5秒后,消失了。

关于控制器Controller的思考
iPhone开发中,只有一个窗口,对应的是多个视图,而视图的组织形式各种各样,关键是要靠控制器来组织各个视图的逻辑关系。大体的关系如下:

窗体—主控制器(比如说导航控制器),主控制器在窗体里面,拖动过去即可,在AppDelegate中写相关变量的代码—在主控制器下有别的控制器,比如视图控制器,可以通过interfacebuilder来关联根视图什么的—-视图控制器相当于一个根视图,可以调用其他的视图—视图中包含类文件(.h,.m)和图形界面文件(.xib)(两个之间必须关联起来。)

翻页效果
经常看到iPhone的软件向上向下翻页面的效果,其实这个很简单,已经有封装好的相关方法处理。
//首先设置动画的相关参数
[UIView beginAnimations:@”Curl”context:nil];
[UIView setAnimationDuration:1.25]; //时间
[UIViewsetAnimationCurve:UIViewAnimationCurveEaseInOut];//速度

//然后设置动画的动作和目标视图
[UIViewsetAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES];

参数UIViewAnimationTransitionCurlUp代表向上翻页,如果向下的话UIViewAnimationTransitionCurlDown.
forView那把当前的视图传进去。
//*后提交动画
[UIView commitAnimations];

自定义按钮
UIButton *Btn;
CGRect frame;
Btn = [[UIButton buttonWithType:UIButtonTypeCustom] retain]; //按钮的类型    
[Btn setImage:[UIImage imageNamed:@“aaa.png”] forState:UIControlStateNormal];//设置按钮图片
Btn.tag = 10;
frame.size.width = 59;  //设置按钮的宽度
frame.size.height = 59;   //设置按钮的高度
frame.origin.x =150;   //设置按钮的位置
frame.origin.y =260;
[Btn setFrame:frame];
[Btn setBackgroundColor:[UIColor clearColor]];
  [Btn addTarget:self action:@selector(btnPressed:)forControlEvents:UIControlEventTouchUpInside];   //按钮的单击事件    
[self.view addSubview:Btn];
[Btn release];
-(void)btnPressed:(id)sender {
//在这里实现按钮的单击事件
}

使用NSTimer与iphone的简单动画,实现飘雪效果
使用NSTimer与iphone的简单动画,实现飘雪效果,这理原理比较简单,就是定时生成一定的雪花图片,然后使用动画的方式向下漂落(我在其它论坛,看到使用path的方式实现的一个云漂来漂去的效果,实际也可以用那种方式实现,这实际就是前面说的动画效果的两种应用)。所以,我们可以在 viewDidLoad事件中,增加一个图片及定时器并启动,这里的pic请在头文件中定义。
-(void)viewDidLoad{
[super viewDidLoad];
self.pic = [UIImage imageNamed:@”snow.png”];//初始化图片
//启动定时器,实现飘雪效果
[NSTimer scheduledTimerWithTimeInterval:(0.2) target:self selector:@selector(ontime) userInfo:nil repeats:YES];
}
然后再实现定时器定时调用的ontime方法:
-(void)ontime{
UIImageView *view = [[UIImageView alloc] initWithImage:pic];//声明一个UIImageView对象,用来添加图片
view.alpha = 0.5;//设置该view的alpha为0.5,半透明的
int x = round(random()%320);//随机得到该图片的x坐标
int y = round(random()%320);//这个是该图片移动的*后坐标x轴的
int s = round(random()%15)+10;//这个是定义雪花图片的大小
int sp = 1/round(random()%100)+1;//这个是速度
view.frame = CGRectMake(x, -50, s, s);//雪花开始的大小和位置
[self.view addSubview:view];//添加该view
[UIView beginAnimations:nil context:view];//开始动画
[UIView setAnimationDuration:10*sp];//设定速度
view.frame = CGRectMake(y, 500, s, s);//设定该雪花*后的消失坐标
[UIView setAnimationDelegate:self];
[UIView commitAnimations];
}
 
 
使用NSTimer实现倒计时
今天在CocoaChina上面看到有人在问倒计时怎么做,记得以前在看Iphone31天的时候做过一个,今天翻出来运行不了了,原因是我的IphoneSDK升级到3.1了,以前使用的是2.2.1,在2.2.1里面是可以使用NSCalendarDate的,但是在3.1里面不能够使用,怎么办,只好用NSTimer了,*后还是给实现了。代码也比较简单,开始运行viewDidLoad的时候加载 [NSTimerscheduledTimerWithTimeInterval:1.0 target:selfselector:@selector(timerFireMethod:) userInfo:nilrepeats:YES];//使用timer定时,每秒触发一次
,然后就是写selector了。

-(void)timerFireMethod:(NSTimer*)theTimer
{
//NSDateFormatter *dateformatter =[[[NSDateFormatter alloc]init]autorelease];//定义NSDateFormatter用来显示格式
//[dateformatter setDateFormat:@”yyyy MM dd hh mmss”];//设定格式
NSCalendar *cal = [NSCalendar   currentCalendar];//定义一个NSCalendar对象
NSDateComponents *shibo = [[NSDateComponents  alloc] init];//初始化目标时间(好像是世博会的日期)
[shibo setYear:2010];
[shibo setMonth:5];
[shibo setDay:1];
[shibo setHour:8];
[shibo setMinute:0];
[shibo setSecond:0];

%title插图%num

 NSDate *todate = [cal   dateFromComponents:shibo];//把目标时间装载入date
[shibo release];
// NSString *ssss = [dateformatter   stringFromDate:dd];
// NSLog([NSString stringWithFormat:@”shiboshi:%@”,ssss]);

NSDate *today = [NSDate date];//得到当前时间
// NSString *sss = [dateformatter  stringFromDate:today];
// NSLog([NSString stringWithFormat:@”xianzaishi:%@”,sss]);
//用来得到具体的时差
unsigned int unitFlags = NSYearCalendarUnit |NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit |NSMinuteCalendarUnit | NSSecondCalendarUnit;
NSDateComponents *d = [cal components:unitFlags   fromDate:today toDate:todate options:0];
lab.text = [NSStringstringWithFormat:@”%d年%d月%d日%d时%d分%d秒”,[d year],[d month], [d day],[d hour], [d minute], [d second]];
}
这样就实现了倒计时的功能。

Iphone幻灯片效果+背景音乐
今天弄了几张好看的图片,我就摸索着实现了图片的幻灯片效果,这个以前也实现过了,也算是温故知新吧,另外就是使用SoundEngine类实现背景音乐的播放。SoundEngine类可以从[url=read.php?tid-1215.html]http://www.cocoachina.com/bbs/read.php?tid-1215.html[/url]下载到。

代码很简单贴出来,以备不时只需:
-(void)viewDidLoad
{
array = [[NSMutableArray alloc] init];
int i = 1;
for(i;i<=30;i++)
{
[array addObject:[UIImageimageNamed:[NSString stringWithFormat:@”%d.jpg”,i]]];
}
pictures.animationImages = array;
pictures.animationDuration = 300;//时间间隔
pictures.animationRepeatCount = 0;//循环播放
[pictures startAnimating];//开始播放

//播放背景音乐,利用SoundEngine类进行播放
SoundEngine_SetListenerPosition(0.0, 0.0,1.0);
SoundEngine_Initialize(44100);
SoundEngine_LoadBackgroundMusicTrack([[[NSBundlemainBundle] pathForResource:@”win” ofType:@”caf”] UTF8String],true, true);
SoundEngine_StartBackgroundMusic();
}
用这种方法播放好像挺占用资源的,比较卡,以后再研究研究其它的方法。

%title插图%num

NSTimer的用法

iPhone为我们提供了一个很强大得时间定时器 NSTimer,它可以完成任何定时功能:
我们使用起来也很简单,只要记住三要素就可以,具体得三要素是:时间间隔NSTimeInterval浮点型,事件代理delegate和事件处理方法@selector();
就可以用
1 +(NSTimer *)scheduledTimerWithTimeIn
2 terval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
[/pre]来初始化一个 时间定时器
下面我写了一个很简单得例子:
-(void)initTimer
{
//时间间隔4 NSTimeInterval timeInterval =1.0;
//定时器6 NSTimer   showTimer =[NSTimer scheduledTimerWithTimeInterval:maxShowTime
target:self
selector:@selector(handleMaxShowTimer:)
userInfo:nil
repeats:NO];
}
//触发事件13 -(void)handleMaxShowTimer:(NSTimer *)theTimer
{
NSDateFormatter dateFormator =[[NSDateFormatter alloc] init];
dateFormator.dateFormat =@”yyyy-MM-dd  HH:mm:ss”;
NSString *date =[dateformater stringFromDate:[NSDate date]];
if([date isEqualToString:@”2010-11-09 23:59:59″])
{
UIAlertView *alert =[[UIAlertView alloc] initWithTitle:TITLE_NAME
message:@”现在马上就有新的一天了!”22 delegate:self
cancelButtonTitle:nil
otherButtonTitles:CONFIRM_TITLE, nil];
[alert show];
[alert release];
}
[data release];
[dateFormator release];
}

 
 
iphone开发之 - 启动页面设置
       不管是开发个人项目还是公司项目,大家通常都有一个需求,就是,在app启动的时候,指定一定的时间来显示自己的或者公司的logo,那么,我就将刚刚写好的启动加载页面设置代码贡献出来。(不对指出请留言,好的话也给我留个言吧,鼓励下我!呵呵)
这里我需要用到NSTimer这个东西,相关的内容可以查看API,有比较详细的解释。
新建一个项目,随便是什么项目,我建立的是“view based application”,然后,命名为“Logo”,然后确定。
直接编辑“Resources”目录下的”LogoViewController.xib”。将背景颜色改称绿色,主要是为了当从logo页跳转过来的时候能有感觉到变化。
然后新建一个NSTimer.

logoviewcon*lo = [[logoviewcon  alloc] initWithNibName:@”logoviewcon”bundle:nil];
self.logo = lo;
[lo release];
[window   addSubview:self.logo.view];
//初始化timmer
NSTimer*timer =  [NSTimer   scheduledTimerWithTimeInterval: 1.5target: selfselector: @selector(logo:) userInfo: nil   repeats: YES];
注意,初始化的代码有这么一段:@selector(logo:),其的方法就是当这个1.5秒时间过去之后自动调用的方法

-(void) logo:(NSTimer*)timer{
[logo.view removeFromSuperview];
[timer invalidate];//这句代码用来终止timmer,否则,每过1.5秒,就会执行该方法一次,我们是要在开始的时候执行一次就够了。
}

iphone 学习笔记

1。隐藏状态栏[[UIApplication sharedApplication] setStatusBarHidden:YES];

/******************************************************************************
1、取随机数:
NSData *datanow = [NSData data];
int i = (int)datanow;
srandom(i);
rand();
//int effectPicNum = random()%7;
******************************************************************************/
/******************************************************************************
2、播放音乐:
-(void) playMusic
{
@try{
//取文件路径
NSString *musicFilePath = [[NSBundle mainBundle] pathForResource:@”startLogo” ofType:@”mp3″];
NSURL *musicURL = [[NSURL alloc] initFileURLWithPath:musicFilePath];
musicPlayer= [[AVAudioPlayer   alloc] initWithContentsOfURL: musicURL   error:nil];
[musicURL   release];
//[musicPlayer  prepareToPlay];
//[musicPlayer  setVolume:1];            //设置音量大小
musicPlayer.numberOfLoops= 0; //设置播放次数,-1为一直循环,0为一次
[musicPlayerplay];
}
@catch(NSException* e) {
}
}
******************************************************************************/
/******************************************************************************
3、每隔0.8秒执行timeCount方法:
NSTimer*countTimer;
countTimer= [NSTimer    scheduledTimerWithTimeInterval: 0.8target: selfselector: @selector(timeCount:)  userInfo: nilrepeats: YES];
[countTimer  fire];     //执行timer
******************************************************************************/
/******************************************************************************
4、延迟1秒执行test方法:
[self   performSelector:@selector(test) withObject:nil  afterDelay:0.1];
******************************************************************************/
/******************************************************************************
5、启动线程:
[NSThread    detachNewThreadSelector:@selector(transImage)  toTarget:self    withObject:nil];
timer=[NSTimer    scheduledTimerWithTimeInterval:0.03   target:self   selector:@selector(TimerClock:) userInfo:nil  repeats:YES]; //启动一个NSTimer执行广播
[timerfire];  //执行timer

-(void)TimerClock:(id)sender
{
//控制延迟触发
if(Timecontrol>1) {
[timerCondition  broadcast];      //广播,触发处于等待状态的timerCondition
}
}

-(void)transImage
{
isRunning=YES;
while (countTime < COUNTTIME) {
[timerCondition  wait];
lim += 255 / (2 * KFrame);
[self  processImage];
countTime += 1000 / KFrame;
}
[timerinvalidate];
isRunning=NO;
}
******************************************************************************/
/******************************************************************************
6、获取文件路径:
//通过NSHomeDirectory获得文件路径
NSString *homeDirectory = NSHomeDirectory();
NSString *fileDirectory = [homeDirectory stringByAppendingPathComponent:@”temp/app_data.plist”];

//使用NSSearchPathForDirectoriesInDomains检索指定路径
NSArray*path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//常量NSDocumentDirectory表示正在查找Documents目录的路径(使用NSCachesDirectory表明要查找的时Caches文件夹),常量NSUserDomainMask表明我们希望将搜索限制于我们应用程序的沙盒,*后一个参数决定了是否“展开”波浪线符号。
//在Mac系统中,‘~’表示主路经(Home),如果不展开,路径看起来就是:‘~/Documents’,展开后即得到完整路径。这个参数一直设置位真即可。
NSString *documentsDirectory = [paths objectAtIndex:0];z
NSString *fileDirectory = [documentsDirectory stringByAppendingPathComponent:@”file.txt”];

//使用Foundation中的NSTemporaryDirectory函数直接返回代表temp文件夹的全路径的字符串对象
NSString *tempDirectory = NSTemporaryDirectory();
NSString*file = [tempDirectory stringByAppendingPathComponent:@”file.txt”];

Example:
NSArray*path = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *docDir = [path objectAtIndex:0];
NSLog(@”filepath:%@”,docDir);
NSString*str = @”hello.jpg”;
NSString*filepath = [docDir stringByAppendingPathComponent:str];
//NSString *filepath = [docDir stringByAppendingPathComponent:[NSString stringWithUTF8String:”///mest.txt”]];
NSLog(@”filepath:%@”,filepath);
BOOLsuccess = [[NSFileManagerdefaultManager]createFileAtPath: filepath contents:nilattributes:nil];
NSLog(@”result”,success);
printf(“Create File:%s %s.”,[filepath UTF8String], success ? “Success”: “Error”);

NSString* reValue= [NSString stringWithString:@”\”success\””];
NSLog(reValue);
******************************************************************************/
/************************************************************************************************************************************************************
7文件、文件夹操作
//如果”/Documents/Theme”路径不存在,则创建。
if(![[NSFileManagerdefaultManager]fileExistsAtPath:themePath])
{
[[NSFileManagerdefaultManager] createDirectoryAtPath:themePath attributes:nil];
}
//删除已存在的同名文件夹
if([[NSFileManagerdefaultManager] fileExistsAtPath:savePath]) {
[[NSFileManagerdefaultManager] removeItemAtPath:savePath error:NULL];
}
************************************************************************************************************************************************************/
/************************************************************************************************************************************************************
7 子线程抛给主线程:
[selfperformSelectorOnMainThread:@selector(shiftView) withObject:nilwaitUntilDone:YES];

************************************************************************************************************************************************************/
/************************************************************************************************************************************************************
8获取当前时间
NSDateFormatter*formatter = [[NSDateFormatteralloc] init];
[formatter setDateFormat:@”yyyy-MM-dd hh:mm:ss”];
NSString *locationString=[formatter stringFromDate: [NSDate date]];

//获取当前时间作为productId
NSDateFormatter*formatter = [[NSDateFormatteralloc] init];
[formatter setDateFormat:@”hhmmss”];
NSString *locationString=[formatter stringFromDate: [NSDate date]];
downloadInfo.productId = locationString;
[formatter release];
/******************************************************************************
函数名称  : getDate
函数描述  : 获取当前日期时间
输入参数  : N/A
输出参数  : N/A
返回值    : NSString 当前时间
备注     :
******************************************************************************/
-(NSString *)getDate
{
NSDateFormatter*formatter = [[NSDateFormatteralloc] init];
[formatter setDateFormat:@”yyyy-MM-dd EEEE HH:mm:ss a”];
NSString *locationString=[formatter stringFromDate: [NSDate date]];
[formatter release];
return locationString;
}
大写的H日期格式将默认为24小时制,小写的h日期格式将默认为12小时
不需要特别设置,只需要在dataFormat里设置类似”yyyy-MMM-dd”这样的格式就可以了
日期格式如下:
y  年  Year  1996; 96
M  年中的月份  Month  July; Jul; 07
w  年中的周数  Number  27
W  月份中的周数  Number  2
D  年中的天数  Number  189
d  月份中的天数  Number  10
F  月份中的星期  Number  2
E  星期中的天数  Text  Tuesday; Tue
a  Am/pm 标记  Text  PM
H  一天中的小时数(0-23)  Number  0
k  一天中的小时数(1-24)  Number  24
K  am/pm 中的小时数(0-11)  Number  0
h  am/pm 中的小时数(1-12)  Number  12
m  小时中的分钟数  Number  30
s  分钟中的秒数  Number  55
S  毫秒数  Number  978
z  时区  General time zone  Pacific Standard Time; PST; GMT-08:00
Z  时区  RFC 822 time zone  -0800
************************************************************************************************************************************************************/
/************************************************************************************************************************************************************
读取和写入plist文件

plist文件是标准的xml文件,在cocoa中可以很简单地使用。这里介绍一下使用方法: 以下代码在Mac和iPhone中均适用。
写入plist文件: NSMutableDictionary * dict = [ [ NSMutableDictionary alloc ] initWith

plist文件是标准的xml文件,在cocoa中可以很简单地使用。这里介绍一下使用方法:

以下代码在Mac和iPhone中均适用。

写入plist文件:
NSMutableDictionary* dict = [ [ NSMutableDictionaryalloc ] initWithContentsOfFile:@”/Sample.plist”];
[ dict setObject:@”Yes”forKey:@”RestartSpringBoard”];
[ dict writeToFile:@”/Sample.plist”atomically:YES];

读取plist文件:

NSMutableDictionary* dict =  [ [ NSMutableDictionaryalloc ] initWithContentsOfFile:@”/Sample.plist”];
NSString* object = [ dict objectForKey:@”RestartSpringBoard” ];
************************************************************************************************************************************************************/

UIView翻转效果实现

新建一个view-based模板工程,在ViewController文件中添加下面的代码,即可实现翻转效果;

– (void)viewDidLoad {
[super viewDidLoad];
//需要翻转的视图

UIView *parentView = [[UIView alloc] initWithFrame:CGRectMake(0, 150, 320, 200)];
parentView.backgroundColor = [UIColor yellowColor];
parentView.tag = 1000;

[self.view addSubview:parentView];
}

//需要在h头文件声明下面的动作响应函数
//在xib文件中添加一个button,其响应函数为下面的函数
//运行程序后,点击button就看到翻转效果
-(IBAction)ActionFanzhuan{

//获取当前画图的设备上下文
CGContextRef context = UIGraphicsGetCurrentContext();

//开始准备动画
[UIView beginAnimations:nil context:context];

//设置动画曲线,翻译不准,见苹果官方文档
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

//设置动画持续时间
[UIView setAnimationDuration:1.0];

//因为没给viewController类添加成员变量,所以用下面方法得到viewDidLoad添加的子视图
UIView *parentView = [self.view viewWithTag:1000];

//设置动画效果

[UIView setAnimationTransition: UIViewAnimationTransitionCurlDown forView:parentView cache:YES];  //从上向下
// [UIView setAnimationTransition: UIViewAnimationTransitionCurlUp forView:parentView cache:YES];   //从下向上
// [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft forView:parentView cache:YES];  //从左向右
// [UIView setAnimationTransition: UIViewAnimationTransitionFlipFromRight forView:parentView cache:YES];//从右向左

//设置动画委托

[UIView setAnimationDelegate:self];

//当动画执行结束,执行animationFinished方法
[UIView setAnimationDidStopSelector:@selector(animationFinished:)];

//提交动画
[UIView commitAnimations];

}

//动画效果执行完毕
– (void) animationFinished: (id) sender{
NSLog(@”animationFinished !”);
}

运行程序,点击按钮,就能看到动画效果了

   iPhone 实现动画效果
iPhone中实现动画,主要有两种方式:UIView的动画块和Core Animation的CATransition类。

1、UIView的动画块
之所以称为动画块,是因为UView动画是成块运行的,也就是说作为完整的事务一次性运行。
beginAnimation:context:标志动画块开始;
commitAnimations标志动画块结束。(这个commit多少已经暗示这个操作是事务性的)
这里面通常涉及4个操作:
beginAnimation:context:标志动画块开始
setAnimationCurve:定义动画加速或减速的方式,有四种,ease-in/ease-out,ease-in,linear,ease-out
setAnimationDuration:定义动画持续时间(以秒为单位)
commitAnimations:标志动画块结束
所有这些操作都是针对UIView的,或者说是UIView的类函数。
给段代码示例:
1.    CGContextRef context = UIGraphicsGetCurrentContext();[UIView beginAnimations:nil context:context];[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];[UIView setAnimationDuration:2.0f];[UIView setAnimationBeginsFromCurrentState:YES];[UIView setAnimationDelegate:self];[UIView setAnimationDidStopSelector:@selector(animationFinished:)];[self.imageView setTransform:CGAffineTransformMakeScale(0.25f, 0.25f)];[UIView commitAnimations];

(这里面设置了动画的delegate,在动画结束后执行animationFinished:函数)

UIView除了实现上面这种简单的动画,还支持视图的翻转。例如在上面代码的[UIView commitAnimations]前加上下面这句,便可以实现视图的翻转(翻转后的试图中,imageView的大小变为原来的0.25倍):

[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:YES];
其中,参数UIViewAnimationTransitionFlipFromLeft定义了翻转的方式。

 
 
 
关于UIView的userInteractionEnabled属性

如果父视图为ParentView包含一个Button,如果再ParentView上添加子视图ChildView,且ChildView盖住了Button,
那么Button就得到不响应了,为了让Button响应,可以设置ChildView的userInteractionEnabled = NO;
*近被这个问题困扰了很久,开始想用事件传递的方法,重写类继承自UIView,*后被这简单属性搞定了….

 
 
让一个UIImageView响应点击事件

UIImageView *imgView =[[UIImageView alloc] initWithFrame:CGRectMake(0, 0,320, 44)];
imgView.userInteractionEnabled=YES;
UITapGestureRecognizer *singleTap =[[UITapGestureRecognizer alloc]initWithTarget:selfaction:@selector(onClickImage)];
[imgView addGestureRecognizer:singleTap];
[singleTap release];

-(void)onClickImage{
// here, do whatever you wantto do
NSLog(@”imageview is clicked!”);
}

 
iphone调用系统电话、浏览器、地图、邮件等
openURL的使用方法:
view plaincopy toclipboardprint?
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:appString]];
其中系统的appString有:
view plaincopy toclipboardprint?
1.Map    http://maps.google.com/maps?q=Shanghai
2.Email  mailto://myname@google.com
3.Tel    tel://10086
4.Msg    sms://10086
openURL能帮助你运行Maps,SMS,Browser,Phone甚至其他的应用程序。这是Iphone开发中我经常需要用到的一段代码,它仅仅只有一行而已。
– (IBAction)openMaps {
//打开地图
NSString*addressText = @”beijing”;
//@”1Infinite Loop, Cupertino, CA 95014″;
addressText =[addressTextstringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
NSString*urlText = [NSStringstringWithFormat:@”http://maps.google.com/maps?q=%@”,addressText];
NSLog(@”urlText=============== %@”, urlText);
[[UIApplicationsharedApplication] openURL:[NSURL URLWithString:urlText]];
}

– (IBAction)openEmail {
//打开mail // Fire off an email to apple support
[[UIApplication sharedApplication]openURL:[NSURL   URLWithString:@”mailto://devprograms@apple.com”]];
}

– (IBAction)openPhone {

//拨打电话
// CallGoogle 411
[[UIApplication sharedApplication] openURL:[NSURLURLWithString:@”tel://8004664411″]];
}

– (IBAction)openSms {
//打开短信
// Text toGoogle SMS
[[UIApplication sharedApplication] openURL:[NSURLURLWithString:@”sms://466453″]];
}

-(IBAction)openBrowser {
//打开浏览器
// Lanuch any iPhone developers fav site
[[UIApplication sharedApplication] openURL:[NSURLURLWithString:@”http://itunesconnect.apple.com”]];
}       

 
 
 
iphone程序内调用谷歌地图

使用CLLocationManager类,MKMapView。并且实现<MKMapViewDelegate,CLLocationManagerDelegate>
//初始化CLLocationManager,CLLocationManager获得当前地理坐标
locmanager=[[CLLocationManager alloc]init];

[locmanager setDelegate:self];
//设置精确度
[locmanager setDesiredAccuracy:kCLLocationAccuracyBest];

[locmanagerstartUpdatingLocation];
执行完以后,会自动调用代理方法:

在代理方法:

– (void)locationManager:(CLLocationManager *)managerdidUpdateToLocation:(CLLocation*)newLocation fromLocation:(CLLocation *)oldLocation{
//初始化矩形大小
CGRect rect=CGRectMake(0,0,320,460);
//设置地图大小为矩形大小
map=[[MKMapView alloc] initWithFrame:rect];

CLLocationCoordinate2Dloc=[newLocation coordinate];
lat=loc.latitude;
lon=loc.longitude;

//coordinate坐标
CLLocationCoordinate2DtheCoordinate;
CLLocationCoordinate2DtheCenter;

//theCoordinate.latitude=lat;
//theCoordinate.longitude=lon;
theCoordinate=loc;
[map setDelegate:self];
//设置地图显示的类型,有卫星地图,道路,等
[map setMapType:MKMapTypeStandard];
//[mapsetMapType:MKMapTypeSatellite];
//区域坐标Region(区域,地域)

MKCoordinateRegiontheRegin;
//theCenter.latitude=lat;
//theCenter.longitude=lon;
theCenter=loc;
theRegin.center=theCenter;

//坐标间距(span:间隔,间距)
MKCoordinateSpantheSpan;

theSpan.latitudeDelta=0.1;
theSpan.longitudeDelta=0.1;
//设置地图显示的区域,
theRegin.span=theSpan;
//[mapsetRegion:theRegin];
[map regionThatFits:theRegin];
map.showsUserLocation=YES;
[self.viewaddSubview:map];

}


– (MKAnnotationView *)mapView:(MKMapView *)mapViewviewForAnnotation:(id<MKAnnotation>)annotation{
NSLog(@”——-viewForAnnotation——-“);
//此类可以显示针一样的图标
MKPinAnnotationView *newAnnotation=[[MKPinAnnotationView  alloc] initWithAnnotation:annotationreuseIdentifier:@”annotation1”];

//newAnnotation.animatesDrop=YES;
//newAnnotation.animatesDrop=NO;

newAnnotation.pinColor=MKPinAnnotationColorPurple;
//显示标志提示
newAnnotation.canShowCallout=YES;
return newAnnotation;
}    

 
 
UIPageControl实现自定义按钮
有时候UIPageControl需要用到白色的背景, 那么会导致上面的点按钮看不见或不清楚,
我们可以通过继承该类重写函数来更换点按钮的图片现实.
实现思路如下.
新建类继承UIPageControl :
@interface MyPageControl : UIPageControl
{
UIImage*imagePageStateNormal;
UIImage*imagePageStateHighlighted;
}
– (id)initWithFrame:(CGRect)frame;
@property (nonatomic, retain) UIImage*imagePageStateNormal;
@property (nonatomic, retain) UIImage*imagePageStateHighlighted;
@end

声明了初始化该类的函数
用了两个UIImage保存两张图片, 大家知道的, UIPageCotrol的按钮分为两态, 一个是正常, 一个是高亮
接下来实现该类以及重写父类方法:
@interfaceMyPageControl(private)  // 声明一个私有方法, 该方法不允许对象直接使用
– (void)updateDots;
@end

@implementation MyPageControl  //实现部分

@synthesize imagePageStateNormal;
@synthesize imagePageStateHighlighted;

– (id)initWithFrame:(CGRect)frame { // 初始化
self = [superinitWithFrame:frame];
return self;
}

– (void)setImagePageStateNormal:(UIImage*)image {  // 设置正常状态点按钮的图片
[imagePageStateNormal   release];
imagePageStateNormal= [image retain];
[self updateDots];
}

-(void)setImagePageStateHighlighted:(UIImage *)image { // 设置高亮状态点按钮图片
[imagePageStateHighlightedrelease];
imagePageStateHighlighted= [image retain];
[self updateDots];
}

– (void)endTrackingWithTouch:(UITouch*)touch withEvent:(UIEvent *)event { // 点击事件
[superendTrackingWithTouch:touch withEvent:event];
[self updateDots];
}

– (void)updateDots { // 更新显示所有的点按钮
if(imagePageStateNormal || imagePageStateHighlighted)
{
NSArray*subview = self.subviews;  // 获取所有子视图
for(NSInteger i = 0; i < [subview count]; i++)
{
UIImageView*dot = [subview objectAtIndex:i];  // 以下不解释, 看了基本明白
dot.image= self.currentPage == i ? imagePageStateNormal : imagePageStateHighlighted;
}
}
}

– (void)dealloc { // 释放内存
[imagePageStateNormalrelease], imagePageStateNormal = nil;
[imagePageStateHighlightedrelease], imagePageStateHighlighted = nil;
[super dealloc];
}

@end
OK, 在添加处加入以下来实例化该对象代码:
MyPageControl *pageControl =[[MyPageControl alloc] initWithFrame:CGRectMake(0,0, 200, 30)];
pageControl.backgroundColor = [UIColorclearColor];
pageControl.numberOfPages = 5;
pageControl.currentPage = 0;
[pageControlsetImagePageStateNormal:[UIImageimageNamed:@”pageControlStateNormal.png”]];
[pageControl setImagePageStateHighlighted:[UIImageimageNamed:@”pageControlStateHighlighted.png”]];
[self.view addSubview:pageControl];
[pageControl release];     

 
    
iPhone电子书toolbar的实现
iPhone电子书的toolbar一般都设计成半透明,上面放置一个进度条和一个Label(用于显示页码),这里用代码做一个*基本的实现。
生成一个UIToolbar
UIToolbar *toolbar =[[[UIToolbar alloc] init] autorelease];
toolbar.barStyle=UIBarStyleBlackTranslucent;
[toolbar sizeToFit];
CGFloat toolbarHeight =[toolbar frame].size.height;
CGRect rootViewBounds =self.parentViewController.view.bounds;
CGFloat rootViewHeight =CGRectGetHeight(rootViewBounds);
CGFloat rootViewWidth =CGRectGetWidth(rootViewBounds);
CGRect rectArea = CGRectMake(0, rootViewHeight-toolbarHeight,rootViewWidth, toolbarHeight);
[toolbar setFrame:rectArea];
toolbar.backgroundColor= [UIColor clearColor];

生成一个Slider
UISlider*readSlider =[[[UISlideralloc]initWithFrame:CGRectMake(0,0, 225,30)] autorelease];
readSlider.minimumValue = 0.0f;
readSlider.maximumValue = 1.0f;
readSlider.continuous = YES;
readSlider.enabled = YES;

生成一个Label
UILabel*readLabel =[[[UILabelalloc]initWithFrame:CGRectMake(230,0, 50,30)] autorelease];
readLabel.backgroundColor = [UIColor clearColor];
readLabel.textColor =[UIColor whiteColor];

Slider和Label加入到toolbar中
NSMutableArray *tbitems =[NSMutableArray array];
[tbitems addObject:[[[UIBarButtonItem alloc]initWithCustomView:readSlider] autorelease]];
[tbitems addObject:[[[UIBarButtonItemalloc] initWithCustomView:readLabel]autorelease]];
toolbar.items = tbitems;

toolbar加入到当前view中
[self.navigationController.view addSubview:toolbar];

点击屏幕即隐藏的功能,将toolbar的hidden属性置为YES即可

toolBar.hidden = YES;

 
 
 
UIView中bounds和frame的差别?
翻译文档上的
bounds是指这个view在它自己坐标系的坐标和大小 而frame指的是这个view在它superview的坐标系的坐标和大小
区别主要在坐标系这一块。
很明显一个是自己为原点的坐标系,一个是以屏幕为原点的坐标系。*对坐标。。。相对坐标。。。比如屏幕旋转的时候就要以相对来重绘。
frame 如果一个按钮,是在表格里,按钮的frame 的坐标也是相对的,并不是相对屏幕,也就是说是相对坐标,不是*对坐标
我也想知道任何一个uiview如何求得它在屏幕上的坐标。
view 的frame是view在它的super view 的位置与尺寸。
view 的bounds可以用来帮助它的subview来定位的 ,layoutSubviews。
Frame  is  in  terms  of  superview’s  coordinate  system
框架是从父视图的坐标系统
Bounds   is  in  terms  of   local  coordinate  system
是在局部坐标系统

%title插图%num

很明显,bounds的原点是(0,0)点,而frame的原点却是任意的。
frame 如果一个按钮,是在表格里,按钮的frame 的坐标也是相对的,并不是相对屏幕,也就是说是相对坐标,不是*对坐标。
frame 是相对坐标。bounds是*对坐标。
Android的开发过程中,*对坐标,这样画出来的位置都是相对于屏幕的而不是相对于控件的

什么是*对坐标值,相对坐标值?
*对坐标是:X,Y    就是相对于坐标原点的。
例如(15,20)相对坐标是:@X,Y   就是相对于参考点(可以是自己设定的一个点)。
例如(15,20)相对于参考点(1,1)的坐标,表示:@14,19
(15,20)相对于参考点(-1,-1)的坐标,表示:@16,21
bounds指这个view在它自己坐标系的坐标和大小 而frame指这个view在它superview的坐标系的坐标和大小.
区别主要在坐标系这一块。很明显一个是自己为原点的坐标系,一个是以屏幕为原点的坐标系。

%title插图%num


 
设置透明度
[myView setAlpha:value];   (0.0 < value < 1.0)

设置背景色    [myView setBackgroundColor:[UIColor redColor]];
                    (blackColor;darkGrayColor;lightGrayColor;whiteColor;grayColor; redColor; greenColor; blueColor; cyanColor;yellowColor;magentaColor;orangeColor;purpleColor;brownColor; clearColor; )

自定义颜色: UIColor *newColor = [[UIColor alloc] initWithRed:(float) green:(float) blue:(float) alpha:(float)];      0.0~1.0
宽度和高度 :  
 768X1024     1024X768    状态栏高 20 像素高   导航栏 工具栏 44像素高

 
 
iOS开发_iphone开发_iphone界面如何实现下拉列表

代码如下:
#import <UIKit/UIKit.h>
@interface DropDownList : UIView<UITableViewDelegate,UITableViewDataSource> {
UITextField* textField;   //文本输入框
NSArray* list;            //下拉列表数据
BOOL showList;             //是否弹出下拉列表
UITableView* listView;    //下拉列表
CGRect oldFrame,newFrame;   //整个控件(包括下拉前和下拉后)的矩形
UIColor *lineColor,*listBgColor;//下拉框的边框色、背景色
CGFloat lineWidth;               //下拉框边框粗细
UITextBorderStyle borderStyle;   //文本框边框style
}
@property (nonatomic,retain)UITextField *textField;
@property (nonatomic,retain)NSArray* list;
@property (nonatomic,retain)UITableView* listView;
@property (nonatomic,retain)UIColor *lineColor,*listBgColor;
@property (nonatomic,assign)UITextBorderStyle borderStyle;
-(void)drawView;
-(void)setShowList:(BOOL)b;
@end
#import “DropDownList.h”
@implementation DropDownList
@synthesize textField,list,listView,lineColor,listBgColor,borderStyle;
– (id)initWithFrame:(CGRect)frame {

if(self=[super initWithFrame:frame]){
//默认的下拉列表中的数据
list=[[NSArray alloc]initWithObjects:@”1″,@”2″,@”3″,@”4″,nil];

borderStyle=UITextBorderStyleRoundedRect;

showList=NO; //默认不显示下拉框
oldFrame=frame; //未下拉时控件初始大小
//当下拉框显示时,计算出控件的大小。
newFrame=CGRectMake(frame.origin.x, frame.origin.y, frame.size.width, frame.size.height*5);

lineColor=[UIColor lightGrayColor];//默认列表边框线为灰色
listBgColor=[UIColor whiteColor];//默认列表框背景色为白色
lineWidth=1;     //默认列表边框粗细为1

//把背景色设置为透明色,否则会有一个黑色的边
self.backgroundColor=[UIColor clearColor];
[self drawView];//调用方法,绘制控件

}
return self;
}
-(void)drawView{
//文本框
textField=[[UITextField alloc]
initWithFrame:CGRectMake(0, 0,
oldFrame.size.width,
oldFrame.size.height)];
textField.borderStyle=borderStyle;//设置文本框的边框风格
[self addSubview:textField];
[textField addTarget:self action:@selector(dropdown) forControlEvents:UIControlEventAllTouchEvents];

//下拉列表
listView=[[UITableView alloc]initWithFrame:
CGRectMake(lineWidth,oldFrame.size.height+lineWidth,
oldFrame.size.width-lineWidth*2,
oldFrame.size.height*4-lineWidth*2)];
listView.dataSource=self;
listView.delegate=self;
listView.backgroundColor=listBgColor;
listView.separatorColor=lineColor;
listView.hidden=!showList;//一开始listView是隐藏的,此后根据showList的值显示或隐藏

[self addSubview:listView];
[listView release];
}
-(void)dropdown{
[textField resignFirstResponder];
if (showList) {//如果下拉框已显示,什么都不做
return;
}else {//如果下拉框尚未显示,则进行显示
//把dropdownList放到前面,防止下拉框被别的控件遮住

[self.superview bringSubviewToFront:self];
[self setShowList:YES];//显示下拉框
}
}
#pragma mark listViewdataSource method and delegate method
-(NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section{
return list.count;
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellid=@”listviewid”;
UITableViewCell* cell=[tableView dequeueReusableCellWithIdentifier:cellid];
if(cell==nil){
cell=[[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellid]autorelease];
}
//文本标签
cell.textLabel.text=(NSString*)[list objectAtIndex:indexPath.row];
cell.textLabel.font=textField.font;

cell.selectionStyle=UITableViewCellSelectionStyleGray;
return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return oldFrame.size.height;
}
//当选择下拉列表中的一行时,设置文本框中的值,隐藏下拉列表
-(void)tableView:(UITableView *)tableViewdidSelectRowAtIndexPath:(NSIndexPath *)indexPath{
//NSLog(@”select”);
textField.text=(NSString*)[list objectAtIndex:indexPath.row];
//NSLog(@”textField.text=%@”,textField.text);
[self setShowList:NO];
}
-(BOOL)showList{//setShowList:No为隐藏,setShowList:Yes为显示
return showList;
}
-(void)setShowList:(BOOL)b{
showList=b;
NSLog(@”showlist is set “);
if(showList){
self.frame=newFrame;
}else {
self.frame=oldFrame;
}
listView.hidden=!b;
}
/*

// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
– (void)drawRect:(CGRect)rect {
// Drawing code.
}
*/
– (void)dealloc {
[super dealloc];
}
@end

 
 
 
create toolbar using new
toolbar = [UIToolbar new];
toolbar.barStyle = UIBarStyleDefault;
[toolbar sizeToFit];
toolbar.frame = CGRectMake(0, 410, 320, 50);
 
 
iphone之UISegmentedControl
代码:
//选择按钮
NSArray*buttonNames = [NSArray arrayWithObjects:@”今天”, @”本周”, @”本月”,nil];
UISegmentedControl* segmentedControl = [[UISegmentedControl alloc]initWithItems:buttonNames];
[segmentedControl setFrame:CGRectMake(60, 10, 200, 40)];
segmentedControl.selectedSegmentIndex=1;
//添加事件
[segmentedControl addTarget:self action:@selector(segmentAction:)forControlEvents:UIControlEventValueChanged];
[self.viewaddSubview:segmentedControl];
[segmentedControl release];

//事件

-(void)segmentAction:(UISegmentedControl *)Seg{
NSIntegerIndex = Seg.selectedSegmentIndex;
NSLog(@”Seg.selectedSegmentIndex:%d”,Index);
}

 
 
iOS Programming – 触摸事件处理

iphone/ipad无键盘的设计是为屏幕争取更多的显示空间,大屏幕在观看图片、文字、视频等方面为用户带来了更好的用户体验。而触摸屏幕是iOS设备接受用户输入的主要方式,包括单击、双击、拨动以及多点触摸等,这些操作都会产生触摸事件。

在Cocoa中,代表触摸对象的类是UITouch。当用户触摸屏幕后,就会产生相应的事件,所有相关的UITouch对象都被包装在事件中,被程序交由特定的对象来处理。UITouch对象直接包括触摸的详细信息。
UITouch类中包含5个属性:
window:触摸产生时所处的窗口。由于窗口可能发生变化,当前所在的窗口不一定是*开始的窗口。
view:触摸产生时所处的视图。由于视图可能发生变化,当前视图也不一定时*初的视图。
tapCount:轻击(Tap)操作和鼠标的单击操作类似,tapCount表示短时间内轻击屏幕的次数。因此可以根据tapCount判断单击、双击或更多的轻击。
times*****p:时间戳记录了触摸事件产生或变化时的时间。单位是秒。
phase:触摸事件在屏幕上有一个周期,即触摸开始、触摸点移动、触摸结束,还有中途取消。而通过phase可以查看当前触摸事件在一个周期中所处的状态。phase是UITouchPhase类型的,这是一个枚举配型,包含了          · UITouchPhaseBegan(触摸开始)
· UITouchPhaseMoved(接触点移动)
· UITouchPhaseStationary(接触点无移动)
· UITouchPhaseEnded(触摸结束)
· UITouchPhaseCancelled(触摸取消)
UITouch类中包含如下成员函数:
– (CGPoint)locationInView:(UIView *)view:函数返回一个CGPoint类型的值,表示触摸在view这个视图上的位置,这里返回的位置是针对view的坐标系的。调用时传入的view参数为空的话,返回的时触摸点在整个窗口的位置。
– (CGPoint)previousLocationInView:(UIView *)view:该方法记录了前一个坐标值,函数返回也是一个CGPoint类型的值, 表示触摸在view这个视图上的位置,这里返回的位置是针对view的坐标系的。调用时传入的view参数为空的话,返回的时触摸点在整个窗口的位置。
当手指接触到屏幕,不管是单点触摸还是多点触摸,事件都会开始,直到用户所有的手指都离开屏幕。期间所有的UITouch对象都被包含在UIEvent事件对象中,由程序分发给处理者。事件记录了这个周期中所有触摸对象状态的变化。
只要屏幕被触摸,系统就会报若干个触摸的信息封装到UIEvent对象中发送给程序,由管理程序UIApplication对象将事件分发。一般来说,事件将被发给主窗口,然后传给*响应者对象(FirstResponder)处理。
关于响应者的概念,通过以下几点说明:
响应者对象(Response object)  响应者对象就是可以响应事件并对事件作出处理。在iOS中,存在UIResponder类,它定义了响应者对象的所有方法。UIApplication、UIView等类都继承了UIResponder类,UIWindow和UIKit中的控件因为继承了UIView,所以也间接继承了UIResponder类,这些类的实例都可以当作响应者。
*响应者(First responder)
当前接受触摸的响应者对象被称为*响应者,即表示当前该对象正在与用户交互,它是响应者链的开端。
响应者链(Responder chain)  响应者链表示一系列的响应者对象。事件被交由*响应者对象处理,如果*响应者不处理,事件被沿着响应者链向上传递,交给下一个响应者(next responder)。一般来说,*响应者是个视图对象或者其子类对象,当其被触摸后事件被交由它处理,如果它不处理,事件就会被传递给它的视图控制器对象(如果存在),然后是它的父视图(superview)对象(如果存在),以此类推,直到顶层视图。接下来会沿着顶层视图(top view)到窗口(UIWindow对象)再到程序(UIApplication对象)。如果整个过程都没有响应这个事件,该事件就被丢弃。一般情况下,在响应者链中只要由对象处理事件,事件就停止传递。但有时候可以在视图的响应方法中根据一些条件判断来决定是否需要继续传递事件。
管理事件分发  视图对触摸事件是否需要作处回应可以通过设置视图的userInteractionEnabled属性。默认状态为YES,如果设置为NO,可以阻止视图接收和分发触摸事件。除此之外,当视图被隐藏(setHidden:YES)或者透明(alpha值为0)也不会收事件。不过这个属性只对视图有效,如果想要整个程序都步响应事件,可以调用UIApplication的beginIngnoringInteractionEvents方法来完全停止事件接收和分发。通过endIngnoringInteractionEvents方法来恢复让程序接收和分发事件。
如果要让视图接收多点触摸,需要设置它的multipleTouchEnabled属性为YES,默认状态下这个属性值为NO,即视图默认不接收多点触摸。

 
键盘透明
textField.keyboardAppearance = UIKeyboardAppearanceAlert;
iPhone键盘改变颜色
只有这2种数字键盘才有效果:UIKeyboardTypeNumberPad,UIKeyboardTypePhonePad
keyboardAppearance = UIKeyboardAppearanceAlert
代码如下:

  1.    NSArray *ws = [[UIApplication sharedApplication] windows];
  2.     for(UIView *w in ws){
  3.         NSArray *vs = [w subviews];
  4.         for(UIView *v in vs){
  5.             if([[NSString stringWithUTF8String:object_getClassName(v)] isEqualToString:@”UIKeyboard”]){
  6.                 v.backgroundColor = [UIColor redColor];
  7.             }
  8.         }
  9.     }

从一个界面push到下一界面左上角返回按钮文字设置
在父viewController中如下设置:
UIBarButtonItem *backbutton = [[UIBarButtonItem alloc]init];
backbutton.title = @”返回列表”;
self.navigationItem.backBarButtonItem = backbutton;
[backbutton release];

更改cell选中的背景
UIView *myview = [[UIView alloc] init];
myview.frame = CGRectMake(0, 0, 320, 47);
myview.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@”0006.png”]];
cell.selectedBackgroundView = myview; 
 
navigationbar的back键触发其他事件
UIButton *back =[[UIButton alloc] initWithFrame:CGRectMake(200, 25, 63, 30)];
[back addTarget:self action:@selector(reloadRowData:) forControlEvents:UIControlEventTouchUpInside];
[back setImage:[UIImage imageNamed:@”返回按钮.png”] forState:UIControlStateNormal];
UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:back];
self.navigationItem.backBarButtonItem =backButtonItom;
[back release];
[backButtonItem release];
防止屏幕暗掉锁屏

[[UIApplication sharedApplication] setIdleTimerDisabled:YES];

将图片从左到右翻页效果显示

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 0, 470)];
[imageView setImage:[UIImage imageNamed:@”Bg.jpg”]];
self.myImageView =imageView;
[self.view addSubview:imageView];
[imageView release];
CGContextRef context = UIGraphicsGetCurrentContext();
[UIView beginAnimations:nil context:context];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.5];
[myImageView setFrame:CGRectMake(0, 0, 310, 470)];
[UIView commitAnimations];

让覆盖在下面层的视图接受触摸事件

searchImage.exclusiveTouch = YES;//*层
searchImage.userInteractionEnabled = NO;
myMapView.exclusiveTouch = NO;//第二层
myMapView.userInteractionEnabled = YES;

View的缩放

NSValue *touchPointValue = [[NSValue valueWithCGPoint:CGPointMake(100,100)] retain];
[UIView beginAnimations:nil context:touchPointValue];
transform = CGAffineTransformMakeScale(0.1,0.21);
firstPieceView.transform = transform;
[UIView commitAnimations];   

 
 
点击 UITextView 输入文字,光标都从*初点开始
能让用户点击 UITextView 输入文字时,光标都从*初点开始
– (void)textViewDidChangeSelection:(UITextView *)textView
{
NSRange range;
range.location = 0;
range.length  = 0;
textView.selectedRange = range;
 
 
UITextView在光标处添加文字
// 获得光标所在的位置
int location =contentTextView.selectedRange.location;
// 将UITextView中的内容进行调整(主要是在光标所在的位置进行字符串截取,再拼接你需要插入的文字即可)
NSString *content = contentTextView.text;
NSString *result = [NSStringstringWithFormat:@”%@[姓名变量]%@”,[contentsubstringToIndex:location],[contentsubstringFromIndex:location]];
// 将调整后的字符串添加到UITextView上面
contentTextView.text = result;
 
 
如何设置UITextView的光标位置
UITextView * m_textInput;
//设置光标到输入文字的末尾
NSUInteger length = m_textInput.text.length;
m_textInput.selectedRange = NSMakeRange(length,0);     
 
 
UITextView方法 用法
UITextView限制行数的问题之前试了好多方法,*终解决了,解决方法非常简单,在UITextViewDelegate中加下面的方法即可:
-(BOOL)textView:(UITextView *)textViewshouldChangeTextInRange:(NSRange)range
replacementText:(NSString*)text {
if (textView.contentSize.height > 104){
textView.text = [textView.text substringToIndex:[textView.textlength]-1];
returnNO;
}
return YES;
}
-(void)textViewDidChangeSelection:(UITextView*)textView
每次输入都知道
[textView becomeFirstResponder]
(void)textViewDidChange:(UITextView*)textView 当textView的内容发生改变时,会调用。。再此计算已经输入的字符个数。
– (BOOL)textView:(UITextView*)textView shouldChangeTextInRange:(NSRange)rangereplacementText:(NSString *)text; {
if([@”\n” isEqualToString:text] == YES) {
[textViewresignFirstResponder];
returnNO;
}
returnYES;
}
 
 
textview根据光标插入数据 
UITableViewCell *cell =  [tableView cellForRowAtIndexPath:indexPath];
//定位光标
NSRange range = [opinion selectedRange];
NSMutableString *top = [[NSMutableString alloc] initWithString:[opinion text]];
NSString *addName = [NSString stringWithFormat:@”%@、”,cell.textLabel.text];
[top insertString:addName atIndex:range.location];
opinion.text = top;
[top release];
 
iphone中的UITouch
手指在屏幕上能达到的精度和鼠标指针有很大的不同。当用户触击屏幕时,接触
区域实际上是椭圆形的,而且比用户想像的位置更靠下一点。根据触摸屏幕的手指、手指的尺寸、手指接触屏幕的力量、手指的方向、以及其它因素的不同,
其“接触部位”的尺寸和形状也有所不同。底层的多点触摸系统会分析所有的这些信息,为您计算出单一的触点。

UIResponder 是所有响应者对象的基类,
它不仅为事件处理,而且也为常见的响应者行为定义编程接口。UIApplication、UIView、和所有从UIView 派生出来的UIKit 类(包括UIWindow)都直接或间接地继承自UIResponder类。

– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch* touch = [touches anyObject];
NSUInteger numTaps = [touch tapCount];
if (numTaps < 2) {
[self.nextResponder touchesBegan:touches withEvent:event];
} else {
[self handleDoubleTap:touch];
}
}

缺省情况下,视图会接收触摸事件。但是,您可以将其userInteractionEnabled
属性声明设置为NO,关闭事件传递的功能。

在一定的时间内关闭事件的传递。应用程序可以调用UIApplication 的
beginIgnoringInteractionEvents 方法,并在随后调用endIgnoringInteractionEvents 方法来实现这个目的。

缺省情况下,视图只接收多点触摸序列的*个触摸事件,而忽略
所有其它事件。如果您希望视图处理多点触摸,就必须使它启用这个功能。在代码或Interface Builder 的查看器窗口中将视图的multipleTouchEnabled 属性设置为YES,就可以实现这个目标。
将事件传递限制在某个单独的视图上。缺省情况下,视图的exclusiveTouch 属性被设置为NO。将这个属性设置为YES 会使相应的视图具有这样的特性:即当该视图正在跟踪触摸动作时,窗口中的其它视图无法同时进行跟踪,它们不能接收到那些触摸事件。
多点触摸:
– (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
– (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event;
– (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;
– (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

当一个或多个手指触碰屏幕时,发送touchesBegan:withEvent:消息。
当一个或多个手指在屏幕上移动时,发送touchesMoved:withEvent:消息。
当一个或多个手指离开屏幕时,发送touchesEnded:withEvent:消息。
当触摸序列被诸如电话呼入这样的系统事件所取消时,发送touchesCancelled:withEvent:消息。
上面这些方法都和特定的触摸阶段(比如UITouchPhaseBegan)相关联,该信息存在于UITouch 对象的phase 属性声明中。
为了处理给定阶段的事件,响应者对象常常从传入的集合参数中取得一或多个UITouch 对象,然后考察这些对象的属性或取得它们的位置(如果需要处理所有触摸对象,可以向该NSSet 对象发送anyObject 消息)。UITouch 类中有一个名为locationInView:的重要方法,如果传入self 参数值,它会给出触摸动作在响应者坐标系统中的位置(假定该响应者是一个UIView 对象,且传入的视图参数不为nil)。另外,还有一个与之平行的方法,可以给出触摸动作之前位置(previousLocationInView:)。UITouch 实例的属性还可以给出发生多少次触
碰(tapCount)、触摸对象的创建或*后一次变化发生在什么时间(times*****p)、以及触摸处于什么阶段(phase)。

– (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch *touch = [touches anyObject];
if ([touch tapCount] == 2) {
CGPoint tapPoint = [theTouch locationInView:self];
// Process a double-tap gesture
}
}
在touchesEnded:withEvent:方法中,当触击次数为一时,响应者对象就向自身发送一个performSelector:withObject:afterDelay:消息,其中的选择器标识由响应者对象实现的、用于处理单击手势的方法;第二个参数是一个NSValue 或NSDictionary 对象,用于保存相关的UITouch 对象;时延参数则表示单击和双击手势之间的合理时间间隔。
在touchesBegan:withEvent:方法中,如果触击次数为二,响应者对象会向自身发送一个cancelPreviousPerformRequestsWithTarget:消息,取消当前被挂起和延期执行的调用。如果触碰次数不为二,则在指定的延时之后,先前步骤中由选择器标识的方法就会被调用,以处理单击手势。

 
 
Iphone开发-NSRunLoop概述和原理
1.什么是NSRunLoop?
我们会经常看到这样的代码:

– (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];
[progress setHidden:NO];
while (pageStillLoading) {
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[progress setHidden:YES];
}
  这段代码很神奇的,因为他会“暂停”代码运行,而且程序运行不会因为这里有一个while循环而受到影响。在[progress setHidden:NO]执行之后,整个函数像暂停了一样,停在循环里面,等loadPageInBackground里面的操作都完成了以后,
才让[progress setHidden:YES]运行。这样做就显得简单,而且逻辑很清晰。如果不这样做,就需要在loadPageInBackground里面表示load完成的地方调用[progress setHidden:YES],显得代码不紧凑而且容易出错。
那么具体什么是NSRunLoop呢?其实NSRunLoop的本质是一个消息机制的处理模式。如果你对vc++编程有一定了解,在windows中,有一系列很重要的函数SendMessage,PostMessage,GetMessage,
这些都是有关消息传递处理的API。但是在你进入到Cocoa的编程世界里面,我不知道你是不是走的太快太匆忙而忽视了这个很重要的问题,Cocoa里面就没有提及到任何关于消息处理的API,
开发者从来也没有自己去关心过消息的传递过程,好像一切都是那么自然,像大自然一样自然?在Cocoa里面你再也不用去自己定义WM_COMMAD_XXX这样的宏来标识某个消息,
也不用在switch-case里面去对特定的消息做特别的处理。难道是Cocoa里面就没有了消息机制?答案是否定的,只是Apple在设计消息处理的时候采用了一个更加高明的模式,那就是RunLoop。
 
 
利用NSRunLoop阻塞NSOperation线程
在使用NSOperationQueue简化多线程开发中介绍了多线程的开发,我这里主要介绍一下使用NSRunLoop阻塞线程。
主要使用在NStimer定时启用的任务或者异步获取数据的情况如socket获取网络数据,要阻塞线程,直到获取数据之后在释放线程。
下面是线程中没有使用NSRunLoop阻塞线程的代码和执行效果:
线程类:

#import <Foundation/Foundation.h>
@interface MyTask : NSOperation {
}
@end

#import “MyTask.h”
@implementation MyTask
-(void)main
{
NSLog(@”开始线程=%@”,self);
[NSTimer timerWithTimeInterval:2 target:self selector:@selector(hiandeTime:) userInfo:nil repeats:NO];
}
-(void)hiandeTime:(id)sender
{
NSLog(@”执行了NSTimer”);
}
-(void)dealloc
{
NSLog(@”delloc mytask=%@”,self);
[super dealloc];
}
@end

线程添加到队列中:

– (void)viewDidLoad
{
[super viewDidLoad];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
MyTask *myTask=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask];
MyTask *myTask1=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask1];
MyTask *myTask2=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask2];
[queue release];
}
执行结果是:
2011-07-25 09:44:45.393 OperationDemo[20676:1803] 开始线程=<MyTask: 0x4b4dea0>
2011-07-25 09:44:45.393 OperationDemo[20676:5d03] 开始线程=<MyTask: 0x4b50db0>
2011-07-25 09:44:45.396 OperationDemo[20676:1803] 开始线程=<MyTask: 0x4b51070>
2011-07-25 09:44:45.404 OperationDemo[20676:6303] delloc mytask=<MyTask: 0x4b4dea0>
2011-07-25 09:44:45.404 OperationDemo[20676:5d03] delloc mytask=<MyTask: 0x4b50db0>
2011-07-25 09:44:45.405 OperationDemo[20676:6303] delloc mytask=<MyTask: 0x4b51070>
可以看到,根本没有执行NSTimer中的方法,线程就释放掉了,我们要执行
NSTimer中的方法,就要利用NSRunLoop阻塞线程。下面是修改后的代码:

-(void)main
{
NSLog(@”开始线程=%@”,self);
NSTimer *timer=[NSTimer timerWithTimeInterval:2 target:self selector:@selector(hiandeTime) userInfo:nil repeats:NO];
[timer fire];
while (!didDisconnect) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}

执行结果如下:

2011-07-25 10:07:00.543 OperationDemo[21270:1803] 开始线程=<MyTask: 0x4d16380>
2011-07-25 10:07:00.543 OperationDemo[21270:5d03] 开始线程=<MyTask: 0x4d17790>
2011-07-25 10:07:00.550 OperationDemo[21270:6303] 开始线程=<MyTask: 0x4d17a50>
2011-07-25 10:07:00.550 OperationDemo[21270:1803] 执行了NSTimer
2011-07-25 10:07:00.551 OperationDemo[21270:5d03] 执行了NSTimer
2011-07-25 10:07:00.552 OperationDemo[21270:6303] 执行了NSTimer
2011-07-25 10:07:00.556 OperationDemo[21270:6503] delloc mytask=<MyTask: 0x4d16380>
2011-07-25 10:07:00.557 OperationDemo[21270:6303] delloc mytask=<MyTask: 0x4d17790>
2011-07-25 10:07:00.557 OperationDemo[21270:5d03] delloc mytask=<MyTask: 0x4d17a50>

我们可以使用NSRunLoop进行线程阻塞。

 
 
loadView:
(加载视图)
– 建立层次结构
– 在不使用 Interface Builder 的时候发生
viewDidLoad:
(视图已加载)
– 加载附加的资源和数据
viewWillAppear:
(视图快要被显示)
– 准备在屏幕上加载
– 视图不会在每次显示重新加载
viewDidAppear:
(视图已被显示)
– 动画和其他视觉元素被加载
 
 
 
BOOL和bool的区别
1、类型不同
BOOL为int型  bool为布尔型
2、长度不同
bool只有一个字节
BOOL长度视实际环境来定,一般可认为是4个字节
3、取值不同
bool取值false和true,是0和1的区别; false可以代表0,但true有很多种,并非只有1。
如果数个bool对象列在一起,可能会各占一个bit,这取决于编译器。
BOOL是微软定义的typedef int BOOL(在windef.h中)。
布尔型变量bool
bool是布尔型变量,也就是逻辑型变量的定义符,类似于float,double等,只不过float定义浮点型,double定义双精度浮点型。
在objective-c中提供了相似的类型BOOL,它具有YES值和NO值。 布尔型变量的值只有 真 (true) 和假 (false)。
布尔型变量可用于逻辑表达式,也就是“或”“与”“非”之类的逻辑运算和大于小于之类的关系运算,逻辑表达式运算结果为真或为假。
bool可用于定义函数类型为布尔型,函数里可以有 return TRUE; return FALSE 之类的语句。
布尔型运算结果常用于条件语句,
if (逻辑表达式)
{
如果是 true 执行这里;
}
else
{
如果是 false 执行这里;
};

BOOL在iphone程序开发中很重要,一般配合if语句进行判断,处理程序中逻辑关系!

 
if 语句与布尔值
if 语句是 C++/C 语言中*简单、*常用的语句,然而很多程序员用隐含错误的方式
写 if 语句。
假设布尔变量名字为 flag,它与零值比较的标准 if 语句如下:
if (flag) // 表示 flag 为真
if (!flag) // 表示 flag 为假
其它的用法都属于不良风格,例如:
if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)
 
 

TextView自适改变高度

 sizeWithFont:constrainedToSize:lineBreakMode:

ios 查看crash日志

作为一名应用开发者,你是否有过如下经历?

为确保你的应用正确无误,在将其提交到应用商店之前,你必定进行了大量的测试工作。它在你的设备上也运行得很好,但是,上了应用商店后,还是有用户抱怨会闪退 !
如果你跟我一样是个完美主义者,你肯定想将应用做到尽善尽美。于是你打开代码准备修复闪退的问题……但是,从何处着手呢?
这时iOS崩溃日志派上用场了。在大多数情况下,你能从中了解到关于闪退的详尽、有用的信息。
通过本教程,你将学习到一些常见的崩溃日志案例,以及如何从开发设备和iTunes Connect上获取崩溃日志文件。你还将学习到符号化( symbolication),从日志追踪到代码 。你还将学习调试一个在待定情况下会闪退的应用。
让我们开始动手吧!

什么是崩溃日志,从哪里能得它?

iOS设备上的应用闪退时,操作系统会生成一个崩溃报告,也叫崩溃日志,保存在设备上。
崩溃日志上有很多有用的信息,包括应用是什么情况下闪退的。通常,上面有每个正在执行线程的完整堆栈跟踪信息,所以你能从中了解到闪退发生时各线程都在做什么,并分辨出闪退发生在哪个线程上。
有几种方法可以从设备上获取崩溃日志。
设备与电脑上的iTunes Store同步后,会将崩溃日志保存在电脑上。根据电脑操作系统的不同,崩溃日志将保存在以下位置:
Mac OS X:

~/Library/Logs/CrashReporter/MobileDevice/

Windows XP:

C:Documents and Settings<USERNAME>Application DataApple ComputerLogsCrashReporterMobileDevice<DEVICE_NAME>

 

Windows Vista or 7:

C:Users<USERNAME>AppDataRoamingApple ComputerLogsCrashReporterMobileDevice<DEVICE_NAME>

当用户抱怨闪退时,你可以要求他让设备与iTunes同步,并根据操作系统的不同,到上述位置把崩溃日志下载下来,然后通过电子邮件发送给你。
你必需尽量获取用户设备生成的所有崩溃日志。因为崩溃日志越多,就越容易诊断问题所在!
另外,如果你装了Xcode,也能很容易通过Xcode从你的设备上获得崩溃日志。将iOS设备连接到电脑上,然后打开Xcode。从菜单栏上选择 Window  菜单, 然后选择 Organizer (快捷方式是 Shift-CMD-2).
在 Organizer 窗口上, 选中 Devices 标签栏. 在左侧的导航面板上,选中 Device Logs, 如下图所示:

看看上图,左侧有好几个 Device Logs 菜单项. LIBRARY 下面的Device Logs是你所有设备(曾经连接到Xcode的)的日志 。每个设备下面的 Device Logs 是对应设备的日志。
应用提交到App Store后,你也能从 iTunes Conect 获取到用户的崩溃日志. 登录到 iTunes Connect 上, 选择Manage Your Applications, 点击相应的应用, 点击应用图标下面的 View Details 按钮, 然后点击右栏Links部分的  Crash Reports 。

如果没有崩溃日志,试试点击Refresh 按钮刷新一下。如果你的应用还卖得不多,或者刚上架不久,iTunes Connect账号上也可能还没有任何崩溃日志。
如果iTunes Connect上有崩溃日志,你将看到如下图:

有时,尽管有用户报告闪退,你仍然看不到崩溃报告。这时,*好让用户直接把崩溃报告发送给你。

什么情况下会产生崩溃日志?

两种主要情况会产生崩溃日志:

  1. 应用违反操作系统规则。
  2. 应用中有Bug。

违反iOS规则包括在启动、恢复、挂起、退出时watchdog超时、用户强制退出和低内存终止。让我们详细了解一下吧。
Watchdog 超时机制
从iOS 4.x开始,退出应用时,应用不会立即终止,而是退到后台。
但是,如果你的应用响应不够快,操作系统有可能会终止你的应用,并产生一个崩溃日志。这些事件与下列UIApplicationDelegate方法相对应:

  • application:didFinishLaunchingWithOptions:
  • applicationWillResignActive:
  • applicationDidEnterBackground:
  • applicationWillEnterForeground:
  • applicationDidBecomeActive:
  • applicationWillTerminate:

上面所有这些方法,应用只有有限的时间去完成处理。如果花费时间太长,操作系统将终止应用。

注意: 如果你没有把需要花费时间比较长的操作(如网络访问)放在后台线程上就很容易发生这种情况。关于如果避免这种情况的信息,请参考我们的另外两篇教程: Grand Central Dispatch 和 NSOperations。

用户强制退出
iOS 4.x开始支持多任务。如果应用阻塞界面并停止响应, 用户可以通过在主屏幕上双击Home按钮来终止应用。此时,操作应用将生成一个崩溃日志。

注意: 双击Home按钮后,你将看到运行过的所有应用。那些应用不一定是正在运行,也不一定是被挂起。
通常,用户点击Home按钮时,应用将在后台保留约10分钟,然后操作系统自动将其终止。 所以双击Home按钮显示的应用列表只是表明那是一系列过去打开过的应用。删除那些应用的图标不会产生任何崩溃日志。

低内存终止
子类化UIViewController时,你或许已经注意到didReceiveMemoryWarning方法。
在前台运行的应用拥有访问和使用内存的*高优化级。然而,这并不意味着该应用能使用设备的所有可用内存 ——每个应用只能使用一部分可用内存。
当内存使用达到一定程度时,操作系统将发出一个 UIApplicationDidReceiveMemoryWarningNotification 通知。同时,调用 didReceiveMemoryWarning 方法。
此时,为了让应用继续正常运行,操作系统开始终止在后台的其他应用以释放一些内存。所有后台应用被终止后,如果你的应用还需要更多内存,操作系统会将你的应用也终止掉,并产生一个崩溃日志。而在这种情况下被终止的后台应用,不会产生崩溃日志。

注意: 根据 苹果文档, Xcode不会自动添加低内存日志。你必需手动获取日志。 然而,根据我的个人经验,使用 Xcode 4.5.2, 低内存日志也会自动导入,只是”Process”和”Type”属性都被标为Unknown(未知)。
另外,值得一提的是在*短时间内分配一大块内存将给系统内存带来巨大负担。这样,也会产生内存警告的通知。

应用中有Bug
如你所想,大多数闪退都是由于应用中有Bug,因此大多数崩溃日志的产生都是因为应用中的Bug。Bug的种类的有很多。

在本教程的后半部分,你将通过调试一个会产生崩溃日志的含有Bug的应用,学习如何找到问题所在并进行修复!

崩溃日志的实例

让我们看看一个崩溃日志的实例,以使你在处理一些实际问题之前心里有谱。
事不宜迟,见见你的新朋友吧:

[objc]

  1. // 1: 进程信息

Incident Identifier: 30E46451-53FD-4965-896A-457FC11AD05F
CrashReporter Key:   5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model:      iPhone4,1
Process:         Rage Masters [4155]
Path:            /var/mobile/Applications/A5635B22-F5EF-4CEB-94B6-FE158D885014/Rage Masters.app/Rage Masters
Identifier:      Rage Masters
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]
// 2: 基本信息
Date/Time:       2012-10-17 21:39:06.967 -0400
OS Version:      iOS 6.0 (10A403)
Report Version:  104
// 3: 异常
Exception Type:  00000020
Exception Codes: 0x000000008badf00d
Highlighted Thread:  0
// 4: 线程回溯
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib            0x327f2eb4 mach_msg_trap + 20
1   libsystem_kernel.dylib            0x327f3048 mach_msg + 36
2   CoreFoundation                    0x36bd4040 __CFRunLoopServiceMachPort + 124
3   CoreFoundation                    0x36bd2d9e __CFRunLoopRun + 878
4   CoreFoundation                    0x36b45eb8 CFRunLoopRunSpecific + 352
5   CoreFoundation                    0x36b45d44 CFRunLoopRunInMode + 100
6   CFNetwork                         0x32ac343e CFURLConnectionSendSynchronousRequest + 330
7   Foundation                        0x346e69ba +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 242
8   Rage Masters                      0x000d4046 0xd2000 + 8262
Thread 1:
0   libsystem_kernel.dylib            0x32803d98 __workq_kernreturn + 8
1   libsystem_c.dylib                 0x3a987cf6 _pthread_workq_return + 14
2   libsystem_c.dylib                 0x3a987a12 _pthread_wqthread + 362
3   libsystem_c.dylib                 0x3a9878a0 start_wqthread + 4
// 5: 线程状态
Thread 0 crashed with ARM Thread State (32-bit):
r0: 0x00000000    r1: 0x00000000      r2: 0x00000001      r3: 0x39529fc8
r4: 0xffffffff    r5: 0x2fd7d301      r6: 0x2fd7d300      r7: 0x2fd7d9d0
r8: 0x2fd7d330    r9: 0x3adbf8a8     r10: 0x2fd7d308     r11: 0x00000032
ip: 0x00000025    sp: 0x2fd7d2ec      lr: 0x001bdb25      pc: 0x30301838
cpsr: 0x00000010
// 6: 二进制映像
Binary Images:
0xd2000 –    0xd7fff +Rage Masters armv7   /var/mobile/Applications/A5635B22-F5EF-4CEB-94B6-FE158D885014/Rage Masters.app/Rage Masters
0x2fe41000 – 0x2fe61fff  dyld armv7   /usr/lib/dyld
0x327f2000 – 0x32808fff  libsystem_kernel.dylib armv7   /usr/lib/system/libsystem_kernel.dylib
0x328a8000 – 0x328bdfff  libresolv.9.dylib armv7   /usr/lib/libresolv.9.dylib
0x32a70000 – 0x32b35fff  CFNetwork armv7   /System/Library/Frameworks/CFNetwork.framework/CFNetwork
0x32b7a000 – 0x32cc3fff  libicucore.A.dylib armv7   /usr/lib/libicucore.A.dylib
0x32cc4000 – 0x32cc5fff  CoreSurface armv7   /System/Library/PrivateFrameworks/CoreSurface.framework/CoreSurface
0x32f65000 – 0x32f8afff  OpenCL armv7   /System/Library/PrivateFrameworks/OpenCL.framework/OpenCL

这报告看起来像天书。:) 我们分几部分来解读吧:

(1) 进程信息
*部分是闪退进程的相关信息。

  • Incident Identifier是崩溃报告的唯一标识符。
  • CrashReporter Key 是与设备标识相对应的唯一键值。虽然它不是真正的设备标识符,但也是一个非常有用的情报:如果你看到100个崩溃日志的CrashReporter Key值都是相同的,或者只有少数几个不同的CrashReport值,说明这不是一个普遍的问题,只发生在一个或少数几个设备上。
  • Hardware Model 标识设备类型。 如果很多崩溃日志都是来自相同的设备类型,说明应用只在某特定类型的设备上有问题。上面的日志里,崩溃日志产生的设备是iPhone 4s。
  • Process 是应用名称。中括号里面的数字是闪退时应用的进程ID。
  • 接下来几行不言自明,无需赘述。

(2) 基本信息
这部分给出了一些基本信息,包括闪退发生的日期和时间,设备的iOS版本。如果有很多崩溃日志都来自iOS 6.0,说明问题只发生在iOS 6.0上。
(3) 异常
在这部分,你可以看到闪退发生时抛出的异常类型。还能看到异常编码和抛出异常的线程。根据崩溃报告类型的不同,在这部分你还能看到一些另外的信息。
(4) 线程回溯
这部分提供应用中所有线程的回溯日志。 回溯是闪退发生时所有活动帧清单。它包含闪退发生时调用函数的清单。看下面这行日志:

2    XYZLib    0x34648e88    0x83000 + 8740

它包括四列:

  1. 帧编号—— 此处是2。
  2. 二进制库的名称 ——此处是 XYZLib.
  3. 调用方法的地址 ——此处是 0x34648e88.
  4. 第四列分为两个子列,一个基本地址和一个偏移量。此处是0×83000 + 8740, *个数字指向文件,第二个数字指向文件中的代码行。

(5) 线程状态
这部分是闪退时寄存器中的值。一般不需要这部分的信息,因为回溯部分的信息已经足够让你找出问题所在。
(6) 二进制映像
这部分列出了闪退时已经加载的二进制文件。

符号化Symbolication

*次看到崩溃日志上的回溯时,你或许会觉得它没什么意义。我们习惯使用方法名和行数,而非像这样的神秘位置:

6    Rage Masters    0x0001625c    0x2a000 + 3003

将这些十六进制地址转化成方法名称和行数的过程称之为符号化。
从Xcode的Organizer窗口获取崩溃日志后过几秒钟,崩溃日志将被自动符号化。上面那行被符号化后的版本如下 :

6    Rage Masters    0x0001625c    -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:35)

Xcode符号化崩溃日志时,需要访问与App Store上对应的应用二进制文件以及生成二进制文件时产生的 .dSYM 文件。必需完全匹配才行。否则,日志将无法被完全符号化。
所以,保留每个分发给用户的编译版本非常重要。提交应用前进行归档时,Xcode将保存应用的二进制文件。可以在Xcode Organizer的Archives标签栏下找到所有已归档的应用文件。
在发现崩溃日志时,如果有相匹配的.dSYM文件和应用二进制文件,Xcode会自动对崩溃日志进行符号化。如果你换到别的电脑或创建新的账户,务必将所有二进制文件移动到正确的位置,使Xcode能找到它们。

注意: 你必需同时保留应用二进制文件和.dSYM文件才能将崩溃日志完整符号化。每次提交到iTunes Connect的构建都必需归档。
.dSYM文件和二进制文件是特定绑定于每一次构建和后续构建的,即使来自相同的源代码文件,每一次构建也与其他构建不同,不能相互替换。
如果你使用Build 和 Archive 命令,这些文件会自动放在适当位置。 如果不是使用Build 和 Archive命令,放在Spotlight能够搜索到的位置(比如Home目录)即可。

 

低内存闪退

因为低内存崩溃日志与普通崩溃日志略有不同,所以本教程特别分开说明一下。:]
iOS设备检测到低内存时,虚拟内存系统发出通知请求应用释放内存。这些通知发送到所有正在运行的应用和进程,试图收回一些内存。
如果内存使用依然居高不下,系统将会终止后台线程以缓解内存压力。如果可用内存足够,应用将能够继续运行而不会产生崩溃报告。否则,应用将被iOS终止,并产生低内存崩溃报告。
低内存崩溃日志上没有应用线程的堆栈回溯。相反,上面显示的是以内存页数为单位的各进程内存使用量。(在撰写本文的时候,一个内存页的大小是4KB。)
被iOS因释放内存页终止的进程名称后面你会看到jettisoned 字样。如果看到它出现在你的应用名称后面,说明你的应用因使用太多内存而被终止了。
低内存崩溃日志看起来像这样:

Incident Identifier: 30E46451-53FD-4965-896A-457FC11AD05F
CrashReporter Key:   5a56599d836c4f867f6eec76afee451bf9ae5f31
OS Version:          iPhone OS 3.1.3 (7E18)
Date/Time:           2012-10-17 21:39:06.967 -0400
Free pages:        96
Wired pages:       10558
Purgeable pages:   0
Largest process:   Rage Masters
Processes
Name                 UUID                    Count resident pages
Rage Masters     9320 (jettisoned) (active)
mediaserverd      255
dataaccessd      505
syslogd       71
apsd      171
securityd      243
notifyd     2027
CommCenter      189
SpringBoard     2158 (active)
accessoryd       91
configd      371
fairplayd       93
mDNSResponder      292
lockdownd     1204
launchd       72

当应用发生低内存闪退时,你必需看看应用中内存使用的方式,以及是如何处理低内存警告的。你可以使用Instruments工具中使用Allocations 和 Leaks来发现内存分配问题和内存泄漏问题。如果你不知道如何利用 Instruments 检查内存问题,可以看看
还有,别忘记虚拟内存! Instruments工具的Leaks 和 Allocations 不能跟踪显存使用情况。必需使用 VM Tracker 才能查看显存使用情况。
VM Tracker 默认是关闭的。打开Instrument,手动 选中Automatic Snapshotting 标志或者按下Snapshot Now 按钮。
本教程后面将会学习如何研究低内存崩溃日志。

异常编码

在研究真实闪退场景之前,还有一点需要重点介绍一下:就是那些有趣的异常编码 。
你可以在报告的异常部分——前面代码的第3部分找到异常编码。有些编码比较常见。
通常,异常编码以一些文字开头,紧接着是一个或多个十六进制值,此数值正是说明闪退根本性质的所在。  从这些编码中,可以区分出闪退是因为程序错误、非法内存访问或者是其他原因。
下面是一些常见的异常编码:

  • 0x8badf00d: 读做 “ate bad food”! (把数字换成字母,是不是很像 :p)该编码表示应用是因为发生watchdog超时而被iOS终止的。  通常是应用花费太多时间而无法启动、终止或响应用系统事件。

 

  • 0xbad22222: 该编码表示 VoIP 应用因为过于频繁重启而被终止。
  • 0xdead10cc: 读做 “dead lock”!该代码表明应用因为在后台运行时占用系统资源,如通讯录数据库不释放而被终止 。
  • 0xdeadfa11: 读做 “dead fall”! 该代码表示应用是被用户强制退出的。根据苹果文档, 强制退出发生在用户长按开关按钮直到出现 “滑动来关机”, 然后长按 Home按钮。强制退出将产生 包含0xdeadfa11 异常编码的崩溃日志, 因为大多数是强制退出是因为应用阻塞了界面。

 

注意: 在后台任务列表中关闭已挂起的应用不会产生崩溃日志。 一旦应用被挂起,它何时被终止都是合理的。所以不会产生崩溃日志。

大展身手的时候到了!

好了! 你已经学习了所有分析崩溃日志和修复错误的基础知识!
假设你刚进入Rage-O-Rage有限公司工作。该公司有一个在App Store上热销的应用,叫 Rage Masters。
你的老板安迪要你帮忙解决几个用户经常抱怨闪退问题。你的任务就是研究这些闪退,符号化用户提供的崩溃日志,查找问题所在,并修复之。

注意: 如果你想自己重新生成崩溃报告,请遵照以下指引:

  1. 下载源码然后在Xcode中打开工程文件。
  2. 使用正确的provisioning profile连接到iOS设备。
  3. 从Xcode工具栏上选择iOS设备——不是模拟器作为target,然后构建应用。
  4. 当你在设备上到默认页面(应用的全屏图片)时,立即在Xcode上点击停止按钮。
  5. 关闭 Xcode。
  6. 在设备上直接打开应用。
  7. 测试场景,完成后连接设备到电脑上,通过Xcode获取崩溃日志。

 

场景 1: 糟糕的代码

一封来自用户的邮件: “大哥,你的应用就是一坨屎! 我将其下载到我自己的iPod Touch和iPhone上,还下载到我儿子的iPod Touch上。在所有的设备上,都是还没打开就闪退了……”
别一封来自用户的邮件说, “我下载了你们的应用,一打开就闪退。真悲催…”
另一封邮件说得更明确:”你们的应用不能运行。我把它下载到我和妻子的设备上。所有设备都是 一打开就闪退了…”

好吧,别灰心! 这些意见藏着什么玄机呢?让我们看看崩溃日志吧:

Incident Identifier: 85833DBA-3DF7-43EE-AF80-4E5C51091F42
CrashReporter Key:   5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model:      iPhone4,1
Process:         Rage Masters [20067]
Path:            /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters
Identifier:      Rage Masters
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]
Date/Time:       2012-11-03 13:37:31.148 -0400
OS Version:      iOS 6.0 (10A403)
Report Version:  104
Exception Type:  00000020
Exception Codes: 0x000000008badf00d
Highlighted Thread:  0
Application Specific Information:
Soheil-Azarpour.Rage-Masters failed to launch in time
Elapsed total CPU time (seconds): 8.030 (user 8.030, system 0.000), 20% CPU
Elapsed application CPU time (seconds): 3.840, 10% CPU
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0:
0   libsystem_kernel.dylib            0x327f2eb4 mach_msg_trap + 20
1   libsystem_kernel.dylib            0x327f3048 mach_msg + 36
2   CoreFoundation                    0x36bd4040 __CFRunLoopServiceMachPort + 124
3   CoreFoundation                    0x36bd2d9e __CFRunLoopRun + 878
4   CoreFoundation                    0x36b45eb8 CFRunLoopRunSpecific + 352
5   CoreFoundation                    0x36b45d44 CFRunLoopRunInMode + 100
6   CFNetwork                         0x32ac343e CFURLConnectionSendSynchronousRequest + 330
7   Foundation                        0x346e69ba +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 242
8   Rage Masters                      0x000ea1c4 -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:36)
9   UIKit                             0x37f30ad4 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 248
10  UIKit                             0x37f3065e -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1186
11  UIKit                             0x37f28846 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 694
12  UIKit                             0x37ed0c3c -[UIApplication handleEvent:withNewEvent:] + 1000
13  UIKit                             0x37ed06d0 -[UIApplication sendEvent:] + 68
14  UIKit                             0x37ed011e _UIApplicationHandleEvent + 6150
15  GraphicsServices                  0x370835a0 _PurpleEventCallback + 588
16  GraphicsServices                  0x370831ce PurpleEventCallback + 30
17  CoreFoundation                    0x36bd4170 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32
18  CoreFoundation                    0x36bd4112 __CFRunLoopDoSource1 + 134
19  CoreFoundation                    0x36bd2f94 __CFRunLoopRun + 1380
20  CoreFoundation                    0x36b45eb8 CFRunLoopRunSpecific + 352
21  CoreFoundation                    0x36b45d44 CFRunLoopRunInMode + 100
22  UIKit                             0x37f27480 -[UIApplication _run] + 664
23  UIKit                             0x37f242fc UIApplicationMain + 1116
24  Rage Masters                      0x000ea004 main (main.m:16)
25  libdyld.dylib                     0x3b630b1c start + 0

发现问题了吗? 异常编码是0x000000008badf00d,还有后面的报告:

Application Specific Information:
Soheil-Azarpour.Rage-Masters failed to launch in time
Elapsed total CPU time (seconds): 8.030 (user 8.030, system 0.000), 20% CPU
Elapsed application CPU time (seconds): 3.840, 10% CPU

这说明应用在启动时就闪退了,iOS的watchdog机制终止了应用。帅! 找到问题了,但是为什会发生这样的事呢?
接着往下看日志。 从下向上读回溯日志。*底下的帧 (frame 25: libdyld.dylib)是*先调用的,然后是帧24, Rage Masters, main (main.m:16) ,依此类推。
跟应用源代码相关的帧是*重要的。忽略掉系统库和框架。下一个与代码相关的帧是:

8    Rage Masters    0x0009f244 -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:35)

应用在执行RMAppDelegate (RMAppDelegate.m:35)类application:didFinishLaunchingWithOptions: 方法第35 行代码时闪退。打开Xcode看看那行代码:

NSData *directoryData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];

就是它了! 同步调用web服务?! 在主线程上?! 在 application:didFinishLaunchingWithOptions: 方法上?!! 谁写的代码呀?!

Network calls on the main thread makes kittens sad.

不管如何,问题得你来修复了。这个调用必需异步进行,甚至更理想的情况是,在application:didFinishLaunchingWithOptions:返回YES之后的其他部分再执行Web服务。
在其他地方调用可能需要比较多的修改。当下,我们只要使应用不闪退就行。可以在日后再实现更好的设计。 将上面那行讨厌的代码(及其下面的三行代码)换成下面这个异步的版本吧:

[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
NSURL *cacheDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSUserDirectory inDomains:NSCachesDirectory] lastObject];
NSURL *filePath = [NSURL URLWithString:kDirectoryFile relativeToURL:cacheDirectory];
[data writeToFile:[filePath absoluteString] atomically:YES];
}];

场景 2: 无法响应事件的按钮

一名用户说: “我不能将某个rage master添加到书签里面。我想添加的时候应用就闪退…”
用一名用户说 :”书签不能用 … 在详细页面上,点击书签按钮,应用就闪退了!”
上面的抱怨说得不是很清楚,引起问题的原因肯定有多样。看看崩溃日志:

Incident Identifier: 3AAA63CC-3088-41CC-84D9-82FE03F9F354
CrashReporter Key:   5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model:      iPhone4,1
Process:         Rage Masters [20090]
Path:            /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters
Identifier:      Rage Masters
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]
Date/Time:       2012-11-03 13:39:00.081 -0400
OS Version:      iOS 6.0 (10A403)
Report Version:  104
Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Crashed Thread:  0
Last Exception Backtrace:
0   CoreFoundation                    0x36bff29e __exceptionPreprocess + 158
1   libobjc.A.dylib                   0x34f0f97a objc_exception_throw + 26
2   CoreFoundation                    0x36c02e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166
3   CoreFoundation                    0x36c0152c ___forwarding___ + 388
4   CoreFoundation                    0x36b58f64 _CF_forwarding_prep_0 + 20
5   UIKit                             0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68
6   UIKit                             0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26
7   UIKit                             0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40
8   UIKit                             0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498
9   UIKit                             0x37fbade4 -[UIControl touchesEnded:withEvent:] + 484
10  UIKit                             0x37ee35f4 -[UIWindow _sendTouchesForEvent:] + 520
11  UIKit                             0x37ed0804 -[UIApplication sendEvent:] + 376
12  UIKit                             0x37ed011e _UIApplicationHandleEvent + 6150
13  GraphicsServices                  0x3708359e _PurpleEventCallback + 586
14  GraphicsServices                  0x370831ce PurpleEventCallback + 30
15  CoreFoundation                    0x36bd416e __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 30
16  CoreFoundation                    0x36bd4112 __CFRunLoopDoSource1 + 134
17  CoreFoundation                    0x36bd2f94 __CFRunLoopRun + 1380
18  CoreFoundation                    0x36b45eb8 CFRunLoopRunSpecific + 352
19  CoreFoundation                    0x36b45d44 CFRunLoopRunInMode + 100
20  GraphicsServices                  0x370822e6 GSEventRunModal + 70
21  UIKit                             0x37f242fc UIApplicationMain + 1116
22  Rage Masters                      0x000ca004 main (main.m:16)
23  libdyld.dylib                     0x3b630b1c start + 0

异常代码是SIGABRT。通常,  SIGABRT 异常是由于某个对象接收到未实现的消息引起的。 或者,用简单的话说,在某个对象上调用了不存在的方法。
这种情况一般不会发生,因为A对象调用了B方法,如果B方法不存在,编译器会报错。但是,如果你是使用selector间接调用方法的,编译器则无法检测对象是否存在该方法了。
回到崩溃日志。它指出闪退发生在编号为0的线程上。 这意味着很可能是在主线程上调用了某个对象没有实现的方法。
如果你接着阅读回溯日志,会发现跟你的代码相关的只有帧22, main.m:16. 这没有多大帮助。 :[
继续向上查看框架调用,出现这个:

2    CoreFoundation    0x36c02e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166

这不是你自己写的代码。但至少它确认了是对象调用了一个没有实现的方法。
回到RMDetailViewController.m文件, 因为那是书签按钮实现动作的地方。 找到书签功能代码:

-(IBAction)bookmarkButtonPressed {

self.master.isBookmarked = !self.master.isBookmarked;

// Update shared bookmarks
if (self.master.isBookmarked)
[[RMBookmarks sharedBookmarks] bookmarkMaster:self.master];
else
[[RMBookmarks sharedBookmarks] unbookmarkMaster:self.master];

// Update UI
[self updateBookmarkImage];
}

看起来没什么问题,再检查一下storyboard (XIB文件) ,确认按钮连接的正确性。

就是它了! 在 MainStoryboard.storyboard,按钮连接的是 bookmarkButtonPressed: 而不是bookmarkButtonPressed (注意后面的分号说明方法有一个参数)。 只要将上面的方法签名修改成这样就能修复问题了:

-(IBAction)bookmarkButtonPressed:(id)sender {
// Remain unchanged..
}

当然,你也可以简单地在XIB文件上删除错误的连接,然后重新连接方法,使XIB文件连接到正确的方法上。两者方法都行。
又处理了一个闪退问题,好样的。:]

场景 3: 表格上的Bug

另一用户抱怨道, “在书签视图上无法删除书签…” 还有另一用户抱怨同样的问题, “当我试图删除书签时,应用闪退…”
这些邮件没什么作用,还是看看崩溃日志!

Incident Identifier: 5B62D681-D8FE-41FE-8D52-AB7E6D6B2AC7
CrashReporter Key:   5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model:      iPhone4,1
Process:         Rage Masters [20088]
Path:            /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters
Identifier:      Rage Masters
Version:         ??? (???)
Code Type:       ARM (Native)
Parent Process:  launchd [1]
Date/Time:       2012-11-03 13:38:45.762 -0400
OS Version:      iOS 6.0 (10A403)
Report Version:  104
Exception Type:  EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Crashed Thread:  0
Last Exception Backtrace:
0   CoreFoundation                    0x36bff29e __exceptionPreprocess + 158
1   libobjc.A.dylib                   0x34f0f97a objc_exception_throw + 26
2   CoreFoundation                    0x36bff158 +[NSException raise:format:arguments:] + 96
3   Foundation                        0x346812aa -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86
4   UIKit                             0x37f04b7e -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] + 7690
5   UIKit                             0x3803a4a2 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 22
6   Rage Masters                      0x000fd9ca -[RMBookmarksViewController tableView:commitEditingStyle:forRowAtIndexPath:] (RMBookmarksViewController.m:68)
7   UIKit                             0x3809a5d4 -[UITableView(UITableViewInternal) animateDeletionOfRowWithCell:] + 80
8   UIKit                             0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68
9   UIKit                             0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26
10  UIKit                             0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40
11  UIKit                             0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498
12  UIKit                             0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68
13  UIKit                             0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26
14  UIKit                             0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40
15  UIKit                             0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498
16  UIKit                             0x37fbade4 -[UIControl touchesEnded:withEvent:] + 484
17  UIKit                             0x37ee35f4 -[UIWindow _sendTouchesForEvent:] + 520
18  UIKit                             0x37ed0804 -[UIApplication sendEvent:] + 376
19  UIKit                             0x37ed011e _UIApplicationHandleEvent + 6150
20  GraphicsServices                  0x3708359e _PurpleEventCallback + 586
21  GraphicsServices                  0x370831ce PurpleEventCallback + 30
22  CoreFoundation                    0x36bd416e __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 30
23  CoreFoundation                    0x36bd4112 __CFRunLoopDoSource1 + 134
24  CoreFoundation                    0x36bd2f94 __CFRunLoopRun + 1380
25  CoreFoundation                    0x36b45eb8 CFRunLoopRunSpecific + 352
26  CoreFoundation                    0x36b45d44 CFRunLoopRunInMode + 100
27  GraphicsServices                  0x370822e6 GSEventRunModal + 70
28  UIKit                             0x37f242fc UIApplicationMain + 1116
29  Rage Masters                      0x000fb004 main (main.m:16)
30  libdyld.dylib                     0x3b630b1c start + 0

这看起来跟前面那个崩溃日志很像。是另一个SIGABRT 异常。 你可能想知道是否是相同的问题:发送信息到一个没有实现相应方法的对象?
让我们从回溯日志看看哪些方法被调用了。从底部开始,你的源代码*后被调用的是帧 6:

6    Rage Masters    0x00088c66 -[RMBookmarksViewController tableView:commitEditingStyle:forRowAtIndexPath:] (RMBookmarksViewController.m:68)

这是UITableViewDataSource 的一个方法. 呵呵?! 毫无疑问苹果已经实现了该方法 —— 你可以重载它, 但不像是还没有实现。而且,这是个可选的委派方法。 所以问题不是调用了一个没有实现的方法。

再看看上面的几个帧:

3    Foundation    0x346812aa -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86
4    UIKit         0x37f04b7e -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] + 7690
5    UIKit         0x3803a4a2 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 22

帧 5, UITableView调用了它自己的另一个方法 deleteRowsAtIndexPaths:withRowAnimation: 然后是看起来像苹果内部方法的_endCellAnimationsWithContext: 被调用。然后Foundation framework发生异常handleFailureInMethod:object:file:lineNumber:description:.
这些分析结合用户的抱怨,看起来是你在处理UITableView删除行过程中有Bug。回到Xcode。你知道看哪里吗 ? 能从崩溃日志中判断出来? 就是RMBookmarksViewController.m文件的第68行:

– (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}

发现问题了吗? 给你点时间,仔细看一下。
找到了吧! 数据源呢? 代码在表格视图上删除了一行,但并没有修改背后的数据源。把上面的代码替换成下面的就能修复问题了:

-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

RMMaster *masterToDelete = [bookmarks objectAtIndex:indexPath.row];
[bookmarks removeObject:masterToDelete];
[[RMBookmarks sharedBookmarks] unbookmarkMaster:masterToDelete];

[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}

搞定了!走起,讨厌的 bug!!

场景 4: 吃棒棒糖时闪退!

用户邮件说, “当rage master吃棒棒糖时应用就闪退…” 另一用户说, “我让rage master 吃棒棒糖,没几次应用就闪退了!”
崩溃日志如下:

Incident Identifier: 081E58F5-95A8-404D-947B-5E104B6BC1B1
CrashReporter Key:   5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model:      iPhone4,1
OS Version:          iPhone OS 6.0 (10A403)
Kernel Version:      Darwin Kernel Version 13.0.0: Sun Aug 19 00:28:05 PDT 2012; root:xnu-2107.2.33~4/RELEASE_ARM_S5L8940X
Date:                2012-11-03 13:39:59 -0400
Time since snapshot: 4353 ms
Free pages:        968
Active pages:      7778
Inactive pages:    4005
Throttled pages:   92319
Purgeable pages:   0
Wired pages:       23347
Largest process:   Rage Masters

Processes
Name                    &lt;UUID&gt;                       rpages       recent_max       [reason]          (state)

lsd &lt;6a9f5b5f36b23fc78f87b6d8f1f49a9d&gt;          331              331         [vm]         (daemon) (idle)
afcd &lt;b0aff2e7952e34a9882fec81a8dcdbb2&gt;          141              141         [vm]         (daemon) (idle)
itunesstored &lt;4e0cd9f873de3435b4119c48b2d6d13d&gt;         1761             1761         [vm]         (daemon) (idle)
softwareupdatese &lt;2bc4b5ae016431c98d3b34f81027d0ae&gt;          311              311         [vm]         (daemon) (idle)
Amazon &lt;4600481f07ec3e59a925319b7f67ba14&gt;         2951             2951         [vm]         (suspended)
accountsd &lt;ac0fce15c1a2350d951efc498d521ac7&gt;          519              519         [vm]         (daemon) (idle)
coresymbolicatio &lt;edba67001f76313b992056c712153b4b&gt;          126              126         [vm]         (daemon) (idle)
Skype &lt;504cf2fe60cb3cdea8273e74df09836b&gt;         3187             3187         [vm]         (background)
MobileMail &lt;bff817c61ce33c85a43ea9a6c98c29f5&gt;        14927            14927         [vm]         (continuous)
MobileSMS &lt;46778de076363d67aeea207464cfc581&gt;         2134             2134         [vm]         (background)
MobilePhone &lt;3fca241f2a193d0fb8264218d296ea41&gt;         2689             2689         [vm]         (continuous)
librariand &lt;c9a9be81aa9632f0a913ce79b911f27e&gt;          317              317         [vm]         (daemon)
kbd &lt;3e7136ddcefc3d77a01499db593466cd&gt;          616              616         [vm]         (daemon)
tccd &lt;eb5ddcf533663f8d987d67cae6a4c4ea&gt;          224              224         [vm]         (daemon)
Rage Masters &lt;90b45d6281e934209c5b06cf7dc4d492&gt;        28591            28591         [vm]         (frontmost) (resume)
ptpd &lt;04a56fce67053c57a7979aeea8e5a7ea&gt;          879              879                      (daemon)
iaptransportd &lt;f784f30dc09d32078d87b450e8113ef6&gt;          230              230                      (daemon)
locationd &lt;892cd1c9ffa43c99a82dba197be5f09e&gt;         1641             1641                      (daemon)
syslogd &lt;cbef142fa0a839f0885afb693fb169c3&gt;          237              237                      (daemon)
mediaserverd &lt;80657170daca32c9b8f3a6b1faac43a2&gt;         4869             4869                      (daemon)
dataaccessd &lt;2a3f6a518f3f3646bf35eddd36f25005&gt;         1786             1786                      (daemon)
aosnotifyd &lt;d4d14f2914c3343796e447cfef3e6542&gt;          549              549                      (daemon)
wifid &lt;9472b090746237998cdbb9b34f090d0c&gt;          455              455                      (daemon)
SpringBoard &lt;27372aae101f3bbc87804edc10314af3&gt;        18749            18749
backboardd &lt;5037235f295b33eda98eb5c72c098858&gt;         5801             5801                      (daemon)
UserEventAgent &lt;6edfd8d8dba23187b05772dcdfc94f90&gt;          601              601                      (daemon)
mediaremoted &lt;4ff39c50c684302492e396ace813cb25&gt;          293              293                      (daemon)
pasteboardd &lt;8a4279b78e4a321f84a076a711dc1c51&gt;          176              176                      (daemon)
springboardservi &lt;ff6f64b3a21a39c9a1793321eefa5304&gt;            0                0                      (daemon)
syslog_relay &lt;45e9844605d737a08368b5215bb54426&gt;            0                0                      (daemon)
DTMobileIS &lt;23303ca402aa3705870b01a9047854ea&gt;            0                0                      (daemon)
notification_pro &lt;845b7beebc8538ca9ceef731031983b7&gt;          169              169                      (daemon)
syslog_relay &lt;45e9844605d737a08368b5215bb54426&gt;            0                0                      (daemon)
ubd &lt;74dc476d1785300e9fcda555fcb8d774&gt;          976              976                      (daemon)
twitterd &lt;4b4946378a9c397d8250965d17055b8e&gt;          730              730                      (daemon)
configd &lt;4245d73a9e96360399452cf6b8671844&gt;          809              809                      (daemon)
absinthed.N94 &lt;7f4164c844fa340caa940b863c901aa9&gt;           99               99                      (daemon)
filecoordination &lt;fbab576f37a63b56a1039153fc1aa7d8&gt;          226              226                      (daemon)
distnoted &lt;a89af76ec8633ac2bbe99bc2b7964bb0&gt;          137              137                      (daemon)
apsd &lt;94d8051dd5f5362f82d775bc279ae608&gt;          373              373                      (daemon)
networkd &lt;0032f46009f53a6c80973fe153d1a588&gt;          219              219                      (daemon)
aggregated &lt;8c3c991dc4153bc38aee1e841864d088&gt;          112              112                      (daemon)
BTServer &lt;c92fbd7488e63be99ec9dbd05824f5e5&gt;          522              522                      (daemon)
fairplayd.N94 &lt;7bd896bd00783a48906090d05cf1c86a&gt;          210              210                      (daemon)
fseventsd &lt;996cc4ca03793184aea8d781b55bce08&gt;          384              384                      (daemon)
imagent &lt;1e68080947be352590ce96b7a1d07b2f&gt;          586              586                      (daemon)
mDNSResponder &lt;3e557693f3073697a58da6d27a827d97&gt;          295              295                      (daemon)
lockdownd &lt;ba1358c7a8003f1b91af7d5f58dd5bbe&gt;          389              389                      (daemon)
powerd &lt;2d2ffed5e69638aeba1b92ef124ed861&gt;          174              174                      (daemon)
CommCenter &lt;1f425e1e897d32e8864fdd8eeaa803a8&gt;         2212             2212                      (daemon)
notifyd &lt;51c0e03da8a93ac8a595442fcaac531f&gt;          211              211                      (daemon)
ReportCrash &lt;8c32f231b2ed360bb151b2563bcaa363&gt;          337              337                      (daemon)

这日志跟我们前面见到的相差很多。

这个一个来自iOS 6的低内存崩溃日志。正如我们前面所说的,低内存崩溃日志与其他类型的崩溃日志很不一样,它们不指向特定的文件和代码行。相反,它们画出了闪退时设备上的内存使用情况的图表。

至少,头部还是跟其他崩溃日志很像的:  提供了 Incident Identifier, CrashReporter Key, Hardware Model, OS Version等信息。

接下来部分是低内存崩溃日志特有的:

  • Free pages 指可用内存页数。每页大小约是4KB, 上面的日志中,可用内存约为3,872 KB (或者说 3.9 MB)。
  • Purgeable pages 是那部分可被清除或重用的内存。在上面的日志中,是0KB。
  • Largest process是闪退时使用大部分内存的应用名称,在上面的日志中,正是你的应用!
  • Processes显示了闪退时各进程列表,还包含内存使用量。包含进程名 (*列), 进程唯一标识符(第二名), 进程使用的内存页数(第三列)。*后一列是每个应用的状态。通常,发生闪退的应用的状态是 frontmost。 这里是 Rage Masters, 使用28591 页 (or 114.364 MB) 内存——这内存太多了!

通过,*大进程和frontmost状态的应用是相同的, 而且也是引起低内存闪退的应用进程。但是也可能看到*大进程和 frontmost状态应用不同的例子。比如,如果*大进程是SpringBoard, 忽略它 , 因为 SpringBoard 进程是显示主屏幕的应用,出现在你双击home按钮等情况,而且它是一直活动的。
低内存发生时,iOS向活动的应用发出低内存警告并终止后台应用。如果前台应用仍然继续增长内存,iOS将终止它。
为了查找低内存问题的原因,你必需使用Instruments剖析应用。如果你不知道怎么做,可以看一下我们 一篇关于这个方面的教程.。 :] 另外, 你也可以走捷径,响应低内存警告通知,以解决部分闪退问题。
回到Xcode查看RMLollipopLicker.m文件。 这是实现吃棒棒糖的视图控制器。看看源代码:

#import “RMLollipopLicker.h”

#define COUNT 20

@interface RMLollipopLicker ()
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (weak, nonatomic) IBOutlet UILabel *label;
@property (weak, nonatomic) IBOutlet UILabel *lickedTimeLabel;
@end

@implementation RMLollipopLicker {
NSOperationQueue *queue;
NSMutableArray *lollipops;
}

#pragma mark – Life cycle

– (void)viewDidLoad {
[super viewDidLoad];

self.progressView.progress = 0.0;
self.label.text = [NSString stringWithFormat:@”Tap on run and I’ll lick a lollipop %d times!”, COUNT];
self.lickedTimeLabel.text = @””;

lollipops = [[NSMutableArray alloc] init];
queue = [[NSOperationQueue alloc] init];
}

– (void)lickLollipop {
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@”Lollipop” withExtension:@”plist”];
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:fileURL];
NSString *lollipop = [dictionary objectForKey:@”Lollipop”];
[lollipops addObject:lollipop];
}

#pragma mark – IBActions

– (IBAction)doneButtonPressed:(id)sender {

[self dismissViewControllerAnimated:YES completion:nil];
}

– (IBAction)runButtonPressed:(id)sender {

[sender setEnabled:NO];
[queue addOperationWithBlock:^{

for (NSInteger i = 0 ; i = COUNT) {
self.label.text = [NSString stringWithFormat:@”Tap on run and I’ll lick a lollipop %d times!”, COUNT];
self.progressView.progress = 0.0;
[sender setEnabled:YES];
}
}];
}
}];

}

@end

当用户点击运行按钮, 应用开始一个背景线程,调用 lickLollipop 方法若干次,然后更新界面反映吃棒棒糖的数量。 lickLollipop 方法从属性列表文件(PLIST)文件读取一个长字符串,然后添加到数组上。这些数据并不重要, 能在不影响用户体验的前提下重新创建。
利用每种能够清除和重建数据而不影响用户体验的情况是好习惯。这样能够方便地释放内存,减少低内存警告。
那么,如何提高代码质量呢? 实现 didReceiveMemoryWarning 方法,像下面这样处理数据:

-(void)didReceiveMemoryWarning {
[lollipops removeAllObjects];
[super didReceiveMemoryWarning];
}

搞定!

下一步?

万岁,你研究了4个闪退案例! 你的应用更完善了,并且学到了一些重要的调试技巧。
你可以到这里下载改进后的项目代码。
你喜欢iOS崩溃日志揭秘吗? 希望你能将学到的运用到你自己的应用中,也希望你能处理闪退,使你的应用更强壮!
如果你对本教程或崩溃日志有问题或意见,可以在下面发表评论。

iOS动画效果

iOS动画包括简易的动画效果,是把底层的CAAnimation做了一次完整的封装

UIView是简易的动画

// 属性动画(property animation)

具体操作步骤(注意事项,现在IOS已经不提倡使用下面方法,简易大家使用Block方法)

UIview可以使用动画的属性如下所示:

frame
bounds
center
transform
alpha
backgroundColor
contentStretch
// 属性动画(property animation)
[UIView beginAnimations:@”animation name” context:@”you parameter”]; // 开始动画(每个动画开始时执行的操作)
[UIView setAnimationDelegate:self]; // 下面是代理中的两个方法
[UIView setAnimationDidStopSelector:@selector(animationStop)];
[UIView setAnimationWillStartSelector:@selector(animationWillStart)];

// 设置动画方式,并指出动画发生的位置
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:_animationView cache:YES];
// 设置动画曲线,控制动画速度
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
CGFloat centerX = arc4random() % 200; CGFloat centerY = arc4random() % 200; view.center = CGPointMake(centerX, centerY); [UIView commitAnimations]; // 执行动画(动画结束时执行时的操作)
// + (void)setAnimationBeginsFromCurrentState:(BOOL)fromCurrentState 设置动画是否做一次反向的执行

/*
+ beginAnimations:context: 设置开始动画
+ commitAnimations 执行动画
+ setAnimationStartDate: 开始时间
+ setAnimationsEnabled: 是否可用
+ setAnimationDelegate: 代理设置
+ setAnimationWillStartSelector: 代理方法1
+ setAnimationDidStopSelector: 代理方法2
+ setAnimationDuration: 持续时间
+ setAnimationDelay: 设置延迟
+ setAnimationCurve: 设置动画曲线,控制动画速度
+ setAnimationRepeatCount: 设置重复次数
+ setAnimationRepeatAutoreverses: 是否反向执行一次
+ setAnimationBeginsFromCurrentState: 是否从当前状态开始执行
+ setAnimationTransition:forView:cache: 设置动画的效果
+ areAnimationsEnabled
*/

常用的Block方法有下面这些方法
[UIView animateWithDuration:0.3 animations:^{
containerView.center = CGPointMake(100, 200);
} completion:^(BOOL finished) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@”哈哈” message:nil delegate:nil cancelButtonTitle:@”取消” otherButtonTitles:@”确定”, nil];
[alert show];
[alert release];
}];
把之前的写在begin和commit之间的代码写到block块中
// transition

过渡动画是指一大的动画是页面之间切换的动画
CoreAnimation

CAAnimation(公共父类)

包括三个子类

CAPropertyAnimation(属性动画)

CATransition (过渡动画)
CAAnimationGroup ()
iOS中的动画 是由Core Animation来支撑的

所有动画类都是以CA开头的
CAAnimation是所有动画的基类,提供了动画的基本属性和功能(比如动画时长等)
CAAnimation有三个子类:CAPropertyAnimation.CATransition,CAAnimationGroup

CAPropertyAnimation:属性动画,属性值改变发生的动画
CAPropertyAnimation有两个子类:CABasicAnimation和CAKeyframeAnimation
CABasicAnimation(基本动画,一般都是属性值从1变到值2产生的动画)
CAKeyframeAnimation(关键帧动画,一个关键帧动画,通常包含若干个不同类型的basicAnimation).
CATransition 过渡动画,通常是页面切换时,使用的动画
CAAnimation 允许一组动画同时执行,这些动画一般都是CAPropertyAnimation子类动画

让动画的层进行动画

先从基本的CABasicAnimation开始
定义一个CABasicAnimation,使用父类的方法

CABasicAnimation *anmination = [CABasicAnimation animationWithKeyPath:@“position”]; // 属性完全来自layel

关键参数path中必须是layel中支持动画的属性

layer中支持动画的属性主要有:

contents

contentsRect
contentsCenter
opacity
hidden
masksToBounds
doubleSided
cornerRadius
borderWidth
borderColor
backgroundColor
shadowOpacity
shadowRadius
shadowOffset
shadowColor
shadowPath
filters
compositingFilter
backgroundFilters
shouldRasterize
rasterizationScale
bounds
position
zPosition
anchorPoint
transform
sublayerTransform

使用方法:
fromValue property
toValue property
byValue property
从一种状态变化到另一种状态
参数类型是ID类型

示例:

CABasicAnimation *anmination = [CABasicAnimation animationWithKeyPath:@“position”]; // 属性完全来自layel
anmination.fromValue = [NSValue valueWithCGPoint:CGPointMake(100, 100)];
anmination.duration = 3;
anmination.toValue = [NSValue valueWithCGPoint:CGPointMake(100, 400)];
containerView.layer addAnimation:anmination forKey:@”aa”];
containerView.center = CGPointMake(100, 400);
通过基本的视图功能改变视图的中心点坐标
CAKeyframeAnimation(keyframe关键帧动画)

values
path
keyTimes
timingFunctions
calculationMode
rotationMode // 决定是否该类动画沿着指定路径运动来匹配路径上的切线
tensionValues
continuityValues
biasValues
CAKeyframeAnimation *keyFrameAnimation = [CAKeyframeAnimation animationWithKeyPath:@”position.x”];
CGFloat centerX = containerView.center.x;
keyFrameAnimation.duration = 0.03;
keyFrameAnimation.values = @[
[NSNumber numberWithFloat:centerX],
[NSNumber numberWithFloat:centerX – 5],
[NSNumber numberWithFloat:centerX + 5],
[NSNumber numberWithFloat:centerX],
[NSNumber numberWithFloat:centerX – 5],
[NSNumber numberWithFloat:centerX + 5],
[NSNumber numberWithFloat:centerX],
[NSNumber numberWithFloat:centerX – 5],
[NSNumber numberWithFloat:centerX + 5],
[NSNumber numberWithFloat:centerX],
];
[containerView.layer addAnimation:keyFrameAnimation forKey:@”shake”];
// 以上代码实现一个窗口震动效果

CATransition 过渡动画效果

// NSString * const kCATransitionFade;
// NSString * const kCATransitionMoveIn;
// NSString * const kCATransitionPush;
// NSString * const kCATransitionReveal;
CATransition *caTranstion = [CATransition animation];
//caTranstion.duration = 3; // 持续时间
//caTranstion.type = @”cube”;
//caTranstion.type = @”suckEffect”;
//caTranstion.type = @”push”;
caTranstion.type = @”oglFlip”;
//caTranstion.type = @”pageCurl”;
//caTranstion.type = @”rippleEffect”;
// NSString * const kCATransitionFromRight;
// NSString * const kCATransitionFromLeft;
// NSString * const kCATransitionFromTop;
// NSString * const kCATransitionFromBottom;
caTranstion.startProgress = 0.5; // 开始位置
caTranstion.endProgress = 0.8; // 结束位置

// fillMode的作用就是决定当前对象过了非active时间段的行为. 比如动画开始之前,动画结束之后。如果是一个动画CAAnimation,则需要将其removedOnCompletion设置为NO,要不然fillMode不起作用.

// 下面来讲各个fillMode的意义

// kCAFillModeRemoved 这个是默认值,也就是说当动画开始前和动画结束后,动画对layer都没有影响,动画结束后,layer会恢复到之前的状态
// kCAFillModeForwards 当动画结束后,layer会一直保持着动画*后的状态
// kCAFillModeBackwards 这个和kCAFillModeForwards是相对的,就是在动画开始前,你只要将动画加入了一个layer,layer便立即进入动画的初始状态并等待动画开始.你可以这样设定测试代码,将一个动画加入一个layer的时候延迟5秒执行.然后就会发现在动画没有开始的时候,只要动画被加入了layer,layer便处于动画初始状态
// kCAFillModeBoth 理解了上面两个,这个就很好理解了,这个其实就是上面两个的合成.动画加入后开始之前,layer便处于动画初始状态,动画结束后layer保持动画*后的状态.
caTranstion.fillMode = kCAFillModeForwards; //
caTranstion.removedOnCompletion = NO; //
caTranstion.subtype = kCATransitionFromLeft;
[containerView.layer addAnimation:caTranstion forKey:@”transtion”];

CATransition 过渡动画效果
立方体、吸收、翻转、波纹、翻页、反翻页、镜头开、镜头关
transition.type 的类型可以有
淡化、推挤、揭开、覆盖
NSString * const kCATransitionFade;
NSString * const kCATransitionMoveIn;
NSString * const kCATransitionPush;
NSString * const kCATransitionReveal;
这四种,

transition.subtype
也有四种
NSString * const kCATransitionFromRight;
NSString * const kCATransitionFromLeft;
NSString * const kCATransitionFromTop;
NSString * const kCATransitionFromBottom;