指针问题

#include <stdio.h>  
   
int main() {  
      
    int num = 10;  
    int *p1 = &num//p1存储num的地址  
    int **p2 = &p1;//p2存储p1的地址  
    int ***p3 = &p2;//p3存储p2的地址  
      
    *p1 = 100;//*p1取出num变量  
    **p2 = 200;//**p2取出num变量  
    ***p3 = 300;//***p3也是取出num变量  
    printf("num = %d\n",num);  
    printf("num = %d\n",***p3);  
      
    return 0;  
}  
2017/8/31 posted in  iOS

消息转发机制的简单实现

第一种

首先我们来实现这两个方法:

+(BOOL)resolveInstanceMethod: (SEL)sel

+(BOOL)resolveClassMethod:(SEL)sel

这两个方法用于方法的动态解析,当一个对象执行一个方法时,发现没有找到所对应的实现。这是就要靠上面的方法来动态添加实现了

举例来说:

//
//  Person.h
//  消息转发

#import <Foundation/Foundation.h>

@interface Person : NSObject
//接口中声明一个方法
-(void)run;

@end

//
//  Person.m
//  消息转发


#import "Person.h"
#import <objc/runtime.h>
@implementation Person //在部署的时候我们没有实现接口中声明的run方法


//编写C函数实现代码
void addrun (id self, SEL _cmd){
    NSLog(@"%@ %s",self,sel_getName(_cmd));
}

+(BOOL)resolveInstanceMethod:(SEL)sel{
    if (sel ==@selector(run)) {
    //如果响应run方法,我们动态将上面实现的addrun方法添加到当前类中。
        class_addMethod(self, sel, (IMP)addrun, "v@:");
    }
    
    return [super resolveInstanceMethod:sel];
}

@end

//  main.m
//  消息转发


#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    //执行代码
        Person *man = [Person new];
        [man run];
    }
    return 0;
}

结果为:
说明执行了我们添加的那个方法,而并没有报错。

第二种

我们可以动态的向类中添加方法来解决无选择子的问题,我们也可以选择转换接受者来选择可以处理该选择子的对象。

首先当我们,没有处理第一种方法的时候,运行期系统会给我们第二个机会,询问是否有备用的接受者。我们就要实现orwardingTargetForSelector

我们举例来看:

首先我们先创建一个新类Car,并且创建一个run方法:

然后在Person.m中,不对resolveInstanceMethod进行任何处理,在forwardingTargetForSelector中返回一个Car实例:

然后继续执行main.m文件,运行为:

说明系统已经将所要实现方法的对象转换为Car的实例对象。

第三种

如果前两种我们都没有实现的话,就会进入第三种,执行完整的消息转发机制。

就会调用两个方法methodSignatureForSelectorforwardInvocation

methodSignatureForSelector方法用来指定方法签名,返回nil表示不处理,否则会进入下一步调用forwardInvocation方法。其中这个签名就是给forwardInvocation中的参数NSInvocation调用的。

开头我们要找的错误unrecognized selector sent to instance原因,原来就是因为methodSignatureForSelector这个方法中,由于没有找到run对应的实现方法,所以返回了一个空的方法签名,最终导致程序报错崩溃。

所以我们需要做的是自己新建方法签名,再在forwardInvocation中用你要转发的那个对象调用这个对应的签名,这样也实现了消息转发。

关于生成签名的类型"v@:"解释一下。每一个方法会默认隐藏两个参数,self_cmdself代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void@表示self:表示_cmd

所以第二个和第三个字符必须是“@:” ,第一个字符是返回类型.

具体的可以看一下了解Type Encodeing

2017/8/31 posted in  iOS

第24条 将类的实现代码分散到便于管理的数个分类之中

一个类里面经常会填满各种各样的方法,有时候将那么多方法放在一个类的实现文件里是合理的。但是我们如果通过OC的分类机制将类代码按逻辑划入几个分区中,这对于开发和调试都有帮助。

我们来举个例子,我们对个人信息建模分类:

#import <Foundation/Foundation.h>
Sinterface EOCPerson : NSObject
@property (nonatomic, copy, readonly) NSString *firstName; 
@property (nonatomic, copy, readonly) NSString *lastName; 
@property (nonatomic, strong, readonly) NSArray *friends;

-(id)initWithFirstName:(NSString*) firstName
            andLastName:(NSString*)lastName;
            
/* Friendship methods */
-(void)addFriend:(EOCPerson*)person;
- (void)removeFriend:(EOCPerson*)person;
-(BOOL)isFriendsWith:(EOCPerson*)person;

/* Work methods */
-(void)performDaysWork;
-(void)takeVacationFromWork;

/* Play methods */
-(void)goToTheCinema;
-(void)goToSportsGame;

在实现该类时,所有方法的代码可能会写在一个大文件里。显得很臃肿。所以可以用“分类”机制把刚才的类改写成下面这样:

@import <Foundation/Foundation.h>
@interface EOCPerson : NSObject
@property (nonatomic, strong, readonly) NSArray *friends;
@property (nonatomic, copy, readonly) NSString *lastName;
@property (nonatomic, copy, readonly) NSString *firstName; 
 
-(id) initWithFirstName: (NSString*)firstName
            andLastNames:(NSString*)lastName;

@end

@interface EOCPerson (Friendship)
-(void)addFriend:(EOCPerson*)person;
-(void)removeFriend:(EOCPerson*)person;
-(BOOL)isFriendsWith:(EOCPerson*)person; 
@end

@interface EOCPerson (Work)
-(void)performDaysWork;
-(void)takeVacationFromWork; 
@end

@interface EOCPerson (Play)
-(void)goToTheCinema;
-(void)goToSportsGame;
@end

现在,类的实现代码按照方法分成了好几个部分。所以说,这项语言特性当然就叫做“分类”啦。

但是这些代码还是在一个文件中声明的,所以我们还可以将其拆分为多个文件:

  • EOCPerson+Friendship(.h/.m)
  • EOCPerson+Work(.h/.m)
  • EOCPerson+Play(.h/.m)

比方说,与交友功能相关的那个分类可以这样写:

// EOCPerson+Friendship.h
#import "EOCPerson.h"
@interface EOCPerson (Friendship)
-(void)addFriend:(EOCPerson*)person;
-(void)removeFriend:(EOCPerson*)person;
-(BOOL)isFriendsWith:(EOCPerson*)person;
@end

// EOCPerson+Friendship.m 

#import EOCPerson+Friendship.h
@implementation EOCPerson (Friendship)
-(void)addFriend:(EOCPerson*)person {
    /*...*/
}
-(void)removeFriend:(EOCPerson*)person {
    /*...*/
}
-(BOOL)isFriendsWith:(EOCPerson*)person {
    /*...*/
}
@end

通过分类机制,可以把类代码分成很多个易于管理的小块,以便单独检视。使用分类机制之后,如果想用分类中的方法,那么要记得在引入EOCPerson.h时一并引入分类的头文件

这样使用分类之后,对于某个分类中的所有 方法来说,分类名称都会出现在其符号中。例如,“addFriend:”方法的“符号名”(symbol name)如下:

-[EOCPerson(Friendship) addFriend:]

也可以创建名为Private的分类把这种方法全都放在里面。这个分类里的方法一般只会在类或框架内部使用,而无须对外公布。这样一来,类的使用者有时可能会在査看回溯信息时发现private一词,就知道不应该直接调用此方法了。

要点

  • 使用分类机制把类的实现代码划分成易于管理的小块。
  • 将应该视为“私有”的方法归入名叫Private的分类中,以隐藏实现细节。
2017/8/31 posted in  第四章 协议与分类

iOS基础概念总结(一)

1. OC的前向声明

由于在编译 Person 类的文件时,不需要知道Dog类的全部细节(Dog类中的方法),若使用 #import "Dog.h" 则必须知道Dog.h的全部细节,而Person类中只需要知道类名Dog就可以了,可用通过以下方式告诉编译器@class Dog;这种方式叫向前声明 (forward declaring),当.m文件中要了解实现细节的时候在引入dog.h

//Person.h
#import <Foundation/Foundation.h>
@class Dog;  //前向声明
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) Dog *dog;

  • 向前声明解决了两个类互相引用的问题。
  • 向前声明可用降低编译时间,除非必要,则可以使用向前申明,并在实现文件中映入头文件,这样做可用尽量降低类之间的耦合。

2. #import和#include的区别?

#importObjective-C导入头文件的语法,可保证不会重复导入。
#include是C/C++导入头文件的语法,如果是objective-c与C/C++混编码,对于C/C++类型的文件,还是使用#include来引入,这种写法需要添加防重复导入的语法。

3. 用NSLog函数输出一个浮点类型,结果四舍五入,并保留一位小数

float money = 1.011;
NSLog(@"%.1f", money);

4.property属性的修饰符有什么样的作用

property是属性访问声明,扩号内支持以下几个属性:

  • getter=getName、setter=setName:设置setter与getter的方法名
  • readwrite、readonly:设置可供访问级别

  • assign:方法直接赋值,不进行任何retain操作,为了解决原类型与环循引用问题

  • retain:其setter方法对参数进行release旧值再retain新值,所有实现都是这个顺序

  • strong:此特质表明该属性定义了一种“拥有关系”(owning relationship)。为这种属性设置新值时,设置方法会先保留新值,并释放旧值,然后再将新值设置上去。

  • weak 此特质表明该属性定义了一种“非拥有关系”(nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似,然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)。

  • unsafe_unretained 此特质的语义和assign相同,但是它适用于“对象类型”(object type),该特质表达一种“非拥有关系”(“不保留”,unretained),当目标对象遭到摧毁时,属性值不会自动清空(“不安全”,unsafe),这一点与weak有区别

  • copy:其setter方法进行copy操作,与retain处理流程一样,先对旧值release,再copy出新的对象,retainCount为1。这是为了减少对上下文的依赖而引入的机制。

  • nonatomic:非原子性访问,不加同步, 多线程并发访问会提高性能。注意,如果不加此属性,则默认是两个访问方法都为原子型事务访问。

5. self.name=@object和_name=@object有什么不同?

self.name =”object”:会调用对象的setName()方法;name = “object”:会直接把"object"字符串赋值给当前对象的name属性。后者的效率会很高,因为没有调用set方法而是直接方法问内存。

2017/8/30 posted in  iOS

第23条:通过委托与数据源协议进行对象间通信

Objective-C语言有一项特性叫做“协议”(protocol),它与Java的“接口"(interface)类似Objective-C不支持多重继承,因而我们把某个类应该实现的一系列方法定义在协议里面。协议最为常见的用途是实现委托模式,不过也有其他用法。理解并善用协议可令代码变得更易维护,因为协议这种方式能很好地描述接口。

“分类”(Category)也是Objective-C的一项重要语言特性。利用分类机制,我们无须继承子类即可直接为当前类添加方法,而在其他编程语言中,则需通过继承子类来实现。由于 Objective-C运行期系统是髙度动态的,所以才能支持这一特性,然而,其中也隐藏着一些陷阱,因此在使用分类之前,应该先理解它。

OC中的通信方式有很多种,我们经常使用一种叫做“委托模式”(Delegate pattem)的编程设计模式来实现对象间的通信,该模式的主旨是: 定义一套接口,某对象若想接受另一个对象的委托,则需遵从此接口,以便成为其“委托对象”(delegate)。而这“另一个对象”则可以给其委托对象回传一些信息,也可以在发生相关事件时通知委托对象。

此模式可将数据与业务逻辑解耦。比方说,用户界面里有个显示一系列数据所用的视图,那么,此视图只应包含显示数据所需的逻辑代码,而不应决定要显示何种数据以及数据之间如何交互等问题。视图对象的属性中,可以包含负责数据与事件处理的对象。这两种对象分别称为“数据源"(data source)与“委托”(delegate)

我们来举个假设要编写一个从网上获取数据的类。此类也许要从远程服务器的某个资源里获取数据。那个远程服务器可能过很长时间才会应答,而在获取数据的过程中阻塞应用程序则是一种非常糟糕的做法。于是,在这种情况下,我们通常会使用委托模式:获取网络数据的类含有一个“委托对象”,在获取完数据之后,它会回调这个委托对象。

EOCDataModel对象就是EOCNetworkFetcher的委托对象。EOCDataModel请求EOCNetworkFetcher "以异步方式执行一项任务"(perform a task asynchronously),EOCNetworkFetcher在执行完这项任务之后,就会通知其委托对象,也就是EOCDataModel

利用协议机制,很容易就能以Objective-C代码实现此模式。在图4-1所演示的这种情况 下,协议可以这样来定义:

@protocol EOCNetworkFetcherDelegate 
-(void)networkFetcher:(EOCNetworkFetcher*)fetcher 
        didReceiveData:(NSData*)data;
-(void)networkFetcher:(EOCNetworkFetcher*)fetcher 
     didFailWithError:(NSError*)error;
@end

委托协议名通常是在相关类名后面加上Delegate一词,整个类名采用“驼峰法”来写。 以这种方式来命名委托协议的话,使用此代码的人很快就能理解其含义了。

然后我们要在这个EOCNetworkFetcher中设置一个属性来存放其委托对象。接口可以写成这样:

@interface EOCNetworkFetcher : NSObject 
@property (nonatomic, weak) id<EOCNetworkFetcherDelegate> delegate; 

一定要注意:这个属性需定义成weak,而非strong,因为两者之间必须为“非拥有关系” (nonowning relationship)。通常情况下,扮演delegate的那个对象也要持有本对象。所以我们要用weak来声明一种非拥有关系来避免“保留环”。

实现委托对象的办法是声明某个类遵从委托协议,然后把协议中想实现的那些方法在类 里实现出来。某类若要遵从委托协议,可以在其接口中声明,也可以在“class-contimiation分类”中声明。如果要向外界公布此类实现了某协议,那么就在接口中声明,而如果这个协议是个委托协议的话,那么通常只会在类的内部使用。所以说,这种情况一般都是在“class-continuation分类”里声明的:

@implementation EOCDataModel () <EOCNetworkFetcherDelegate> 
@end
@implementation EOCDataModel
-(void)networkFetcher:(EOCNetworkFetcher*)fetcher 
        didReceiveData:(NSData*)data {
/* Handle data */
}
-(void)networkFetcher:(EOCNetworkFetcher*)fetcher 
        didFailWithError:(NSError*)error {
/* Handle error */
}
@and

之后要用委托对象来调用方法时,必须提前使用类型信息査询方法(参见第14条)判断这个委托对象能否响应相关选择子。以EOCNetworkFetcher为例,应该这样写:

NSData *data = /*data obtained from network */;
if ([_delegate respondsToSelector:
     @selector(networkFetcher:didReceiveData:)))
{ 
    [_delegate networkFetcher:self  didReceiveData:data];
}

但是当方法变多了,我们就会频繁的来检查委托对象是否能够相应选择子,其实这个操作检测一次就可以了,所以我们可以用“位段(bitfield)”数据类型将该方法响应能力缓存起来。

这是一项乏人问津的C语言特性,但在此处用起来却正合适。我们可以把结构体中某个字段所占用的二进制位个数设为特定的值。比如像这样:

struct data {
    unsigned int fieldA : 8; 
    unsigned int fieldB : 4; 
    unsigned int fieldC : 2; 
    unsigned int fieldD : 1;
};

在结构体中,fieldA位段将占用8个二进制位,fieldB占用4个,fieldC占用两个,fieldD占用1个。于是,fieldA可以表示0至255之间的值,而fieldD则可以表示0或1这两个值。 我们举例子来说的话就是:

@interface EOCNetworkFetcher ()     {
    struct {
        unsigned int didReceiveData :   1;
        unsigned int didFailWithError : 1;      
        unsigned int didUpdateProgressTo : 1;
        }_delegateFlags;

这个结构体用来缓存委托对象是否能响应特定的选择子。实现缓存功能所用的代码可以 写在delegate属性所对应的设置方法里:

-(void)setDelegate:(id<EOCNetworkFetcherDelegate>)delegate {
    _delegate = delegate;   
    
    _delegateFlags.didReceiveData  = 
        [delegate respondsToSelector:
              @selector(networkFetcher:didReceiveData:)]; 
    
    _delegateFlags.didFailWithError =
        [delegate respondsToSelector:
              @selector(networkFetcher:didFailWithError:)]; 

    _delegateFlags.didUpdateProgressTo =
        [delegate respondsToSelector:
              @selector(networkFetcher:didUpdateProgressTo:)];

}

这样的话,每次调用delegate的相关方法之前,就不用检测委托对象是否能响应给定的选择子了,而是直接查询结构体里的标志:

if (_delegateFlags.didUpdateProgressTo) {
    [_delegate networkFetcher:self
          didUpdateProgressTo:currentProgress];
}

在相关方法要调用很多次时,值得进行这种优化。而是否需要优化,则应依照具体代码来定。这就需要分析代码性能,并找出瓶颈,若发现执行速度需要改进,则可使用此技巧。如果要频繁通过数据源协议从数据源中获取多份相互独立的数据,那么这项优化技术极有可能会提高程序效率.

要点

  • 委托模式为对象提供了一套接口,使其可由此将相关事件告知其他对象。
  • 将委托对象应该支持的接口定义成协议,在协议中把可能需要处理的事件定义成方法。
  • 当某对象需要从另外一个对象中获取数据时,可以使用委托模式。这种情境下,该模式亦称“数据源协议”(data source protocal)。
  • 若有必要,可实现含有位段的结构体,将委托对象是否能响应相关协议方法这一信息 缓存至其中。
2017/8/30 posted in  第四章 协议与分类

第二十二条 理解NSCopying协议

使用对象时经常需要拷贝它。在Objective-C中,此操作通过copy方法完成。如果想令自己的类支持拷贝操作,那就要实现NSCopying协议,该协议只有一个方法:

-(id)copyWithZone:(NSZone*)zone

为何会出现NSZone呢?因为以前开发程序时,会据此把内存分成不同的“区”(zone), 而对象会创建在某个区里面。现在不用了,每个程序只有一个区:“默认区”(default zone)。 所以说,尽管必须实现这个方法,但是你不必担心其中的zone参数。

copy方法由NSObject实现,该方法只是以“默认区”为参数来调用“copyWithZone:”。 我们总是想覆写copy方法,其实真正需要实现的却是“copyWithZone:”方法。这里我们一定要注意。

若想使某个类支持拷贝功能,只需声明该类遵从NSCopying协议,并实现其中的那个方法即可。比方说,有个表示个人信息的类,可以在其接口定义中声明此类遵从 NSCopying 协议:

#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject <NSCopying>
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName;
-(id)initWithFirstName:(NSString*)firstName
          andLastName: (NSString*)lastName;

@end

然后,实现协议中规定的方法:

-(id)copyWithZone:(NSZone*)zone {
    EOCPerson *copy = [[[self class] allocWithZone:zone]
                     initWithFirstName :_firstName
                            andLastName:_lastName];
    return copy;
}

当然我们又是也要考虑具体情况,除了要拷贝对象,还要完成其他一些操作,比如类对象中的数据结构可能并未在初始化方法中设置好,需要另行设置。

#import <Foundation/Foundation.h>
@interface EOCPerson : NSObject<NSCopying>
@property (nonatomic, copy, readonly) NSString *firstName; 
@property (nonatomic, copy, readonly) NSString *lastName;
-(id)initWithFirstName: (NSString*) firstName
            andLastName:(NSString*)lastName;
-(void)addFriend:(EOCPerson*)person;
-(void)removeFriend:(EOCPerson*)person;

@end

@implementation EOCPerson {
    NSMutableSet *_friends;
)
- (id)initWithFirstName: (NSString*) firstName
             andLastName:(NSString*)lastName { 
    if ((self = [super init])) {
    _firstName = [firstName copy];
    _lastName = [lastName copy];
    _friends = [NSMutableSet newJ;
    }
    return self;

}
-(void)addFriend:(EOCPerson*)person {
    [_friends addObject:person];
}
-(void)removeFriend:(EOCPerson*)person {
    [_friends removeObject:person];
}
-(id)copyWithZone:(NSZone*)zone {
    EOCPerson *copy =[[[self class] allocWithZone:zone]
                        initWithFirstName: _firstName
                        andLastName:_lastName]; 
    copy->_friends = [_friends mutableCopy]; 
    return copy;
}
@end

这次所实现的方法比原来多了一些代码,它把本对象的_friends实例变量复制了一份, 令copy对象的_frieiids实例变量指向这个复制过的set。注意,这里使用了->语法,因为_friends并非属性,只是个在内部使用的实例变量。其实也可以声明一个属性来表示它,不过由于该变量不会在本类之外使用,所以那么做没必要。

我们在上面发现了一个mutableCopy方法,此方法来自另一个叫做NSMutableCopying的协议。该协议与NSCopying类似,也只定义了一个方法,然而方法名不同:

- (id)mutableCopyWithZone:(NSZone*)zone

mutableCopy这个“辅助方法”(helper)与copy相似,也是用默认的zone参数来调“mutableCopyWithZone:”。如果你的类分为可变版本(mutable variant)与不可变版本 (immutable variant),那么就应该实现NSMutableCopying。若采用此模式,则在可变类中覆写“copyWithZone:”方法时,不要返回可变的拷贝,而应返回一份不可变的版本。无论当前实例是否可变,若需获取其可变版本的拷贝,均应调用mutableCopy方法。同理,若需要不可变的拷贝,则总应通过copy方法来获取。

对于不可变的NSArray与可变的NSMutableArray来说,下列关系总是成立的:

-[NSMutableArray copy] =>NSArray
-[NSArray mutableCopy】 =>NSMutableArray

所谓我们会发现:在可变对象上调用copy方法会返冋另外一个不可变类的实例。这样做是为了能在可变版本与不可变版本之间自由切换。

浅拷贝与深拷贝

在编写拷贝方法时,还要决定一个问题,就是应该执行“深拷贝”(deep copy)还是“浅拷贝”(shallow copy)深拷贝的意思就是:在拷贝对象自身时,将其底层数据也一并复制过 去。Foundation框架中的所有collection类在默认情况下都执行浅拷贝,也就是说,只拷贝容器对象本身,而不复制其中数据。这样做的主要原因在于,容器内的对象未必都能拷贝,而且调用者也未必想在拷贝容器时一并拷贝其中的每个对象

一般情况下,我们会遵照系统框架所使用的那种模式,在自定义的类中以浅拷贝的方式实现“copyWithZone:”方法。但如果有必要的话,也可以增加一个执行深拷贝的方法。以 NSSet为例,该类提供了下面这个初始化方法,用以执行深拷贝:

-(id)initWithSet:(NSArray*)array copyltems:(BOOL)copyltems

copyltem参数设为YES,则该方法会向数组中的每个元素发送copy消息,用拷贝好的元素创建新的set,并将其返回给调用者。
EOCPerson那个例子中,存放朋友对象的set是用“ copyWithZone:”方法来拷贝的, 根据刚才讲的内容可知,这种浅拷贝方式不会逐个复制set中的元素。若需要深拷贝的话, 则可像下面这样,编写一个专供深拷贝所用的方法:

-(id)deepCopy {
    EOCPerson *copy =[[[self class] alloc]
                initWithFirstName:_firstName
                      andLastName:_lastName];               
    copy->_friends = [[NSMutableSet alloc] initWithSetfriends copyltems:YES];
    return copy;
}

因为没有专门定义深拷贝的协议,所以其具体执行方式由每个类来确定,你只需决定自 己所写的类是否要提供深拷贝方法即可。另外,不要假定遵从了 NSCopying协议的对象都会执行深拷贝。在绝大多数情况下,执行的都是浅拷贝。如果需要在某对象上执行深拷贝,那 么除非该类的文档说它是用深拷贝来实现NSCopying协议的,否则,要么寻找能够执行深拷贝的相关方法,要么自己编写方法来做。

要点

  • 若想令自己所写的对象具有拷贝功能,则需实现NSCopying协议。
  • 如果自定义的对象分为可变版本与不可变版本,那么就要同时实现NSCopyingNSMutableCopying 协议。
  • 复制对象时需决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝。
  • 如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。
2017/8/29 posted in  第三章 接口与API设计