NSString&NSMutableString常用操作梳理

上一篇梳理了NSArray&NSMutableArray常用操作,这次来梳理一下Objective-C中每天都要用到的字符串处理类——NSString。

Objective-C中的NSString/NSMutableString类似MFC中的CString,封装了字符串的日常基本操作。

 

1.创建初始化(Initialization&Creation

1.1 常量字符串(literal string)

NSString* constString = @"Hello, World!"; // 此处使用了字面量语法,否则需要使用alloc initWithUTF8String或stringWithUTF8String来从双引号的C String初始化NSString对象

 

变量constString并不是真正包含一个字符串对象,而指向内存中字符串对象的指针(地址),我们称之为对象标识符

以下示例中,字面量 @“Hello, World!”存储在文字常量区。指针constString1和constString2都指向它,它们在编译时生成的真实类型是NSConstantString(继承关系链——:NSSimpleCString:NSString)。

 

Objective-C里没有包或者命名空间,靠前缀来区分,NS是“NeXTSTEP”的缩写。CF是“CoreFoundation”的缩写。CFString可以看做是NSString的C底层实现。

Foundation库(Foundation.framework/Foundation.h)是有Cocoa框架提供的基本数据管理和服务功能的Objective-C接口,而Core Foundation库 (CoreFoundation.framework/CoreFoundation.h) 则是Cocoa底层实现,提供了C语言层面的接口。

 

以下介绍不可变字符串两种初始化创建方法。

After an immutable string has been initialized in the following way, it cannot be modified.

1.2 Initializing an String(NS_DESIGNATED_INITIALIZER)

- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_DESIGNATED_INITIALIZER;
 
// 从UNICODE C String中选取定长字符初始化NSString对象
- (instancetype)initWithCharacters:(const unichar*)characters length:(NSUInteger)length;
// 从UTF8 C String初始化NSString对象
- (instancetype)initWithUTF8String:(const char*)nullTerminatedCString;
// 从C String初始化NSString对象,指定编码格式。
- (instancetype)initWithCString:(const char*)nullTerminatedCString encoding:(NSStringEncoding)encoding;
 
// 从另一个NSString初始化新的NSString对象
- (instancetype)initWithString:(NSString*)aString;
// 从NSData指定编码格式初始化NSString对象
- (instancetype)initWithData:(NSData*)data encoding:(NSStringEncoding)encoding;
// 从C Buffer指定编码格式初始化NSString对象
- (instancetype)initWithBytes:(const void*)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding;
// 可变参格式化初始化NSString对象
- (instancetype)initWithFormat:(NSString*)format, ... NS_FORMAT_FUNCTION(1,2);

比较常用的有以下几个:

// UTF8 C String初始化NSString对象

- (instancetype)initWithUTF8String:(constchar *)nullTerminatedCString;

// C String初始化NSString对象,指定编码格式(例如NSUTF8StringEncoding)。

- (instancetype)initWithCString:(constchar *)nullTerminatedCString encoding:(NSStringEncoding)encoding;

 

// C Buffer指定编码格式初始化NSString对象

- (instancetype)initWithBytes:(constvoid *)bytes length:(NSUInteger)len encoding:(NSStringEncoding)encoding;

// 可变参格式化初始化NSString对象

- (instancetype)initWithFormat:(NSString *)format, ...NS_FORMAT_FUNCTION(1,2);

 

示例:

    //NSString* string = [[NSString alloc] initWithFormat:@"Hello, %s","World!"];
    NSString* string = [[NSString alloc] initWithFormat:@"Hello, %@", @"World!"];
    NSLog(@"string= %@", string);

 

关于格式化规范,参考String Format Specifiers

  • %@:OC对象描述(description)。
  • %d/%D:带符号32位整数(int):long:%ld;long long:%lld(或%qd);NSInteger:%zd(对应 size_t 长度的 signed integer)。
  • %u/%U:无符号32位整数(unsigned int),unsigned long:%lu;unsigned long long:%llu(或%qu);NSUInteger:%tu(对应 ptrdiff_t 长度的 signed integer)。
  • %o/%O:无符号32位整数(unsigned int)的八进制格式。
  • %x/%X:无符号32位整数(unsigned int)的十六进制格式,可采用 %#x 带 0x 前缀或自行添加 0x 前缀(0x%x)。
  • %hd/%hu:带/无符号16位短整型;%hhd/%hhu格式符则可打印有/无符号字符数值。
  • %c:8位无符号字符(unsigned char)。如果非ASCII码则以八进制“\\ddd”或十六进制“\\udddd”格式显示字符值。若要打印其数字,则应使用%hhu格式符。
  • %C:16位UNICODE字符(unichar)。如果非ASCII码则以八进制“\\ddd”或十六进制“\\udddd”格式显示字符值。
  • %f:64位浮点数(double)。
  • %p:打印带“0x”前缀的对象指针(地址)。如果不想带“0x”前缀,可使用%zx格式符。

%lld,%llu、%zd,%tu 等中的 d/u 均可替换为 x 以十六进制打印。

Xcode 中 'BOOL' aka 'signed char',建议以 %hhd 打印 BOOL 值。

在 Xcode armv7/arm64 Toolchain 下,sizeof 运算符的返回值 size_t(__SIZE_TYPE__) 被定义为 long unsigned int(unsigned long),可以按 %lu 格式打印。

1.3 Creating an String(autorelease)

// 创建一个字符串独享,其值为空
+ (instancetype)string;

示例:

    NSString* constString = @"";
    NSString* string = [NSString string];
    BOOL bPointerEqual = constString==string; // NO
    BOOL bContentEqualTo = [constString isEqualTo:string]; // YES
    BOOLbEqualToString = [constString isEqualToString:string]; // YES

 

以下是一些便利构造方法:

// initWithString对应的类方法
+ (instancetype)stringWithString:(NSString *)string;

// initWithCString:encoding对应的类方法
+ (instancetype)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc;

// initWithCharacters:length: 对应的类方法
+ (instancetype)stringWithCharacters:(const unichar *)characters length:(NSUInteger)length;

// initWithUTF8String对应的类方法
+ (instancetype)stringWithUTF8String:(const char *)nullTerminatedCString;

// initWithFormat对应的类方法
+ (instancetype)stringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);
+ (instancetype)localizedStringWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);

比较常用的有以下几个:

// initWithCString:encoding对应的类方法

+ (instancetype)stringWithCString:(constchar *)cString encoding:(NSStringEncoding)enc;

 

// initWithUTF8String对应的类方法

+ (instancetype)stringWithUTF8String:(constchar *)nullTerminatedCString;

// initWithFormat对应的类方法

+ (instancetype)stringWithFormat:(NSString *)format, ...NS_FORMAT_FUNCTION(1,2);

 

2.访问字符串(Querying

2.1 字符串长度(字符数组大小)

// Thenumber of Unicode characters in the receiver.
@property (readonly) NSUInteger length;

示例:

    NSString* constString1 = nil;
    NSString* constString2 = @"";
    NSString* constString3 = @"Hello, World!";
    NSString* constString4 = @"哈喽,世界!"; // 汉字+半角标点混合
   
    NSLog(@"constString1[size,length] = [%zd, %zd]", sizeof(constString1),constString1.length); // [8,0]
    NSLog(@"constString2[size,length] = [%zd, %zd]", sizeof(constString2),constString2.length); // [8,0]
    NSLog(@"constString3[size,length] = [%zd, %zd]", sizeof(constString3),constString3.length); // [8,13]
    NSLog(@"constString4[size,length] = [%zd, %zd]", sizeof(constString4),constString4.length); // [8,6]

以上可知string.length可用来判空:如果length为零,则表示字符串对象为nil或为不包含任何字符。

2.2 字符(字符数组元素)

// 获取指定索引位置/范围的字符(集)
- (unichar)characterAtIndex:(NSUInteger)index;
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange;

 

示例:

    unichar ch = [constString3 characterAtIndex:7];
    NSLog(@"ch = %c", ch); // W
    unichar* cBuf = malloc(sizeof(unichar)*constString3.length);
    [constString3 getCharacters:cBuf];
    NSString* stringFromCharacters1 = [[NSString alloc] initWithCharacters:cBuf length:constString3.length];
    NSLog(@"stringFromCharacters1 = %@", stringFromCharacters1); // @"Hello, World!"
    [stringFromCharacters1 release];
   
    [constString3 getCharacters:cBuf range:NSMakeRange(7, 6)];
    NSString* stringFromCharacters2 = [NSString stringWithCharacters:cBuf length:constString3.length];
    NSLog(@"stringFromCharacters2 = %@", stringFromCharacters2);  // @"World! World!"

2.3 C String

//Convenience to return null-terminated UTF8 representation
@property (readonly) __strong const char *UTF8String NS_RETURNS_INNER_POINTER;

2.4 取字面值

类似cstdlib中的atoi/atol/strtol/atof。

/* convenience methods all skip initial space characters (whitespaceSet)and ignore trailing characters. 
    <strong>NSScanner</strong> can be used for more"exact" parsing of numbers. */
@property (readonly) double doubleValue;
@property (readonly) float floatValue;
@property (readonly) int intValue;
@property (readonly) NSInteger integerValue NS_AVAILABLE(10_5, 2_0);
@property (readonly) long long longLongValue NS_AVAILABLE(10_5, 2_0);
@property (readonly) BOOL boolValue NS_AVAILABLE(10_5, 2_0);

2.5 大小写转换

// 所有字符转换成大写
@property (readonly, copy) NSString *uppercaseString;
// 所有字符转换成小写
@property (readonly, copy) NSString *lowercaseString;
// 每个单词首字母大写,其余字母小写。
@property (readonly, copy) NSString *capitalizedString;

 

3.查询字符串(Finding

3.1 定位子串(rangeOf)

// 返回查找到包含子串的范围
- (NSRange)rangeOfString:(NSString *)aString;
// 返回查找到包含子串的范围,可指定查询选项(忽略大小写、逆序)
- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask;
// 返回查找到包含子串的范围,可指定查询选项(忽略大小写、逆序),可进一步指定查找范围
- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)searchRange;

3.2 取子串(subString)

// 返回从指定索引到结尾的子串
- (NSString *)substringFromIndex:(NSUInteger)from;
// 返回从开头到指定索引的子串
- (NSString *)substringToIndex:(NSUInteger)to;
// 返回从指定范围(开始索引+长度)界定的子串
- (NSString *)substringWithRange:(NSRange)range;   // Hint: Use withrangeOfComposedCharacterSequencesForRange: to avoid breaking up composedcharacters

示例:

    NSString* prefix = [constString3 substringToIndex:7]; // @"Hello, "
    NSString* suffix = [constString3 substringFromIndex:7]; // @"World!"
    NSString* substr =[constString3 substringWithRange:NSMakeRange(3,6)]; // @"lo, Wo"

3.3 是否包含子串(prefix/suffix/contains)

// 是否以指定子串开头
- (BOOL)hasPrefix:(NSString *)aString;
// 是否以指定子串结尾
- (BOOL)hasSuffix:(NSString *)aString;
// 是否包含子串,注意只适用于iOS8以上!
- (BOOL)containsString:(NSString *)aString NS_AVAILABLE(10_10, 8_0);

示例:

    BOOL bHasPrefix = [constString3 hasPrefix:@"Hello,"]; // YES
    BOOL bHasSuffix = [constString3 hasSuffix:@"World!"]; // YES
    BOOL bContain= [constString3 containsString:@"lo, Wo"]; // YES

以下为NSString扩展类别(NSStringUtilities)判断是否包含子串的适配接口:

@implementation NSString (NSStringUtilities)
- (BOOL)hasContainStr:(NSString*)subStr
{
    if(!subStr) {
        return NO;
    }
    
    if([self respondsToSelector:@selector(containsString:)]) { // ≥iOS8
        return [self containsString:subStr];
    } else { // <iOS8
        NSRange range = [self rangeOfString:subStr];
        return (range.location!=NSNotFound ? YES : NO); // return (range.length>0 ? YES : NO);
    }
}
@end

3.4 查询字符集

/* These return the range of the first character from the set in the string, not the range of a sequence of characters.
*/
- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet;
- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet options:(NSStringCompareOptions)mask;
- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)aSet options:(NSStringCompareOptions)mask range:(NSRange)searchRange;
 
- (NSRange)rangeOfComposedCharacterSequenceAtIndex:(NSUInteger)index;
- (NSRange)rangeOfComposedCharacterSequencesForRange:(NSRange)range NS_AVAILABLE(10_5, 2_0);

 

4.比较字符串(Comparing

// 判断两个字符串内容是否相等
- (BOOL)isEqualToString:(NSString *)aString;
 
/* In the compare: methods, the range argument specifies the subrange,rather than the whole, of the receiver to use in the comparison.
The range is not applied to the search string.  For example, [@"AB"compare:@"ABC" options:0 range:NSMakeRange(0,1)] compares"A" to "ABC", not "A" to "A", and will return NSOrderedAscending.
*/
// 比较字符串,如果相同返回NSOrderedSame;如果不相同,返回第一个不相同的字符值比较结果(NSOrderedAscending、NSOrderedDescending)
- (NSComparisonResult)compare:(NSString *)string;
// 比较字符串,可指定比较选项(忽略大小写、逆序、按十进制值)
- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask;
// 比较字符串,可指定比较选项(忽略大小写、逆序、按十进制值),可进一步指定查找范围
- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)compareRange;
// 比较字符串,可指定查询选项(忽略大小写、逆序、按十进制值),可进一步指定查找范围,可进一步按照本地化比较
- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask range:(NSRange)compareRange locale:(id)locale; //locale arg used to be a dictionary pre-Leopard. We now accept NSLocale. Assumes the current locale if non-nil and non-NSLocale. nil continues to mean canonical compare, which doesn't depend on user's locale choice.
 
// 相当于compare:string options:NSCaseInsensitiveSearch
- (NSComparisonResult)caseInsensitiveCompare:(NSString *)string;
// 按照本地化比较
- (NSComparisonResult)localizedCompare:(NSString *)string;
// 以上两个版本组合
- (NSComparisonResult)localizedCaseInsensitiveCompare:(NSString *)string; 

 

5.替换子串(Replacing

/* Replace all occurrences of the target string in the specified range with replacement. Specified compare options are used for matching target. IfNSRegularExpressionSearch is specified, the replacement is treated as a template, as in the corresponding NSRegularExpression methods, and no other options can apply except NSCaseInsensitiveSearch and NSAnchoredSearch.*/
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange NS_AVAILABLE(10_5, 2_0);
 
/* Replace all occurrences of the target string with replacement. Invokes the above method with 0 options and range of the whole string.*/
- (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement NS_AVAILABLE(10_5, 2_0);
 
/* Replace characters in range with the specified string, returning new string.*/
- (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement NS_AVAILABLE(10_5, 2_0);

 

6.衍生字符串(Deriving

// 当前字符串追加aString生成返回一个新的NSString对象。
- (NSString *)stringByAppendingString:(NSString *)aString;
- (NSString *)stringByAppendingFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);

7.分解字符串(Separate & Join Components)

7.1 componentsSeparatedByString/componentsJointedByString

// NSString::componentsSeparatedByString接口按照分割字符(子串)来切分字符串:NSString->NSArray
- (NSArray *)componentsSeparatedByString:(NSString *)separator;
- (NSArray *)componentsSeparatedByCharactersInSet:(NSCharacterSet *)separator NS_AVAILABLE(10_5, 2_0);

// NSArray::componentsJoinedByString接口将多个字符串(字符串数组)以衔接字符连接:NSArray->NSString
- (NSString *)componentsJoinedByString:(NSString *)separator;

以下代码示例对典型的本地文件路径字符串按分隔符进行拆分分解,然后再按分隔符衔接复原:

    NSString* originString = @"do-do-sol-sol-la-la-sol";
    NSArray* separatedComponents = [originString componentsSeparatedByString:@"-"]; // 拆分
    NSLog(@"separatedComponents = %@", separatedComponents);
    NSString* jointedString = [separatedComponents componentsJoinedByString:@"-"]; //复原
    NSLog(@"jointedString = %@", jointedString);

7.2 NSPathUtilities

NSPathUtilities中NSString (NSStringPathExtensions)扩展提供了pathComponentslastPathComponentpathExtension属性用于分析文件目录/路径字符串(Path String)。

    // Mac文件系统目录路径
    NSString* pdWin8VMPath = @"/Users/faner/VM/Windows 8.1.pvm";
    // 拆分文件路径字符串
    NSArray* pathComponents = [pdWin8VMPath componentsSeparatedByString:@"/"];
    NSLog(@"separatedComponents = %@", pathComponents); // {@"",@"Users",@"faner",@"VM",@"Windows 8.1.pvm"}
    // 分析文件路径字符串
    NSString* lastPathComponent = pathComponents.lastObject; // full file name
    NSArray* lastPathSeparatedComponents = [lastPathComponent componentsSeparatedByString:@"."];
    NSString* fileName = lastPathSeparatedComponents.firstObject; // file name
    NSString* pathExtension = lastPathSeparatedComponents.lastObject; // file extension
    NSLog(@"pdWin8VMPath.fileName/pathExtension = {%@, %@}", fileName, pathExtension); // {Windows 8, pvm}
    // 复原文件路径字符串
    NSString* jointedString = [pathComponents componentsJoinedByString:@"/"];
    NSLog(@"jointedString = %@", jointedString);
    
    // NSPathUtilities.h
    NSLog(@"pdWin8VMPath.absolutePath = %d", pdWin8VMPath.absolutePath); // 1(是绝对文件路径)
    NSLog(@"pdWin8VMPath.pathComponents = %@", pdWin8VMPath.pathComponents); // {@"/",@"Users",@"faner",@"VM",@"Windows 8.1.pvm"}
    NSLog(@"pdWin8VMPath.lastPathComponents = %@", pdWin8VMPath.lastPathComponent); // Windows 8.1.pvm
    NSLog(@"pdWin8VMPath.pathExtension = %@", pdWin8VMPath.pathExtension); // pvm

注意:不同于普通的字符串,Mac绝对文件路径往往以‘/’开头,代表文件系统根节点(File System Root),故文件路径字符串中的pathComponents不能简单等同于[path componentsSeparatedByString:@"/"]!

字符串处理经常涉及到的资源路径和超链接路径的处理,它们和文件系统目录路径的构成稍有不同,一般由scheme(protocol)+resourceSpecifier构成。

本地文件系统的scheme是“file”,以下基于文件路径构建resource URL(追加"file://"前缀):

    // 基于文件路径字符串构建资源URL,例如用于共享的URL Scheme
    NSURL* pdWin8VMURL = [NSURL fileURLWithPath:pdWin8VMPath];
    NSLog(@"pdWin8VMURL = %@", pdWin8VMURL); // pdWin8VMURL.absoluteString = file:///Users/faner/VM/Windows%208.1.pvm/

构造NSURL

使用NSString相关格式化接口可以拼接构造URL,NSPathUtilities中NSString (NSStringPathExtensions)扩展提供的接口(stringByAppendingPathComponent/stringByDeletingLastPathComponentstringByAppendingPathExtension/stringByDeletingPathExtension)也可辅助拼接构造URL。

当然,NSURL类提供了更直接的便利初始化函数(Convenience initializers),支持直接基于NSString初始化创建NSURL。

7.3 NSURLPathUtilities

字符串处理经常涉及到的资源路径和超链接路径的处理,它们和文件系统目录路径的构成稍有不同,一般由scheme(protocol)+resourceSpecifier构成。WWW网站地址HTTP URL的scheme为“http”、“https”,host后的第一个‘/’代表网站根目录(WebRoot)。

NSURL.h中的NSURL (NSURLPathUtilities)扩展也提供了pathComponentslastPathComponentpathExtension属性用于URL路径字符串。

沿袭上面的例子,下面以两个小例程来分别阐述本地资源路径(asset URL)和超链接路径(HTTP URL)的典型处理。

NSURLPathUtilities Demo1:iOS相册文件asset URL

    // demo1:iOS相册文件assetURL
    NSString* iOSDCIMAssetPath = @"file:///var/mobile/Media/DCIM/101APPLE/IMG_1354.JPG";
    NSURL* iOSDCIMAssetURL = [NSURL URLWithString:iOSDCIMAssetPath];
    // absoluteString&relativeString
    NSLog(@"iOSDCIMAssetURL.absoluteString = %@", iOSDCIMAssetURL.absoluteString);
    NSLog(@"iOSDCIMAssetURL.relativeString = %@", iOSDCIMAssetURL.relativeString);
    // host&port = {(null), (null)}
    NSLog(@"iOSDCIMAssetURL.host/port = {%@, %@}", iOSDCIMAssetURL.host, iOSDCIMAssetURL.port);
    // scheme&resourceSpecifier
    NSLog(@"iOSDCIMAssetURL.scheme = %@", iOSDCIMAssetURL.scheme); // file
    NSLog(@"iOSDCIMAssetURL.resourceSpecifier = %@", iOSDCIMAssetURL.resourceSpecifier); // /var/mobile/Media/DCIM/101APPLE/IMG_1354.JPG
    // path&relativePath
    NSLog(@"iOSDCIMAssetURL.path/relativePath = {%@, %@}", iOSDCIMAssetURL.path, iOSDCIMAssetURL.relativePath); // 同resourceSpecifier
    // pathComponents = {"/", "var", "mobile", "Media", "DCIM", "101APPLE", "IMG_1354.JPG"}
    NSLog(@"iOSDCIMAssetURL.pathComponents = %@", iOSDCIMAssetURL.pathComponents);
    // lastPathComponent = pathComponents.lastObject; lastPathComponent/pathExtension = {IMG_1354.JPG, JPG}
    NSLog(@"iOSDCIMAssetURL.lastPathComponent/pathExtension = {%@, %@}", iOSDCIMAssetURL.lastPathComponent, iOSDCIMAssetURL.pathExtension);

说明:relativePath是相对baseURL而言,一般baseURL为nil,则relativePath等于path。


NSURLPathUtilities Demo2:知乎搜索“大圣归来”的HTTP URL

    // demo2:知乎搜索“大圣归来”的HTTP URL
    NSString* zhihuSearchHttpURLPath = @"http://www.zhihu.com:80/search?q=%E5%A4%A7%E5%9C%A3%E5%BD%92%E6%9D%A5&type=question"; // 汉字部分已经使用CFURLCreateStringByAddingPercentEscapes进行URLEncode。
    NSURL* zhihuSearchHttpURL = [NSURL URLWithString:zhihuSearchHttpURLPath];
    // absoluteString&relativeString
    NSLog(@"zhihuSearchHttpURL.absoluteString = %@", zhihuSearchHttpURL.absoluteString);
    NSLog(@"zhihuSearchHttpURL.relativeString = %@", zhihuSearchHttpURL.relativeString);
    // host&port = {www.zhihu.com, 80}
    NSLog(@"zhihuSearchHttpURL.host/port = {%@, %@}", zhihuSearchHttpURL.host, zhihuSearchHttpURL.port);
    // scheme&resourceSpecifier = [url componentsSeparatedByString:@":"]
    NSLog(@"zhihuSearchHttpURL.scheme = %@", zhihuSearchHttpURL.scheme); // http
    NSLog(@"zhihuSearchHttpURL.resourceSpecifier = %@", zhihuSearchHttpURL.resourceSpecifier); // 协议冒号(不包括)之后双斜杠开始至末尾
    // path&relativePath = {/search, /search}
    NSLog(@"zhihuSearchHttpURL.path/relativePath = {%@, %@}", zhihuSearchHttpURL.path, zhihuSearchHttpURL.relativePath);
    // pathComponents = {"/","search"}
    NSLog(@"zhihuSearchHttpURL.pathComponents = %@", zhihuSearchHttpURL.pathComponents);
    // lastPathComponent = pathComponents.lastObject
    NSLog(@"zhihuSearchHttpURL.lastPathComponent = %@", zhihuSearchHttpURL.lastPathComponent);
    // fragment/query,其中query为问号之后的部分
    NSLog(@"zhihuSearchHttpURL.fragment/query = {%@, %@}", zhihuSearchHttpURL.fragment, zhihuSearchHttpURL.query); // {(null), q=%E5%A4%A7%E5%9C%A3%E5%BD%92%E6%9D%A5&type=question}
    // parameterString(RFC 1808)
    NSLog(@"zhihuSearchHttpURL.parameterString = %@", zhihuSearchHttpURL.parameterString); // (null)

在处理URL时,经常要解析提取query items。主要有两种途径析取:

(1)基于分隔符‘&’和‘=’对字符串进行分解提取。

    // 解析查询字符串的key-value(parseComponentsFromQueryString)
    NSString* queryString = zhihuSearchHttpURL.query;
    NSMutableDictionary *queryDict = [NSMutableDictionary dictionary];
    if (queryString.length) {
        NSArray *kvComponents = [queryString componentsSeparatedByString:@"&"];
        for (NSString *kvComponent in kvComponents) {
            NSString *key, *value;
            
            if ([kvComponent hasContainStr:@"="]) {
                NSArray* KV = [kvComponent componentsSeparatedByString:@"="];
                key = KV.firstObject;
                value = KV.lastObject;
            } else {
                key = kvComponent;
                value = @"";
            }
            
            // 暂未考虑重名参数:k={v1,v2,...}
            [queryDict setObject:value forKey:key];
        }
    }
    // {q = "%E5%A4%A7%E5%9C%A3%E5%BD%92%E6%9D%A5"; type = question;}
    NSLog(@"parseComponentsFromQueryString = %@", queryDict);

 

(2)基于iOS7提供的新接口,基于URLString构造NSURLComponents,自动提取queryItems(NSURLQueryItem[])。

    // NS_CLASS_AVAILABLE(10_9, 7_0)提供了NSURLComponents;NS_CLASS_AVAILABLE(10_10, 8_0)进一步提供了NSURLQueryItem
    NSURLComponents* zhihuSearchHttpURLComponents =  [NSURLComponents componentsWithString:zhihuSearchHttpURLPath];
    NSLog(@"zhihuSearchHttpURLComponents.queryItems = %@", zhihuSearchHttpURLComponents.queryItems); // array of NSURLQueryItem

 

8.解析字符串NSScanner

8.1 NSNumber

可使用 NSNumberFormatter 与 NSString 之间进行便捷转换。

// Even though NSNumberFormatter responds to the usual NSFormatter methods,
//   here are some convenience methods which are a little more obvious.
- (nullable NSString *)stringFromNumber:(NSNumber *)number;
- (nullable NSNumber *)numberFromString:(NSString *)string;

 

8.2 NSScanner

NSString 提供了 intValue 等解析数值的接口(Extracting numeric values),还可利用 NSScannersscanf) 进行析取数值

@interface NSScanner : NSObject <NSCopying>

@property (readonly, copy) NSString *string;
@property NSUInteger scanLocation;
@property (nullable, copy) NSCharacterSet *charactersToBeSkipped;
@property BOOL caseSensitive;

- (instancetype)initWithString:(NSString *)string NS_DESIGNATED_INITIALIZER;

@end

@interface NSScanner (NSExtendedScanner)

// On overflow, the below methods will return success and clamp
- (BOOL)scanInt:(nullable int *)result;
- (BOOL)scanLongLong:(nullable long long *)result;
- (BOOL)scanHexInt:(nullable unsigned *)result; // Optionally prefixed with "0x" or "0X"
- (BOOL)scanHexLongLong:(nullable unsigned long long *)result NS_AVAILABLE(10_5, 2_0); // Optionally prefixed with "0x" or "0X"

@property (getter=isAtEnd, readonly) BOOL atEnd;

+ (instancetype)scannerWithString:(NSString *)string;

@end

 

9.可变字符串(NSMutableString

9.1 Initializing an String(NS_DESIGNATED_INITIALIZER)

/* In addition to these two, NSMutableString responds properly to allNSString creation methods.*/
- (NSMutableString *)initWithCapacity:(NSUInteger)capacity;
+ (NSMutableString *)stringWithCapacity:(NSUInteger)capacity;

9.2 set/append aString

// Replaces the characters of the receiver with those in a given string.aString must not be nil.
- (void)setString:(NSString *)aString;

 

setString类似于对retain propery的赋值(setter)。

字符串置为空串:=@””或setString:@””;

// Adds to the end of the receiver the characters of a given string.aString must not be nil
- (void)appendString:(NSString *)aString;
- (void)appendFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1,2);

9.3 insert/replace

- (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc;
 
// 替换
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString;
- (NSUInteger)replaceOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange;

9.4 delete

// 删除指定位置、指定长度的子串
- (void)deleteCharactersInRange:(NSRange)range;

 

10.文件、URL与字符串

10.1 从指定文件读取内容到字符串

- (instancetype)initWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;
+ (instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;

10.2 从指定url读取(下载)内容到字符串

- (instancetype)initWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error;
+ (instancetype)stringWithContentsOfURL:(NSURL *)url encoding:(NSStringEncoding)enc error:(NSError **)error;

10.3 将字符串内容写到指定url/path

/* Write to specified url or path using the specified encoding.*/
- (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)useAuxiliaryFileencoding:(NSStringEncoding)encerror:(NSError **)error;
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFileencoding:(NSStringEncoding)encerror:(NSError **)error;

 

11.对象字符串化

11.1 NSString From CGPoint/CGSize/CGRect

NSStringFromCGPoint/CGPointFromStringNSStringFromCGSize/CGSizeFromString NSStringFromCGRect/CGRectFromString

可用于对C复合类型(struct)进行OC描述(description),生成对应的字符串,方便NSLog调试输出。

11.2 NSString From Class

// 获取某个对象的具体类名,传入[obj class],相当于[obj className]
FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
 
// 基于类名获取类
FOUNDATION_EXPORT Class NSClassFromString(NSString *aClassName);

示例:

    NSLog(@"NSStringFromClass([constString2 class]) = %@", NSStringFromClass([constString2 class])); //__NSCFConstantString

以下为Xcode的Single View Application模板的main.m代码,其中UIApplicationMain函数的第4个参数需要指定delegateClassName

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

 

类似的还有NSStringFromSelector,以下为调试打印类的元数据信息示例:

#import<Foundation/Foundation.h>
#import <objc/runtime.h>// class_getName
 
@implementation NSString (NSStringDebug)
- (void)debugTrace
{
    // __FILE__ = main.m
    NSLog(@"__FILE__.fileName = %s", ((strrchr(__FILE__,'/')?:__FILE__-1)+1));
    NSLog(@"__FILE__.lastPathComponent =%@", [[NSStringstringWithUTF8String:__FILE__]lastPathComponent]);
   
    //__func__,__FUNCTION__ = -[NSString(NSStringDebug) debugTrace]
    NSLog(@"__PRETTY_FUNCTION__ = %s", __PRETTY_FUNCTION__);
   
    // className = __NSCFString
    NSLog(@"self.className = %@",self.className); // NSClassDescription.h
    NSLog(@"object_getClassName(self) = %s",object_getClassName(self));// objc/runtime.h
    NSLog(@"class_getName([self class]) =%s",class_getName([self class]));// objc/runtime.h
    NSLog(@"NSStringFromClass([self class]) =%@",NSStringFromClass([self class]));// NSObjCRuntime.h
   
    // _cmd = debugTrace
    NSLog(@"NSStringFromSelector(_cmd) =%@",NSStringFromSelector(_cmd)); // NSObjCRuntime.h
}
@end
 
int main(int argc,constchar * argv[]) {
    @autoreleasepool {
        //insert code here...
        NSString* stringEx = [NSString stringWithFormat:@"stringEx::debugTrace"];
        [stringEx debugTrace];
    }
   
    return0;
}

 

参考:《gcc 預先定義的巨集》《利用小技巧用于跟踪函数的进出过程》《Objective-C中的Debug表达式

 

11.3 Class & SEL from NSString

可通过字符串创建Class、获取Selector(SEL),然后调用performSelector实现动态调用(反射)。

以下代码片段中,hideAlertView接口试图dismiss当前顶层UIAlertView/UIAlertController,在iOS 7.x系统中调用半公开的_UIAlertManager::topMostAlert获取顶层UIAlertView。

- (void)hideAlertView
{
    if (SYSTEM_VERSION < 7.0) { // iOS 7.0之前采用UIAlertView
        for (UIWindow* w in [UIApplication sharedApplication].windows) {
            for (NSObject* o in w.subviews) {
                if ([o isKindOfClass:[UIAlertView class]]) {
                    UIAlertView *alert = (UIAlertView*)o;
                    [alert dismissWithClickedButtonIndex:alert.cancelButtonIndex animated:NO];
                }
            }
        }
    } else if (SYSTEM_VERSION < 8.0) { // iOS 8.0之前采用UIAlertView
        Class UIAlertManager = NSClassFromString(@"_UIAlertManager");
        SEL selTopMostAlert = NSSelectorFromString(@"topMostAlert");
        if ([UIAlertManager respondsToSelector:selTopMostAlert]) {
             UIAlertView *topMostAlert = [UIAlertManager performSelector:selTopMostAlert];
             [topMostAlert dismissWithClickedButtonIndex:topMostAlert.cancelButtonIndex animated:NO];
        }
    } else { // iOS 8.0之后采用UIAlertController
        UINavigationController *navCtrl = self.navigationController;
        if(navCtrl.presentedViewController && [navCtrl.presentedViewController isKindOfClass:[UIAlertController class]]) {
            [navCtrl.presentedViewController dismissViewControllerAnimated:NO completion:nil];
        }
    }
}

 

注意:以上代码中使用NSClassFromString、NSSelectorFromString涉及到的私有接口(_UIAlertManager::topMostAlert)可能审核不通过。打散格式化组合在一定程度上可以规避苹果的静态扫描检查。

 

参考:

 

Objective-C研究院之NSString字符串(三)

Objective-C研究院之NSMutableString(四)
 

NSString &Unicode》《遍历NSString每一个字符的正确方式

NSString属性用copy还是用strong?》《Use copy for NSStringproperties

 

Objective-c中 isEqual ,isEqualToString , == 三者的区别

NSString: isEqual vs.isEqualToString》《Equality》《isEqual: vsisEqualToString:

 

How to convert anNSString to hex values

NSString转换成16进制》《Objective-C NSStringto Hex

How to convertNSString to hexadecimal string of fixed block size

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页