本文讨论的是模拟NSString类封装一个自定义的字符串类,下图是完成部分功能封装的结果
OCFoundation框架中的重要组成部分,是OC的字符串类,它可以将c中的字符串转换成一个对象.下面是最简单的创建对象的一句代码:用字符串常量创建
NSString *string1 = @"字符串常量";
这里的@符号是OC中的关键字,但是我猜测它是一个运算符或者是一个特别的函数,因为string1是一个NSString类的指针,指针了堆内存中的字符串对象,那么@"字符串常量"必定要返回一个同类的指针,这一点可以在Xcode得到验证,如下图
我猜测NSString类是至少有一个字符串数组或者字符指针成员变量,它保存或者指向@"string"中的"string",要不然在NSLog输出字符串时没法找到字符串,比如
NSLog(@"%@ %p %p", string1, string1, @"字符串常量");
既然string1只是一个NSString*指针,指向的是堆内存的一个地址,它不是字符串,那么NSLog函数内部必定调用了string1的某个方法返回了其中的字符串成员变量. 另外注意这里的两个%p 它们打印的地址是一样的,这说明它们指向的是同一个地址,也就是说@""确实返回了一个NSString* 指针
下面我们来讨论封装模拟NSString类的第一步,做一个自己的@""函数,因为它是OC字符串对象创建的开始,让它返回我们需要的指针类型!
一、自定义@""
1 ➢自定义OC字符串类SHString
1 ➢自定义OC字符串类SHString
先写一个SHString类,有一个成员变量:如下
//
// SHString.m
// OC字符串
//
// Created by mac on 14-8-16.
// Copyright (c) 2014年 SouHanaQiao. All rights reserved.
//
#import "SHString.h"
@interface SHString ()
// 私有方法
- (void)_setString_:(unichar *)string;
- (unichar *)str_ing_;
@end
@implementation SHString
// 私有set方法
- (void)_setString_:(unichar *)string
{
_string = string;
}
// 私有get方法
- (unichar *)str_ing_
{
return _string;
}
- (id)init
{
if (self = [super init])
{
_string = NULL;
}
return self;
}
@end
这里的两个私有方法是setter和getter方法,由于OC是弱语法,没有真正的私有方法,所以这里的方法名尽量写得不正常,以免用户调用到.
2➢模拟@""
由于我们无法以@""来写函数,这里我们可以用宏来曲线救国,新建一个.m和.h文件,.h文件内容如下:
// operator.h
// OC字符串
// Created by mac on 14-8-16.
// Copyright (c) 2014年 SouHanaQiao. All rights reserved.
//
#ifndef OC____operator_h
#define OC____operator_h
#import "SHString.h"
SHString *func(const char *cString); // 模拟@""的函数
#define $(a) func(a) // 将函数名用$()宏代替
#endif
这样我们就可以用 $("string") 来模拟 @"string" 表达式,而且返回一个我们自定义类SHString的指针,等于是创建一个字符串对象,下面
是.m文件内容
//
// operator.c
// OC字符串
//
// Created by mac on 14-8-16.
// Copyright (c) 2014年 SouHanaQiao. All rights reserved.
//
#import "operator.h"
#import <objc/message.h>
#undef setString
// 取消sel的警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
SHString *func(const char *cString)
{
SHString *ocString = [[SHString alloc] init]; // 创建一个空的字符串对象
const char *p = cString;
const char *q = cString;
while(*q++); // 找到cString的大小
char *_string = malloc(sizeof(char) * (q - p)); // 申请内存
strcpy((char*)_string, (const char*)cString); // 拷贝字符串
// 给ocString的_string 成员赋值
SEL sel = sel_registerName("_setString_:");
objc_msgSend(ocString, sel,_string);
//[ocString performSelector:sel withObject:_string];
return ocString;
}
当调用上面的func函数,就会有一块堆内存开辟给_string指向它,并且拷贝一份字符串到其中的 _string中,然后返回这块内存的首地址(SHString* 指针). 于是,我们就可以写出这样的代码
SHString *string = $("hello");
创建一个自定义的字符串对象,但是我们无法打印,因为NSLog内部不可能有解析打印SHString类中的字符串的功能,它的%@只是指针NSString的,我们传一个SHString*的指针,肯定无法输出字符串.
这里有两个办法:1> 再次自定义一个NSLog的函数,但是这不太现实,牵扯到封装字符串之外的事,当然也可以只定义一个打印SHString类的函数,就比较.
2> 重写desciption方法,它是与NSLog函数的接口
这里为了简便,选择第二种
在SHString.m中重写该方法
在SHString.m中重写该方法
// 重写description方法
- (NSString *)description
{
if (_string) // 如果_string指针不为空
{
NSString *str = [[NSString alloc] initWithUTF8String: (char*)_string];
return str;
}
return @"";
}
至此,我们已经可以用$()来创建一个字符串对象并用NSLog打印了,
如:
//
// main.m
// 博客
//
// Created by mac on 14-8-16.
// Copyright (c) 2014年 SouHanaQiao. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "operator.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
SHString *string = $("hello world!");
NSLog(@"%@", string);
}
return 0;
}
最后为了安全可以将setter方法和getter方法方法名做个宏,如果还是有用户找到了方法名,也可以让调用失败.