IOS字符串比较

NSString 比较字符串
NSString *value=@”123”;

比较的方法:

[value compare:(NSString *)];
[value compare:(NSString *) options:(NSStringCompareOptions)];
[value compare:(NSString *) options:(NSStringCompareOptions) range:(NSRange)];

 

 

传入的参数
compare:(NSString *)
传入一个需要比较的字符串eg:[value compare:@”123”];返回NSOrderedSame.

options:(NSStringCompareOption),传入NSStringCompareOptions枚举的值。

enum{
    NSCaselnsensitiveSearch = 1,//不区分大小写比较
    NSLiteralSearch = 2;//区别大小写比较
    NSBackwardsSearch = 4,//从字符串末尾开始搜索
    NSAnchoredSearch = 8,//搜索限制范围的字符串
    NSNumbericSearch = 64,//按照字符串里的数字为依据,算出顺序,例如foo2<foot7<foot34
    //以下定义高于mac os10.5或者高于ip   2.0可用。
    NSDiacriticlnsensitiveSearch = 128,//忽略“-”符号的比较
    NSWidthlnsensitiveSearch = 256,//忽略字符串的长度,比较出结果。
    NSForcedOrderingSearch = 512,//忽略不区分大小写比较的选项,并强制返回NSOrderedAscending 或者 NSOrderedDescending
    //以下定义高于iphone 3.2可用
    NSRegularExpressionSearch = 1024//只能应用于 rangeOfString:..., stringByReplacingOccurrencesOfString:...和 replaceOccurrencesOfString:... 方法。使用通用兼容的比较方法,如果设置此项,可以去掉 NSCaseInsensitiveSearch 和 NSAnchoredSearch
}

 

range:(NSRange)比较字符串的范围

结构变量:

location:需要比较的字符串起始位置(以0为起始)

length:需要比较的字符串长度

返回值:

 typedef enum _NSComparisonResult
{
    NSOrderedAscending = -1;//<升序
    NSOrderedSame,  //=等于
    NSOrderedDescending //> 降序
}NSComparisonResult;

 

eg:版本号比较

NSString *num1 = @"5.2.0";
NSString *num2 = @"5.3.0";

if([num1 compare:num2 options:NSNnmericSearch] == NSOrderedDescending)
{
    nslog(@"%@ is bigger",num1);
}else
{
    nslog(@"%@ is bigger",num2);
}
NSOrderedDescending是降序,如果numb1>numb2用这个函数相比较那么就等于降序。

iOS compare字符串的比较相关的使用略微整理

从前,有一个程序员,他得到了一盏神灯。灯神答应实现他一个愿望。然后他向神灯许愿,希望在有生之年能写一个好项目。后来….后来….. 他得到了永生。

 

关于字符串,这里我总结一些方法案例:

一、compare:

(判断两对象值的大小,按字母顺序进行比较)

NSString *string = @”9.1.1″;

[string compare:@”9.1.1″];返回的类型是NSComparisonResult 的NSOrderedSame(= 等于)

[string compare:@”9.2.1″];返回的是NSOrderedAscending (后面一个字符串大于前面一个 升序)

[string compare:@”9.0.1″];返回的是NSOrderedDescending (后面一个字符串小于前面一个 降序)

 

在系统内部的样式

typedef NS_ENUM(NSInteger, NSComparisonResult)

{
NSOrderedAscending = -1L,

NSOrderedSame,

NSOrderedDescending

};

一般情况下我们可以使用这个来进行版本号的对比:

例子:

NSString *num1 = @”5.2.0″;
NSString *num2 = @”5.3.0″;
if ([num1 compare:num2 options:NSNumericSearch] == NSOrderedDescending){
ULog(@”%@ is bigger”,num1);
}else{
ULog(@”%@ is bigger”,num2);
}

解释:

NSOrderedDescending是降序,如果numb1>numb2用这个函数相比较那么就等于降序。

字符串的比对结果NSComparisonResult(在NSObjCRunTime.h中定义)是按照字符的ASCII值进行比对的

NSString * str1 = @”abc”;

NSString * str2 = @”abd”;

NSString * str3 = @”ABC”;

NSString * str4 = @”abc”;

NSString * str5 = @”123″;

那么,

[str1 compare:str2] == NSOrderedAscending;//(升序)

[str2 compare:str1] == NSOrderedDescending;//(降序)

[str1 compare:str3] == NSOrderedDescending;//(降序)

[str1 compare:str4] == NSOrderedSame;//(同序)

[str1 compare:str5] == NSOrderedDescending;//(降序)

(compare的比对含义:左比右 左侧的数值小于(等于或者大于)右侧的值,即compare的目标值(右边的值)相比于参数值(左边的值)在字母表中的排序位置更靠后(相同或者靠前))

(出现问题:

原因:是由于字符串他的比对是逐位从左到右的比对的,9是比1大的。所以出现上面的问题。

同样:

%title插图%num

它的比对成功也是由于@”90.0.1″的第二位0在ASCII表中的位置在.之后。是错误的比对误造成正确的@”90.0.1″大于@”9.0.1″的样子。

同样,对于字符串长度的不一致的

%title插图%num

添加option值(改为:)

NSComparisonResult result = [string2 compare:string options:NSNumericSearch];

能够使字符串的比对添加选项option,针对数字进行比较。解决上面数据的根据字符从左至右依次比对出现的问题:

%title插图%num

扩展:

在c语言中(摘自:https://blog.csdn.net/ctrl_qun/article/details/66970652)

#include <stdio.h>

#include <string.h>

int main(void)

{
char str_1[] = “abc”;

char str_2[] = “abc”;

char str_3[] = “ABC”;

if (strcmp(str_1, str_2) == 0)

printf(“str_1 is equal to str_2. \n”);

else

printf(“str_1 is not equal to str_2. \n”);

if (strcmp(str_1, str_3) == 0)

printf(“str_1 is equal to str_3.\n”);

else

printf(“str_1 is not equal to str_3.\n”);

return 0;

}

 

上例的打印输出如下所示:

%title插图%num
strcmp()函数有两个参数,即要比较的两个字符串。strcmp()函数对两个字符串进行大小写敏感的(case-sensitiVe)和字典式的(lexicographic)比较,并返回下列值之一:
—————————————————-
返  回  值         意  义
—————————————————-
<0               *个字符串小于第二个字符串
0               两个字符串相等    ·
>0               *个字符串大于第二个字符串
—————————————————-

在上例中,当比较str_1(即“abc”)和str_2(即“abc”)时,strcmp()函数的返回值为0。然而,当比较str_1(即”abc”)和str_3(即”ABC”)时,strcmp()函数返回一个大于0的值,因为按ASCII顺序字符串“ABC”小于“abc”。

strcmp()函数有许多变体,它们的基本功能是相同的,都是比较两个字符串,但其它地方稍有差别。下表列出了C语言提供的与strcmp()函数类似的一些函数:
—————————————————————–
函  数  名                   作  用
—————————————————————–
strcmp()         对两个字符串进行大小写敏感的比较
strcmpi()        对两个字符串进行大小写不敏感的比较
stricmp()        同strcmpi()
strncmp()        对两个字符串的一部分进行大小写敏感的比较
strnicmp()       对两个字符串的一部分进行大小写不敏感的比较
—————————————————————–
在前面的例子中,如果用strcmpi()函数代替strcmp()函数,则程序将认为字符串“ABC”等于“abc”。

 

二、[ compare:(NSString *) options:(NSStringCompareOptions)];
传入 NSStringCompareOptions 枚举的值

enum{
NSCaseInsensitiveSearch = 1,//不区分大小写比较

NSLiteralSearch = 2,//区分大小写比较

NSBackwardsSearch = 4,//从字符串末尾开始搜索

NSAnchoredSearch = 8,//搜索限制范围的字符串

NSNumbericSearch = 64//按照字符串里的数字为依据,算出顺序。例如 Foo2.txt < Foo7.txt < Foo25.txt

//以下定义高于 mac os 10.5 或者高于 iphone 2.0 可用

NSDiacriticInsensitiveSearch = 128,//忽略 “-” 符号的比较

NSWidthInsensitiveSearch = 256,//忽略字符串的长度,比较出结果

NSForcedOrderingSearch = 512//忽略不区分大小写比较的选项,并强制返回 NSOrderedAscending 或者 NSOrderedDescending

//以下定义高于 iphone 3.2 可用

NSRegularExpressionSearch = 1024//只能应用于 rangeOfString:…, stringByReplacingOccurrencesOfString:…和 replaceOccurrencesOfString:… 方法。使用通用兼容的比较方法,如果设置此项,可以去掉 NSCaseInsensitiveSearch 和 NSAnchoredSearch

}

 

 

(可以多个条件一起约束

例如:

BOOL result = [astring01 compare:astring02 options:NSCaseInsensitiveSearch | NSNumericSearch] == NSOrderedSame;    //NSCaseInsensitiveSearch:不区分大小写比较 NSLiteralSearch:进行完全比较,区分大小写 NSNumericSearch:比较字符串的字符个数,而不是字符值)

三、[ compare:(NSString *) options:(NSStringCompareOptions) range:(NSRange)];
range:(NSRange)

比较字符串的范围

结构变量:

typedef struct _NSRange {
NSUInteger location;  //需要比较的字串起始位置(以0为起始)

NSUInteger length;  //需要比较的字串长度

} NSRange;

 

NSRange的使用:

NSRange range1 = {2,4};

NSRange range4 = NSMakeRange(2, 5);

//截取字符串

NSString *homebrew = @”cushy yigexinshuju caicaikan”;

 

NSLog(@”%@”, [homebrew substringWithRange:range1]);

NSLog(@”%@”, [homebrew substringWithRange:range4]);

//查找字符串

NSRange range2 = [homebrew rangeOfString: @”cai”];

NSLog(@”range:%@”, NSStringFromRange(range2));

NSRange range3 = [homebrew rangeOfString:@”xinshu”];

NSLog(@”range:%@”, NSStringFromRange(range3));

//反向查找字符串的位置

NSRange range5 = [homebrew rangeOfString:@”ju”];

NSLog(@”%@”, NSStringFromRange(range5));

NSRange range6 = [homebrew rangeOfString:@”ju” options: NSBackwardsSearch];

NSLog(@”%@”, NSStringFromRange(range6));

 

四、改变字符串的大小写
NSString *string1 = @”A String”;

NSString *string2 = @”String”;

NSLog(@”string1:%@”,[string1 uppercaseString]);//大写

NSLog(@”string2:%@”,[string2 lowercaseString]);//小写

NSLog(@”string2:%@”,[string2 capitalizedString]);//首字母大小

iOS优秀的图片压缩处理方案

背景:

*近遇到一个图片压缩的问题,项目需求压缩图片500k以内上传服务器,还要求图片要清晰一点。事实证明500k的图片肉眼识别已经是很清晰,那就没办法了,硬着头皮上了。下面是我的一些心得体会,以及考虑到的一些方向,拿出来以供大家参考。

 

思路:

本来以为很简单的问题,自己随意写了一个UIImageJPEGRepresentation的方法进行一个循环压缩不就搞定了?后来事实证明这个想法是错的,有太多东西不是你想当然的。

问题:

1.为什么不提UIImagePNGRepresentation(<#UIImage * _Nonnull image#>)?

回复:据说这个读取图片的大小会比较大,因为是png格式,读取的内容会有多图层的的问题导致读取的会显示比较大,而且比较耗时间。网上有人做过测试:同样是读取摄像头拍摄的同样景色的照片,UIImagePNGRepresentation() 返回的数据量大小为199K,而 UIImageJPEGRepresentation(UIImage* image, 1.0) 返回的数据量大小只为 140KB,比前者少了50多KB。如果对图片的清晰度要求不高,还可以通过设置 UIImageJPEGRepresentation 函数的第二个参数,来降低图片数据量。

如果还有什么问题可以继续百度,这里不进行过多赘述。

 

2.首先*个参数是我们都知道的图片image,但是第二个参数scale,一个0~1的浮点型比率,你以为0就是没有,压缩到0b大小,1.0就是原图大小?

答案是:错!首先你的图片的大小是根据(图片的宽*图片的高*每一个色彩的深度,这个和手机的硬件有关,一般是4)。其次,第二个参数苹果官方并没有明确说明这个参数的具体意义。对于大图片来说,即使你的scale选的很小,比如:0.0000000(n个0)001,但是得到的结果还是很大,这里做了一个实验:一个10M左右的图片,处理后大小为2M多。有点像是“压不动”的感觉。当然如果是小图片的话那就是没问题,能满足你的希望的压缩到的大小。

 

既然是循环压,那么就要说一下算法。考虑到递归,二分法,后来发现网上也是有的,二分法处理:更快一点压缩图片到指定的大小。先看一段代码:

//二分*大10次,区间范围精度*大可达0.00097657;*大6次,精度可达0.015625
for (int i = 0; i < 10; ++i) {
compression = (max + min) / 2;
imageData = UIImageJPEGRepresentation(image, compression);
//容错区间范围0.9~1.0
if (imageData.length < fImageBytes * 0.9) {
min = compression;
} else if (imageData.length > fImageBytes) {
max = compression;
} else {
break;
}
}
上面就是使用二分法进行处理,比for循环依次递减“高效”很多,而且也合理很多。

这样压缩到“*致”(一般我们不用进行太多的for循环,个人觉得参数到0.05已经可以了如果还是比你想要的大很多那就不要用UIImageJPEGRepresentation了),劳民伤财,劳的是cpu的高速运转,伤的是手机老化加快。哈哈,皮一下!

 

3.上面也提到了压缩“压不动”怎么办?

我们其实可以换一个方式,进行尺寸压缩:

提到尺寸压缩,你会不会很失望,看你的文章,原来也是使用UIGraphicsBeginImageContextWithOptions然后drawInRect绘制一个图片大小。

代码类似如下:

/* 根据 dWidth dHeight 返回一个新的image**/
– (UIImage *)drawWithWithImage:(UIImage *)imageCope Size:(CGSize)size {
//这里设置为0,意为自动设置清晰度,图片可以是别的传过来的图片信息
UIGraphicsBeginImageContextWithOptions(size, NO,0);
[imageCope drawInRect:CGRectMake(0, 0, size.width, size.height)];
imageCope = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return imageCope;
}
 

首先我需要说一下这个绘制很耗内存性能的,[UIImage drawInRect:]在绘制时,先解码图片,再生成原始分辨率大小的bitmap,这是很耗内存的,并且还有位数对齐等耗时操作。如果在一个方法中循环压缩比例进行代码的比例压缩,那么这种使用UIKit类进行图片绘制的话是需要先把图片读入内存然后在进行绘制,那么势必会给内存中占用大量的临时内存bitmap,而这个如果再加上循环,那么内存占有将是不可估量的。

 

4.你可能会说,我加一个自动释放池@autoreleasepool,不就好了?

错:首先这个自动释放池@autoreleasepool不要放在循环的外面,包着这个循环,原因就不过多说明,可以自行百度。然后放在for循环内部包着这个绘制的方法,你的内存并不是画完就得到了释放,内存占有的情况可以得到缓解,但是还是不能解决内存突然暴增的问题。尤其是大图片的压缩尤其明显。

 

下面就整体说明一下使用过程中的方案:

1.直接压缩图片(直接靠图片尺寸压缩绘制图片):

+ (void)compressedImageFiles:(UIImage *)image imageKB:(CGFloat)fImageKBytes imageBlock:(ReturnCompressImage)block {

__block UIImage *imageCope = image;
CGFloat fImageBytes = fImageKBytes * 1024;//需要压缩的字节Byte
__block NSData *uploadImageData = nil;

//        uploadImageData = UIImagePNGRepresentation(imageCope);
uploadImageData = UIImageJPEGRepresentation(imageCope, 1.0);
//    NSLog(@”图片压前缩成 %fKB”,uploadImageData.length/1024.0);
//    CGFloat value1 = uploadImageData.length/1024.0;

CGSize size = imageCope.size;
CGFloat imageWidth = size.width;
CGFloat imageHeight = size.height;

if (uploadImageData.length > fImageBytes && fImageBytes >0) {

dispatch_async(dispatch_queue_create(“CompressedImage”, DISPATCH_QUEUE_SERIAL), ^{

/* 宽高的比例 **/
CGFloat ratioOfWH = imageWidth/imageHeight;
/* 压缩率 **/
CGFloat compressionRatio = fImageBytes/uploadImageData.length;
/* 宽度或者高度的压缩率 **/
CGFloat widthOrHeightCompressionRatio = sqrt(compressionRatio);

CGFloat dWidth   = imageWidth *widthOrHeightCompressionRatio;
CGFloat dHeight  = imageHeight*widthOrHeightCompressionRatio;
if (ratioOfWH >0) { /* 宽 > 高,说明宽度的压缩相对来说更大些 **/
dHeight = dWidth/ratioOfWH;
}else {
dWidth  = dHeight*ratioOfWH;
}

imageCope = [self drawWithWithImage:imageCope width:dWidth height:dHeight];
//            uploadImageData = UIImagePNGRepresentation(imageCope);
uploadImageData = UIImageJPEGRepresentation(imageCope, 1.0);

//            NSLog(@”当前的图片已经压缩成 %fKB”,uploadImageData.length/1024.0);
//微调
NSInteger compressCount = 0;
/* 控制在 1M 以内**/
while (fabs(uploadImageData.length – fImageBytes) > 1024) {
/* 再次压缩的比例**/
CGFloat nextCompressionRatio = 0.9;

if (uploadImageData.length > fImageBytes) {
dWidth = dWidth*nextCompressionRatio;
dHeight= dHeight*nextCompressionRatio;
}else {
dWidth = dWidth/nextCompressionRatio;
dHeight= dHeight/nextCompressionRatio;
}

imageCope = [self drawWithWithImage:imageCope width:dWidth height:dHeight];
//                uploadImageData = UIImagePNGRepresentation(imageCope);
uploadImageData = UIImageJPEGRepresentation(imageCope, 1.0);

/*防止进入死循环**/
compressCount ++;
if (compressCount == 10) {
break;
}
}

//            NSLog(@”图片已经压缩成 %fKB”,uploadImageData.length/1024.0);
//            CGFloat value2 = uploadImageData.length/1024.0;

imageCope = [[UIImage alloc] initWithData:uploadImageData];
dispatch_sync(dispatch_get_main_queue(), ^{
if (block) {
block(imageCope);
}
});
});
} else{
if (block) {
block(imageCope);
}
}
}

/* 根据 dWidth dHeight 返回一个新的image**/
+ (UIImage *)drawWithWithImage:(UIImage *)imageCope width:(CGFloat)dWidth height:(CGFloat)dHeight{
UIGraphicsBeginImageContext(CGSizeMake(dWidth, dHeight));
[imageCope drawInRect:CGRectMake(0, 0, dWidth, dHeight)];
imageCope = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return imageCope;
}
循环压运行时截图:

%title插图%num

缺点:如上图的结果看到的这种方式使用了UIKit的UIGraphicsBeginImageContext方案来绘制图案,内存占用也是比较严重的。

优点:使用了内存的压缩率比值来进行压缩,手机的cpu消耗会相对来说比较小。

 

2.使用Image I/O相关的处理方式,使用相关的生成缩略图的形式压缩图片文件。

#pragma mark – UIImagePickerControllerDelegate
– (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
NSLog(@”info:\n%@”, info);
UIImage *image = info[UIImagePickerControllerOriginalImage];
NSData *imgData = UIImageJPEGRepresentation(image, 1.0);
NSLog(@”length1: %lu”, (unsigned long)imgData.length);

NSURL *imageURL = info[UIImagePickerControllerReferenceURL];
ALAssetsLibrary *assetsLibrary = [[ALAssetsLibrary alloc] init];
[assetsLibrary assetForURL:imageURL resultBlock:^(ALAsset *asset) {
image = [self thumbnailForAsset:asset maxPixelSize:600];
imgData = UIImageJPEGRepresentation(image, 1.0);
NSLog(@”length2: %lu”, (unsigned long)imgData.length);
NSArray * paths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@”new/ceshi.jpg”];// 保存文件的名称

BOOL result = [imgData writeToFile: filePath atomically:YES]; // 保存成功会返回YES

NSLog(@”文件保存成功?%d”,result);
} failureBlock:nil];

[picker dismissViewControllerAnimated:YES completion:^{
}];
}
– (UIImage *)thumbnailForAsset:(ALAsset *)asset maxPixelSize:(NSUInteger)size {
NSParameterAssert(asset != nil);
NSParameterAssert(size > 0);
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGDataProviderDirectCallbacks callbacks = {
.version = 0,
.getBytePointer = NULL,
.releaseBytePointer = NULL,
.getBytesAtPosition = getAssetBytesCallback,
.releaseInfo = releaseAssetCallback,
};
CGDataProviderRef provider = CGDataProviderCreateDirect((void *)CFBridgingRetain(rep), [rep size], &callbacks);
CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL);
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef) @{
(NSString *)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(NSString *)kCGImageSourceThumbnailMaxPixelSize : @(size),
(NSString *)kCGImageSourceCreateThumbnailWithTransform : @YES,
});
CFRelease(source);
CFRelease(provider);
if (!imageRef) {
return nil;
}
UIImage *toReturn = [UIImage imageWithCGImage:imageRef];
CFRelease(imageRef);
return toReturn;
}
使用ImageIO接口,避免在改变图片大小的过程中产生临时的bitmap,就能够在很大程度上减少内存的占有从而避免由此可能导致的app闪退问题。

 

思路整理:

综合上面的所有情况现在我的*终处理方案如下:

1.首先使用UIImageJPEGRepresentation进行尽可能的压缩,这里我使用二分法(考虑到手机性能问题,这里二分法设置10次(能精确到0.00097657)以内即可)处理压缩的比率参数。

2.其次根据我设置的二分法的*小可能压缩一下原图片信息,比对一下*小的二分法能处理的*大限度得到的*小图片信息能否满足条件(在你设定的目标大小以内)。以减少不必要的循环,保护cpu处理。

3.然后对处理后的图片信息,保留*大压缩比(即上面的*小二分法的scale结果),然后再进行和*终目标的大小比值,求根,然后对图像的宽和高等比压缩处理。然后再次根据*小二分法的scale以UIImageJPEGRepresentation读取结果再和你的目标大小比对,然后以此循环。直到大小小于目标大小为止。

 

这样得到的图片几乎就能够在你设定的大小以内的附近,而且图片的信息肉眼几乎看不出来多大的区别。亲自试了3M,4M,6M,10M的大图片没有发现内存消耗有太大的波动。而且压缩出来的图片清晰度很高。

 

这里上代码如下:

– (void)compressedImageFiles:(UIImage *)image
imageKB:(CGFloat)fImageKBytes imageBlock:(void(^)(NSData *imageData))block{
//二分法压缩图片
CGFloat compression = 1;
NSData *imageData = UIImageJPEGRepresentation(image, compression);
NSUInteger fImageBytes = fImageKBytes * 1000;//需要压缩的字节Byte,iOS系统内部的进制1000
if (imageData.length <= fImageBytes){
block(imageData);
return;
}
CGFloat max = 1;
CGFloat min = 0;
//指数二分处理,s首先计算*小值
compression = pow(2, -6);
imageData = UIImageJPEGRepresentation(image, compression);
if (imageData.length < fImageBytes) {
//二分*大10次,区间范围精度*大可达0.00097657;*大6次,精度可达0.015625
for (int i = 0; i < 6; ++i) {
compression = (max + min) / 2;
imageData = UIImageJPEGRepresentation(image, compression);
//容错区间范围0.9~1.0
if (imageData.length < fImageBytes * 0.9) {
min = compression;
} else if (imageData.length > fImageBytes) {
max = compression;
} else {
break;
}
}

block(imageData);
return;
}

// 对于图片太大上面的压缩比即使很小压缩出来的图片也是很大,不满足使用。
//然后再一步绘制压缩处理
UIImage *resultImage = [UIImage imageWithData:imageData];
while (imageData.length > fImageBytes) {
@autoreleasepool {
CGFloat ratio = (CGFloat)fImageBytes / imageData.length;
//使用NSUInteger不然由于精度问题,某些图片会有白边
NSLog(@”>>>>>>>>>>>>>>>>>%f>>>>>>>>>>>>%f>>>>>>>>>>>%f”,resultImage.size.width,sqrtf(ratio),resultImage.size.height);
CGSize size = CGSizeMake((NSUInteger)(resultImage.size.width * sqrtf(ratio)),
(NSUInteger)(resultImage.size.height * sqrtf(ratio)));
//            resultImage = [self drawWithWithImage:resultImage Size:size];
//            resultImage = [self scaledImageWithData:imageData withSize:size scale:resultImage.scale orientation:UIImageOrientationUp];
resultImage = [self thumbnailForData:imageData maxPixelSize:MAX(size.width, size.height)];
imageData = UIImageJPEGRepresentation(resultImage, compression);
}
}

//   整理后的图片尽量不要用UIImageJPEGRepresentation方法转换,后面参数1.0并不表示的是原质量转换。
block(imageData);

}
– (UIImage *)thumbnailForData:(NSData *)data maxPixelSize:(NSUInteger)size {
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
CGImageSourceRef source = CGImageSourceCreateWithDataProvider(provider, NULL);

CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(source, 0, (__bridge CFDictionaryRef) @{
(NSString *)kCGImageSourceCreateThumbnailFromImageAlways : @YES,
(NSString *)kCGImageSourceThumbnailMaxPixelSize : @(size),
(NSString *)kCGImageSourceCreateThumbnailWithTransform : @YES,
});
CFRelease(source);
CFRelease(provider);

if (!imageRef) {
return nil;
}

UIImage *toReturn = [UIImage imageWithCGImage:imageRef];

CFRelease(imageRef);

return toReturn;
}
运行的中进行的结果状态:

%title插图%num

综合以上对比,目前这种压缩方案无论是在内存方面还是cpu的使用方面都是很有优势,很好的能够避免因为压缩大图片导致的app闪退问题(因为循环呀大图片都是没有问题的)。

demo地址:https://github.com/KirstenDunst/CSXImageCompress

文章地址:https://blog.csdn.net/BUG_delete/article/details/84636899

demo里面我是做了批量压缩处理,对于多个大图处理,内存也是没有什么太大的波动的。这里附上demo中的批量压缩的图片存储路径:

%title插图%num

得到new文件里面的图片经过压缩的都在490kb至499kb范围之间(尽可能接近我们限定的值),图片清晰度也很高。

如有问题,欢迎指正!这里也奉献一些大图(6M,10M)以供测试:https://pan.baidu.com/s/13eexiBPy_lyJxBLXIddnZw

 

后续补充:

之前的测试中有发现使用上面demo中的方法有遇到iphone手机内存不足的手机拍的照片没有问题,再进行compressedImageFiles压缩处理的时候,会得到  “糊掉的”  图片,之后经过处理,在图片进行二分法压缩前,进行了一次图片的重绘操作解决了这个问题。以上的demo工具中的另一个方法resetSizeOfImage:能够解决这个问题。

 

扩展:

其实上面的demo中提到的Quartz2D或者UIKit的类中对图片的压缩,水印,剪切等操作,当看过CoreGraphics之后觉得图片原来也可以这么玩。它是iOS的核心图形库,包含Quartz2D绘图API接口,常用的是point,size,rect等这些图形,都定义在这个框架中,类名以CG开头的都属于CoreGraphics框架,它提供的都是C语言函数接口,是可以在iOS和mac OS 通用的。刚接触,这里的了解并不是很深入,但是是更接近底层的图像处理,操作处理上面也是有着很大的灵活性,也有可能会解答iphone内存不足遇到的压缩图片需要重绘问题。之后有时间我会再次整理一篇CoreGraphics的图片处理文章,敬请期待吧!

 

IOS学习之——CoreLocation定位的使用

1 在移动互联网时代,移动app能解决用户的很多生活琐事,比如
2 导航:去任意陌生的地方
3 周边:找餐馆、找酒店、找银行、找电影院
4
5 在上述应用中,都用到了地图和定位功能,在iOS开发中,要想加入这2大功能,必须基于2个框架进行开发
6 Map Kit :用于地图展示
7 Core Location :用于地理定位
8
9 2个热门专业术语
10 LBS :Location Based Service
11 SoLoMo :Social Local Mobile(索罗门)
12
13 CoreLocation框架使用前提
14 导入框架
15
16 导入主头文件
17 #import <CoreLocation/CoreLocation.h>
18
19 CoreLocation框架使用须知
20 CoreLocation框架中所有数据类型的前缀都是CL
21 CoreLocation中使用CLLocationManager对象来做用户定位
22
23 CLLocationManager
24
25 CLLocationManager的常用操作
26 开始用户定位
27 – (void)startUpdatingLocation;
28
29 停止用户定位
30 – (void) stopUpdatingLocation;
31
32 当调用了startUpdatingLocation方法后,就开始不断地定位用户的位置,中途会频繁地调用代理的下面方法
33 – (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations;
34 locations参数里面装着CLLocation对象
35
36
37 CLLocation
38
39 CLLocation用来表示某个位置的地理信息,比如经纬度、海拔等等
40 @property(readonly, nonatomic) CLLocationCoordinate2D coordinate;
41 经纬度
42
43 @property(readonly, nonatomic) CLLocationDistance altitude;
44 海拔
45
46 @property(readonly, nonatomic) CLLocationDirection course;
47 路线,航向(取值范围是0.0° ~ 359.9°,0.0°代表真北方向)
48
49 @property(readonly, nonatomic) CLLocationSpeed speed;
50 行走速度(单位是m/s)
51
52 用- (CLLocationDistance)distanceFromLocation:(const CLLocation *)location方法可以计算2个位置之间的距离
53
54
55 CLLocationManager
56
57 @property(assign, nonatomic) CLLocationDistance distanceFilter;
58 每隔多少米定位一次
59
60 @property(assign, nonatomic) CLLocationAccuracy desiredAccuracy;
61 定位精确度(越精确就越耗电)
62
63 CLLocationCoordinate2D
64
65 CLLocationCoordinate2D是一个用来表示经纬度的结构体,定义如下
66 typedef struct {
67         CLLocationDegrees latitude; // 纬度
68         CLLocationDegrees longitude; // 经度
69 } CLLocationCoordinate2D;
70
71 一般用CLLocationCoordinate2DMake函数来创建CLLocationCoordinate2D
72
73
74 用户隐私的保护
75
76 从iOS 6开始,苹果在保护用户隐私方面做了很大的加强,以下操作都必须经过用户批准授权
77 要想获得用户的位置
78 想访问用户的通讯录、日历、相机、相册等等
79
80 当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权
81
82 从iOS 8开始,用户定位分两种情况
83 总是使用用户位置:NSLocationAlwaysUsageDescription
84 使用应用时定位:NSLocationWhenInUseDescription
85
86 当想访问用户的隐私信息时,系统会自动弹出一个对话框让用户授权
87
88
89 CLGeocoder
90
91 使用CLGeocoder可以完成“地理编码”和“反地理编码”
92 地理编码:根据给定的地名,获得具体的位置信息(比如经纬度、地址的全称等)
93 反地理编码:根据给定的经纬度,获得具体的位置信息
94
95 地理编码方法
96 – (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;
97
98 反地理编码方法
99 – (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;
100
101
102 CLGeocodeCompletionHandler
103
104 当地理\反地理编码完成时,就会调用CLGeocodeCompletionHandler
105 typedef void (^CLGeocodeCompletionHandler)(NSArray *placemarks, NSError *error);
106 这个block传递2个参数
107 error :当编码出错时(比如编码不出具体的信息)有值
108 placemarks :里面装着CLPlacemark对象
109
110 CLPlacemark
111
112 CLPlacemark的字面意思是地标,封装详细的地址位置信息
113 @property (nonatomic, readonly) CLLocation *location;
114 地理位置
115
116 @property (nonatomic, readonly) CLRegion *region;
117 区域
118
119 @property (nonatomic, readonly) NSDictionary *addressDictionary;
120 详细的地址信息
121
122 @property (nonatomic, readonly) NSString *name;
123 地址名称
124
125 @property (nonatomic, readonly) NSString *locality;
126 城市

关于 iOS 和 android 电话权限的一些疑惑

三年前国内 Android 生态一团糟,很多应用乱要权限否则不能使用。
微信:电话,存储

手机淘宝:电话

支付宝:存储

百度地图:存储

等等等,去年逐渐发现很多应用不给权限都能正常使了(大概由于各种曝光和监管,也算是一种进步了)

之前发现 iOS 13 能够实现手机号码自动登录各大应用(使用运营商认证服务,才发现 iOS 和 Android 上不让获取电话权限没卵用,运营商已经通过认证服务提供的 api 把用户的身份全卖了)。

我的疑惑是,我在用*新版的 iOS 14.4,*近发现各大应用的运营商认证服务都不能用了(包括移动,联通,电信自家营业厅也不能用),但是各大 Android 平台运营商认证服务都还正常?目测 iOS 新的 SDK 做了限制?一时想不明白

 

8 条回复    2021-02-07 07:25:59 +08:00
dingwen07
    1

dingwen07   61 天前 via iPhone

安卓给电话权限能拿到 ICCID 和 SIM 卡号码(中国移动不写入、但是好像 ROM 里手动设置的也会被读取到),现在国产软件不用电话权限做设备识别码了,因为没有 IMEI
iOS 啥都不能

电话号码都是靠运营商 API 的,什么权限都没法阻挡这个,但是理论上你点击确认按钮之前软件本身是获取不到手机号的

dingwen07
    2

dingwen07   61 天前 via iPhone

iOS 14.4 哔哩哔哩可以获取

![IMG.jpg]( https://i.loli.net/2021/02/05/Ffw2uE7CyKsWgLU.jpg)

processzzp
    3

processzzp   61 天前 via iPhone   ❤️ 8

@dingwen07
三家运营商的本机号码认证业务域名

移动: *.cmpassport.com
联通: *.hmrz.wo.cn
电信: *.e.189.cn

通过你喜欢的方法,把上述域名的流量屏蔽掉就可以了。

dvbuzhidao1
    4

dvbuzhidao1   61 天前

@processzzp 但是 iOS 获取不到是什么鬼,好像并没有机制阻止 iOS 应用调用认证业务域名?包括运营商自家的应用
jim9606
    5

jim9606   61 天前

电话权限是为了 IMEI,但从 Android10 开始获取不到了,所以取消强制获取。

存储是因为 Android 11 上 target 为 11 的 APP 强制启用分区存储,那堆直接往 sd 卡目录拉屎的老代码懒得改也不得不改了,不然以后上不了商店。

你以为是监管和良心发现,哪有这种好事,都是平台带头大哥逼的。

虽然 ISP 的认证 SDK 在点同意前只能获得部分手机号,不过我寻思拿这个+定位+临时 ID,跟踪用户也是够用的。

unco020511
    6

unco020511   61 天前

至少可以选择不用运营商快捷登录
zjm947373
    7

zjm947373   61 天前

@jim9606 #5 确实有部分是监管的原因,工信部这几年一直在整治
https://www.miit.gov.cn/jgsj/xgj/fwjd/index.html
bclerdx
    8

bclerdx   60 天前 via Android

@zjm947373 整治的好。

求推荐一个 IOS 上带快速标记的会议录音 APP, 类似 Recordium

RT 业务需要进行会议或讲座录音。之前用过讯飞听见和 Notta,不过感觉他们的重点是 语音-文字转录。

但实际上因为现场的收音状况不太可能一直都很理想,所以实际还是需要自己听语音。

比起一边听一边在 APP 里校准,我觉得直接挑选并跳跃到需要的内容进行听写会更有效率。

所以,想问问有没有带快速标记的录音 APP,比如听到重点可以点一下,这样就知道这个标记前几分钟的内容,或标记与标记之间的内容需要听写。

如果是买断制的话会更好。

我记得以前有一个 Recordium 很好用,但是很久很久以前就下架了。

notability或者noted

iOS图片加工—图片水印,图片裁剪和屏幕截图

一.图片水印

1.创建个UIImageView
@property (weak, nonatomic) IBOutlet UIImageView *neImage;

2.创建个方法实现水印功能
– (void)viewDidLoad {
[super viewDidLoad];

UIImage *bgImage = [UIImage imageNamed:@””];

//创建一个位图上下文
UIGraphicsBeginImageContextWithOptions(bgImage.size, NO, 0.0);

//将背景图片画入位图中
[bgImage drawInRect:CGRectMake(0, 0, bgImage.size.width, bgImage.size.height)];

//将水印Logo画入背景图中
UIImage *waterIma = [UIImage imageNamed:@””];
[waterIma drawInRect:CGRectMake(bgImage.size.width – 40 – 5, bgImage.size.height – 40 – 5, 40, 40)];

//取得位图上下文中创建的新的图片
UIImage *newimage = UIGraphicsGetImageFromCurrentImageContext();

//结束上下文
UIGraphicsEndImageContext();

//在创建的ImageView上显示出新图片
self.neImage.image = newimage;

//压缩新照片为PNG格式的二进制数据
NSData *data = UIImagePNGRepresentation(newimage);

//将图片写入到手机存储中
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@”new.png”];
[data writeToFile:path atomically:YES];

}
二.图片裁剪

//1.加载原图
UIImage *oldImage = [UIImage imageNamed:@”me”];

//2.获取位图上下文
CGFloat bigCic = oldImage.size.width + 2 * 2;
UIGraphicsBeginImageContextWithOptions(CGSizeMake(bigCic, bigCic), NO, 0.0);

//3.画大圆
[[UIColor whiteColor] set];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddArc(ctx, bigCic * 0.5, bigCic * 0.5, bigCic * 0.5, 0, M_PI * 2, 0);
CGContextFillPath(ctx);

//4.画小圆
CGFloat smallCic = oldImage.size.width * 0.5;
CGContextAddArc(ctx, bigCic * 0.5 , bigCic * 0.5, smallCic, 0, M_PI * 2, 0);
CGContextClip(ctx);

//5.画图
[oldImage drawInRect:CGRectMake(2, 2, oldImage.size.width, oldImage.size.height)];

//6.获取新图
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

//7.结束上下文
UIGraphicsEndImageContext();

//8.显示新图
self.IconView.image = newImage;

//9.写入到手机存储
NSData *data = UIImagePNGRepresentation(newImage);
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@”newClip.png”];
[data writeToFile:path atomically:YES];
三,屏幕截图

//1.开启位图上下文
UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0.0);

//2.渲染截图
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];

//3.获取新图
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

//4.写入到手机存储
NSData *data = UIImagePNGRepresentation(newImage);
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@”newClip.png”];
[data writeToFile:path atomically:YES];

//5.关闭上下文
UIGraphicsEndImageContext();

25条提高iOS App性能的技巧和诀窍

这篇文章来自iOS Tutorial Team 成员 Marcelo Fabri, 他是 Movile 的一个iOS开发者. Check out his personal website or follow him on Twitter.原文地址

     当我们开发iOS应用时,好的性能对我们的App来说是很重要的。你的用户也希望如此,但是如果你的app表现的反应迟钝或者很慢也会伤害到你的审核。

然而,由于IOS设备的限制有时很难工作得很正确。我们开发时有很多需要我们记住这些容易忘记的决定对性能的影响。

这是为什么我写这篇文章的原因。这篇文章用备忘录的形式集合了25个技巧和诀窍可以用来提高你的app性能。所以保持阅读来给你未来的App一个很不错的提高。

      Note:在优化代码之前,必须保证有个需要解决的问题!不要陷入”pre-optimizing(预优化)”你的代码。勤 用Instruments分析你的代码,发现任何一个需要提高的地方。Matt Galloway写了一个使用Instruments优化代码的的教程

   

    以下这些技巧分为三个不同那个的级别—基础,中级,高级。

   基础

   这些技巧你要总是想着实现在你开发的App中。

1. 用ARC去管理内存(Use ARC to Manage Memory)

2.适当的地方使用reuseIdentifier(Use a reuseIdentifier Where Appropriate)

3.尽可能设置视图为不透明(Set View as Opaque When Possible)

4.避免臃肿的XIBs文件(Avoid Fat XiBs)

5.不要阻塞主进程(Don’t Block the Main Thread)

6.调整图像视图中的图像尺寸(Size Images to Image Views)

7.选择正确集合(Choose the Correct Collection)

8.启用Gzip压缩(Enable GZIP Compression)

 

   中级

   这些技巧是当你遇到更复杂的情况的时候使用。

9. 重用和延迟加载视图(Reuse and Lazy Load Views)

10.缓存,缓存,缓存(Cache,Cache,Cache)

11.考虑绘图(Consider Drawing)

12.处理内存警告(Handle Memory Warnings)

13.重用大开销对象(Reuse Expensive Objects)

14.使用精灵表(Use Sprite Sheets )

15.避免重复处理数据(Avoid Re-Processing Data)

16.选择正确的数据格式(Choose the Right Data Format)

17.适当的设置背景图片(Set  Background Images Appropriately)

18.减少你的网络占用(Reduce Your Web Footprint)

19.设置阴影路径(Set the Shadow Path )

20.你的表格视图Optimize Your Table Views)

21.选择正确的数据存储方式(Choose Correct Data Storage Option)

 

高级

   这些技巧你应该只在你很积*认为它们能解决这个问题,而且你觉得用它们很舒适的时候使用。

22.加速启动时间(Speed up Launch Time )

23.使用自动释放池(Use AutoRelease Pool)

24.缓存图像(Cache Images-Or not )

25.尽可能避免日期格式化器(Avoid Date Formatters Where Possible)

没有其他的,一起去看看这些技巧吧!

 

 基础的性能提升

1)用ARC去管理内存

ARC是伴随IOS5 一起发布的,它用来消除常见的的内存泄漏。

ARC是”Automatic Reference Counting”的缩写。它自动管理你代码中的retain/release循环,这样你就不必手动做这事儿了。

下面这段代码展示了创建一个view的常用代码

查看源码

打印 ?

1 UIView *view =[[UIView alloc] init];
2 //...
3 [self.view addSubview:view];
4 [view release];

这里*其容易忘记在代码结束的地方调用release,ARC将会自动的,底层的为你做这些工作。

除了帮助你你避免内存泄漏,ARC还能保证对象不再使用时立马被回收来提高你的性能。你应该在你的工程里多用ARC。

这里是一些学习更多关于ARC的非常棒的资源

  • Apple’s official documentation 苹果的官方文档。
  • Matthijs Hollemans’s Beginning ARC in iOS Tutorial
  • Tony Dahbura’s How To Enable ARC in a Cocos2D 2.X Project
  • 如果你还是不确信ARC的好处,看看这篇文章 eight myths about ARC 说服你为什么用ARC。

值得注意的是ARC不能消除所有的内存泄漏。你依然有可能内存泄漏,这主要可能是由于blocks(块),引用循环,CoreFoundation对象管理不善(通常是C结构体,或者是确实很糟糕的代码)。

2)适当的地方使用reuseIdentifier

在app开发中的一个常见的为UITableViewCells,UICollectionViewCells,UITableViewHeaderFooterViews设置一个正确的reuseIdentifier(重用标识)。

为了*大化性能,一个tableView的数据源一般应该重用UITableViewCell对象,当它在tableView:cellForRowAtIndexPath:中分配数据给cells的时候。一个表视图维护了一个UITableViewCell对象的队列或者列表,这些对象已被数据源标记为重用。

如果你不用reuseIdentifier 会怎么样呢?

如果你用,你的tableview每显示一行将会配置一个全新的cell。这是非常费事的操作而且*对会影响你app滚动的性能。

自从引进了iOS6,你应该为header and footer 视图设置reuseIdentifiers,就像在 UICollectionView’s cells 和 supplementary views(补充视图)一样。

使用reuseIdentifiers,当你的数据源要求提供一个新的cell给tableview的时候调用这个方

查看源码

打印 ?

1 NSString *CellIdentifier = @"Cell";
2  
3 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

3)可能的时候设置视图为不透明

如果你有不透明视图(opaque views)–也就是说,没有透明度定义的视图,你应该设置他们的opaque属性为YES。

为什么? 这会允许系统以*优的方式绘制你的views。这是一个简单的属性可以在Interface Builder 和代码中设置。

苹果的文档 Apple documentation中有对这个属性的描述

这个属性提供了一个提示给图系统如何对待这个视图。如果设置为YES,绘制系统将会把这个视图视为完全不透明。这样允许系统优化一些绘制操作和提高性能。如果设置为NO,绘图系统会复合这个视图和其他的内容,这个属性的默认值是YES

在相对静态的屏幕上,设置opaque属性不会有什么大问题。尽管如此,如果你的视图是嵌入在一个scrollView,或者是一个复杂的动画的一部分,不设置这个属性*对会影响你的程序的性能。

你也可以使用Debug\Color olor Blended Layers选项 在你的模拟器中形象化的看见没有设置为不透明(opaque)的视图.你的目标应该是尽可能多的设置视图为透明。

4)  避免臃肿的XIB文件

故事板,由iOS5引进,很快的替代XIBs。尽管如此,XIBs在一下情况下依然是很有用的。如果你需要在IOS5之前版本的设备上运行或者你想自定义重用的视图,那么你确实不能避免使用它们。

如果你专注使用XIBs,那么让它们尽量的简单。尝试为一个试图控制器创建一个XIB,如果可能的话,把一个视图控制器的视图分层管理在单独的XIBs中。

注意当你加载一个XIB到内存的时候,它所有的内容都会载入内存,包括所有的图片。如果你有视图但不是要立即使用,那你就浪费了珍贵的内存。值得注意的是这不会发生在故事板中,因为故事版只会在需要的时候实例化一个视图控制器。

当你载入一个xib,所有的图像文件会被缓存,如果是开发OSX,那么音频文件也会被缓存。

Apple’s documentation 如是说:

当你载入一个包含了图和声音资源引用的nib文件时,nib加载代码读取实际的图片文件和音频文件到内存中并缓存它。在OS X中,图片和音频资源被存储在已命名的缓存 中这样你可以在之后需要的时候访问它们。在iOS中,只有图片资源被缓存,访问图片,你使用NSImage或者UIImage的imageNamed:方法来访问,具体使用取决于你 的平台。

显然这也发生在使用故事板的时候。尽管如此,我还不能找到这种说法的证据。如果你知道,请给我留言。

想学习更多关于故事板的更多内容吗?看看Matthijs Hollemans的 Beginning Storyboards in iOS 5 Part 1and Part 2.

5)不要阻塞主进程

你永远不应该在主线程中做任何繁重的工作。这是因为UIKIt的所有工作都在主线程中进行,比如绘画,管理触摸,和响应输出。

你的app的所有工作都在主线程上进行就会有阻塞主线程的风险,你的app会表现的反应迟钝。这是在App Store里获一星评论的快速途径!(作者卖萌..)

阻塞主线程*多的情况就是发生在你的app进行I/O操作,包括牵扯到任何需要读写外部资源的任务,比如读取磁盘或者网络

你可以异步的执行网络任务使用NSURLConnection中的这个方法:

查看源码

打印 ?

1 + (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler

或者使用第三方框架比如 AFNetworking.

如果你在做任何大开销的操作(比如执行一个耗时的计算,或者读写磁盘)使用Grand Central Dispatch(GCD)或者 NSOperations 和 NSOperationQueues.

使用GCD的模板如下代码所示:

查看源码

打印 ?

01 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
02  
03     // switch to a background thread and perform your expensive operation
04  
05   
06  
07     dispatch_async(dispatch_get_main_queue(), ^{
08  
09         // switch back to the main thread to update your UI
10  
11   
12  
13     });
14  
15 });

这里为什么dispatch_async 嵌套在*个的里面?这是因为任何UIKit相关的代码都必须在主线程上执行。

对NSOperation和GCD的详情感兴趣?看看Ray Wenderlich’s Multithreading and Grand Central Dispatch on iOS for Beginners 教程,和 Soheil Azarpour’s How To Use NSOperations and NSOperationQueues 教程。

6)调整图像视图中的图像尺寸

如果你用UIImageView呈现app束中的图片时,确认图片和UIImageView的尺寸相同。缩放图片会非常的耗时,特别是当你的UIImageView被嵌入UIScrollView。

如果图片是从远程服务器上下载的,有时你没法控制图片尺寸,或者你不能在服务器上在下载之前缩放它。在这些情况下你可以在图片下载完成后手动缩放一次,*好是在后台进程中。然在UIImageView中使用调整尺寸之后的图片。

7)选择正确集合

学着怎么在手头工作中使用*合适的类或对象是写出高效代码的基本。当时用集合是(collections),这个说法特别对。

可喜的是在苹果开发者文档( Collections Programming Topics)中有详细解释可用类之间的关系,还有解释各个类的适用情况。这个文档是每个使用集合的人的必读文档。

这是一个*常见的集合类型的快速简介:

  • Arrays:有序的值的列表,用index快速查找,通过值查找慢,insert/delete操作慢。
  • Dictionaries:存储键/值对.用index快速查找。
  • Sets: 无序的值列表。通过值快速查找,insert/delete快。

8)启用Gzip压缩

大量和持续增长的app依赖从远端服务器或者外部APIs获取的外部数据。某些时候你可能会开发一些需要下载XML,JSON,HTML或者其他文本格式的应用。

问题是移动设备不能保证网络环境,用户可能一分钟在边缘网络,下一分钟又是3G网络,无论什么情况下,你不想你的用户一直等待。

一个减少文件大小并加速下载的网络资源的方法是同时在你的服务器和客户端上使用GZIP压缩,对于文本数据这种有高比率压缩的数据来说非常有用。

好消息是iOS早已默认支持GZIP压缩,如果你是使用NSURLConnection或者建立在这之上的框架比如AFNetworking。更好的消息是一切云服务提供商像 Google App Engine早已发送压缩之后的响应数据。

这里有一篇文章great article about GZIP compression 介绍如何在你的Apache或IIS服务器上启用GZIP。

中级性能提升

好的,当谈到优化你的代码时,你应该很自信你已经初级的方法已经完全掌握了。但有时候有的问题的解决方法并不是那么显而易见,它由你app的结构和代码决定,尽管如此,在正确的上下文中,它们可能是没有价值的。

9)重用和延迟加载视图
越多的视图就有越多的绘图操作,*终意味着更多的CPU和内存开销。这说得特别对如果你的app嵌入很多视图在UIScrollView时。

管理这个的技巧是去模拟UITableView 和 UICollectionView的行为:不要一次创建所有的子视图,而是在需要的时候创建,然后把他们假如重用队列中。

这样,你只需要在视图浮动时配置你的视图,避免昂贵的资源分配开销。

视图创建的时机问题也同样适用于你app的其他地方。试想当你点击一个button时呈现一个视图的情景。至少有两种方法:

1.屏幕*次载入时创建视图并隐藏它。当你需要的时候,显示出来。

2.需要呈现的时候一次创建视图并显示它。

每种方法都有各自的优缺点

使用*种方法,你消耗了更多内存因为从创建开始到它释放前你都保持了它的内存,然而,当你点击button的时候,你的app会表现得响应快速因为它只需要更改视图的可视化属性。

使用第二种方法会有相反的效果,在需要的时候创建视图,消耗更少的内存,但当button被点击时应用会表现得不那么响应快速。

10)缓存,缓存,缓存

在开发应用时的一个伟大的经验是”Cache what matters”–也就是说那些不大会改变但会平凡被访问的东西。

你能缓存些什么呢?缓存的候选项有远程服务器的响应,图片,已计算过的值(比如UITableView的行高)。

NSURLConnection 根据处理的Http头缓存资源到磁盘或者内存中,你甚至可以手动创建一个NSURLRequest值加载缓存过的值。

这里有一段很棒的代码,用在任何时候你需要针对一个不大会改变的图片创建一个NSURLRequest。

查看源码

打印 ?

01 + (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {
02  
03    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
04  
05  
06    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image
07  
08     request.HTTPShouldHandleCookies = NO;
09  
10     request.HTTPShouldUsePipelining = YES;
11  
12     [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
13  
14     return request;
15  
16 }

如果想知道更多关于Http caching,NSURLCache,NSURLConnection等内容,请阅读the NSURLCache entry

注意,你可以通过NSURLConnection获取取一个URL请求,AFNetworking也可以。有了这个技巧这样你不用改变任何你的网络代码。

如果要缓存不牵扯到HTTP请求的其他东西,NSCache是很好的选择。

NSCache像NSDictionary,但是当系统需要回收内存的时候会自动的移除内容。

对HTTP Cache感兴趣并想学更多的内容?推荐阅读这篇文章best-practices document on HTTP caching

11)考虑绘图

在IOS中有很多方法可以制作拥有很棒外观的buttons,你可以是由全尺寸的图像,也可以使用调整尺寸之后的图像,或者你用CALayer,CoreGraphics,甚至OpenGL手动的它们。

当然,每种途径都有不同的复杂度级别和不同的性能,这篇文章非常值得一读post about iOS graphics performance here,这是Apple UIKit团队成员Andy Matuschak发表的文章,里面对各种方法有一些非常棒的见解和对性能的权衡。

使用预渲染图片更快,因为iOS不用创建一张图像和绘制图形到屏幕上(图像已经处理好了)。问题是你需要全部把这些图片放进应用束里,增加它的尺寸。那就是为什么使用可调整尺寸的图片是那么好:你通过移除”浪费了的“图片空间来节约空间。你也不需要为不同的元素生成不同的图片。(例如 buttons)

尽管如此,用图片你会失去代码调整你图片的能力,需要一次又一次的生成它们然后把它们加入到应用中。这是个缓慢的过程。另外一点如果你有动画或者很多张稍微变化的图片(例如 颜色叠加),你需要加很多的图片增加了应用束的大小。

总结一下,你需要想对你来说*重要的是什么:绘图性能还是app的大笑.通常两个都很重要,所以你会在一个工程里使用这两种方法。

12)处理内存警告

当系统内存低的时候iOS会通知所有的正在运行的app,关于低内存警告的处理苹果官方文档 official Apple documentation描述:

如果你的应用收到这个警告,它必须尽可能多的释放内存。*好的方法是移除对缓存,图像对象,和其他稍后要创建的对象的强引用。

幸运的是,UIKit提供了一些方法去接收低内存警告:

  • 实现App代理中的applicationDidReceiveMemoryWarning:方法。
  • 重载你自定义UIViewController子类中的didReceiveMemoryWarning方法。
  • 注册接收UIApplicationDidReceiveMemoryWarningNotification的通知

一旦收到这些警告,你的处理方法必须立刻响应并释放不必要的内存。

举例,如果视图当前不可见,UIViewController的默认行为是清除这些视图;子类可以通过清除额外的数据结构来补充父类的默认行为。一个应用程序维护一个图片的缓存,没有在屏幕上的图片都会被释放。

一旦收到内存警告,释放可能的全部内存是很重要的,否则你就有让你的app被系统杀死的的风险。

尽管如此,开始扑杀对象释放内存的时候要小心,因为你需要保证它们会在之后重新创建。当你开发app的时候,用你的模拟器上的模拟内存警告功能测试这种情况。

13)重用大开销对象

有的对象的初始化非常慢–NSDateFormatter 和 NSCalendar是两个例子,但是你不能避免使用它们,当你从 JSON/XML响应中解析日期时。

避免使用这些对象时的性能瓶颈,试着尽可能的重用这些对象。你可以加入你的类中成为一个属性,也可以创建为静态变量。

注意如果你选择了第二种方式,这个对象在app运行的时候会一直保持在内存里,像单例一样。

下面这段代码演示了NSDateFomatter作为一个属性的lazy加载,*次被调用然后创建它,之后就使用已创建在的实例

查看源码

打印 ?

01 // in your .h or inside a class extension
02  
03 @property (nonatomic, strong) NSDateFormatter *formatter;
04  
05   
06 // inside the implementation (.m)
07  
08 // When you need, just use self.formatter
09  
10 - (NSDateFormatter *)formatter {
11  
12     if (! _formatter) {
13  
14         _formatter = [[NSDateFormatter alloc] init];
15  
16         _formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"// twitter date format
17  
18     }
19  
20     return _formatter;
21  
22 }

同样要记住设置一个NSDateFormatter的日期格式几乎跟创建一个新的一样慢。因此,如果在你的应用中你频繁需要处理多个日期格式,你的代码应该获利于初始化创建,重用,多个NSDateFormatter对象。

14) 使用精灵表

你是一个游戏开发者吗?精灵表是你的好朋友之一.精灵表让绘制比标准屏幕绘制方法更快速,消耗更少的内存。

这里有两个很棒的精灵表使用的教程

  1. How To Use Animations and Sprite Sheets in Cocos2D
  2. How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats

第二个教程详细覆盖了像素格式,它可以对游戏性能有一个可衡量的影响。

如果对精灵表还不是很熟悉,一个很好的介绍 SpriteSheets – The Movie, Part 1and Part 2. 这些视频的作者是Andreas Löw,一个*流行的创建精灵表的工具Texture Packer的创建者。

除了使用精灵表之外,之前已经说到的内容也可以用在游戏上.举个例子,如果你的游戏有很多精灵,比如在标准的敌人或炮弹射击游戏,你可以重用精灵表额如是每次重新创建它们。

15)避免重复处理数据

很多app调用函数获取远程服务器上的数据.这些数据通常是通过JSON 或者 XML格式来传输。非常重要的是在请求和接收数据的时候努力在两端使用相同的数据结构。

理由?在内存中操纵数据以合适你的数据结构是非常昂贵的。

比如,如果你需要在表格视图中显示数据,*好请求和接收数据是数组的格式,以避免任何中间操纵数据,使其适合你在app中使用的数据结构

相似的,如果你的应用程序依赖于访问特定值的键,那么你可能会想要请求和接收一个键/值对的字典

通过*次就获取正确格式的数据,在自己的应用程序中你就会避免很多的重复处理工作,使数据符合你的选择的结构。

16)选择正确的数据格式

你可以有很多方法从web 服务中传递数据到你的app中

JSON 是一种通常比XML小且解析更快的格式,它的传输的内容也比较小。自iOS5起,内置的JSON解析很好用 built-in JSON deserialization

尽管如此,XML的一个优势当你使用SAXparsing方法时,你可以传输过程中读取它,在面的非常大的数据时,你不必像JSON一样在数据下载完之后才开始读取。

17)适当的设置背景图片

像iOS编码的其他工作一样,至少有两种不同方式去替换你视图的背景图片。

  1. 你可以设置你的视图的背景颜色为UIColor的colorWithPatternImage创建的颜色。
  2. 你可以添加一个UIImageView子试图给View

如果你有全尺寸的背景图片,你*对要用UIImageView,因为UIColor的colorWithPatternImage是重复的创建小的模式图片,在这种情况下用UIImageView方式会节约很多内存。

查看源码

打印 ?

1 // You could also achieve the same result in Interface Builder
2  
3  UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
4  
5 [self.view addSubview:backgroundView];

尽管如此,如果你计划用模式图片背景,你应该是用UIColor的colorWithPatternImage。它更快一些,而且这种情况不会使用很多内存。

查看源码

打印 ?

1 self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];

18)减少你的网络占用

UIWebView 是非常游泳的.它非常容易用来显示web内容,甚至创建你app的视窗。这些都是标准UIKit 空间很难做到的。

尽管如此,你可能注意你可以用在你的app中的UIWebView组件并没有Apple的Safari app快。这是Webkit’s的Nitro引擎的限制使用。JIT compilation.

所以为了获得*佳的性能,你需要调整你的HTML。*件事是尽可能多的避免Javascript,包括避免大的框架比如jQuery。有时使用vanilla Javascript取代依赖的框架会快很多。

随时随地遵循异步加载Javascript文件的实践。特别当它们不直接影响到页面表现的时候,比如分析脚本。

*后,总是要意识到你在用的图片,保持图片的正确尺寸。正如这个教程前面所提到的,利用精灵表的优势来节约内存和提高速度。

想要获取更多的信息,看看WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS.

19) 设置阴影路径

你需要给视图或者layer添加一个阴影,你应该怎么做?

大多数开发者是添加 QuartzCore框架到工程中,然后写如下代码:

查看源码

打印 ?

01 #import <QuartzCore/QuartzCore.h>
02  
03  // Somewhere later ...
04  
05 UIView *view = [[UIView alloc] init];
06  
07  // Setup the shadow ...
08  
09 view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
10  
11 view.layer.shadowRadius = 5.0f;
12  
13 view.layer.shadowOpacity = 0.6;

看起来非常简单,是吧?

不好的是这个方法有一个问题。核心动画必须要先做一幕动画确定视图具体形状之后才渲染阴影,这是非常费事的操作。

这里有个替代方法让系统更好的渲染,设置阴影路径:

查看源码

打印 ?

1 view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

如果你想知道这个内容的更多技巧,Mark Pospesel 写过一篇post about shadowPath.

设置阴影路径,iOS不需要总是计算如何绘制阴影。而是用已经计算好的的路径。坏消息是它依赖与你的视图格式,你是视图可能很难计算这个路径。另一个问题是你需要在每次视图的框架改变时更新阴影路径。

20) 优化你的表格视图

表格视图需要快速的滚动,如果不能,用户能确切注意到很滞后。

为了让你的表格视图流畅的滚动,保证你实现了下列的建议。

  • 通过正确的reuseIdentifier重用cells
  • 尽量多的设置views 为不透明,包括cell本身。
  • 避免渐变,图像缩放,屏幕以外的绘制。
  • 如果行高不总是一样,缓存它们。
  • 如果cell显示的内容来自网络,确保异步和缓存。
  • 使用shadowPath来建立阴影。
  • 减少子视图的数目。
  • cellForRowAtIndexPath:中做尽量少的工作,如果需要做相同的工作,那么只做一次并缓存结果。
  • 使用适当的数据结构存储你要的信息,不同的结构有对于不同的操作有不同的代价。
  • 使用rowHeight,sectionFooterHeight,sectionHeaderHeight为常数,而不是询问代理。

21) 选择正确的数据存储方式

当要存储和读取大数据的时候你的选择是什么?

你有一些选项,包括:

  • 使用 NSUserDefaults存储它们。
  • 存储在结构化文件中,XML,JSON,Plist格式中。
  • 是用NSCoding打包?
  • 存储在本地数据库,如SQLite
  • 使用NSData

NSUserDefaults有什么问题呢?虽然说NSUserDefaults是好而且简单,它确实很好只有当你有很少的数据要存(像你的等级,或者音量是开还是关)。一旦你接触大数据,会有更好的其他选择。

保存在结构化文件中也可能有问题。一般的,在解析之前,你需要加载整个文件到内存中,这是非常耗时的操作。你可以使用SAX去处理XML文件,但是那是一个复杂的作法。同时你加载了全部的对象进内存,其中有你想要的也有不想要的。

那么NSCoding怎么样呢?不幸的是,它也同样要读写文件,跟上面说的方法有同样的问题。

你*好的解决方法是使用SQLite或者 Core Data. 通过这些技术,你可以执行特定的查询只加载需要的对象,避免强力搜索方法来检索数据。性能方面,SQLite和Core Data 非常接近。

SQLite 和 Core Data*大的不同就是它们的使用方法。Core Data呈现为一个对象图模型,但是SQLite是一个传统的DBMS(数据库管理系统).通常Apple建议你用Core Data,但是除非你有特殊的原因不让你你会想避开它,使用更低级的SQLite。

如果在你的app中使用SQLite,一个方便的库 FMDB 允许你使用SQLite而不用专研SQLite的C API。

高级性能技巧

寻找一些精英的方式去成为十足的代码忍者?这些高级性能技巧可以合适的时候使用让你的app运行得尽可能的高效。

22)加速启动时间

App的启动时间非常重要,特别是*次启动的时候。*影响意味着太多了!

*大的事情是保证你的App开始尽量的快,尽量的多的执行异步任务,不如网络请求,数据库访问,或者数据解析。

尽量避免臃肿的XIBs,因为你在主线程中加载。但是在故事板中不会有这个问题,所以尽量用它们。

Note: 监察人不会运行你的app在Xcode调试中, 所以确保测试启动性能时断开与Xcode的连接。

23)使用自动释放池

NSAutoreleasePool负责释放在代码块中的自动释放对象。通常,它是被UIKit自动调用的。但是也有一些场景我们需要手动创建NSAutoreleasePools。

举个例子,如果你创建太多的临时对象在你的代码中,你会注意到你的内存用量会增加直到对象被释放掉。问题是内存只有在UIKit排空(drains)自动释放池的时候才能被释放,这意味着内存被占用的时间超过了需要。

好消息是你可以在你的@autoreleasepool段中创建临时对象来避免上述情况。代码如下所示。

查看源码

打印 ?

01 NSArray *urls = <# An array of file URLs #>;
02 for (NSURL *url in urls) {
03  
04     @autoreleasepool {
05  
06         NSError *error;
07  
08         NSString *fileContents = [NSString stringWithContentsOfURL:url
09  
10                                          encoding:NSUTF8StringEncoding error:&error];
11  
12         /* Process the string, creating and autoreleasing more objects. */
13  
14     }
15  
16 }

在每次迭代之后会自动释放所有的对象。

 

你可以阅读更多关于NSAutoreleasePool的内容Apple’s official documentation.

24)缓存图像

这里有两种方法去加载app束中的Image,*个常见的方式是用imageNamed. 第二个是使用imageWithContentsOfFile

为什么会有两种方法,它们有效率吗?

imageNamed 在载入时有缓存的优势。文档 documentation for imageNamed是这样解释的:

这个方法看起来在系统缓存一个图像对象并指定名字,如果存在则返回对象,如果匹配图像的对象不在缓存中,这个方法会从指定的文件中加载数据,并缓存它,然后返回结果对象。

作为替代,imageWithContendsOfFile 简单的载入图像并不会缓存。

这两个方法的的演示片段如下:

查看源码

打印 ?

1 UIImage *img = [UIImage imageNamed:@"myImage"]; // caching
2  
3 // or
4 UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"]; // no caching

如果你加载只使用一次大图片,那就不需要缓存。这种情况imageWithContendsOfFile会非常好,这种方式不会浪费内存来缓存图片。什么时候使用哪一种呢?

然而,imageNamed 对于要重用的图片来说是更好的选择,这种方法节约了经常的从磁盘加载图片的时间。

25) 尽可能避免日期格式化器

如果你要用NSDateFormatter来解析日期数据,你就得小心对待了。之前提到过,尽量的重用NSDateFormatters总是一个好的想法。

然而,如果你需要更快的速度,你可以使用C代替NSDateFormatter来解析日期。 Sam Soffes写了一篇 blog post about this topic来说明如何用代码来解析 ISO-8601日期串。尽管如此,你可以很容易的修改他的代码例子来适应你的特殊需求。

噢,听起来很棒,但是你相信有更好的办法吗?

如果你能控制你所处理日期的格式,尽可能的选择使用 Unix timestamps。Unix时间戳是简单的整数代表从某个起始时间点开始到现在的秒数。这个起始点通常是1970年1月1日 UTC 00:00:00。

你可以容易的把时间戳转换为NSDate,如下面所示:

查看源码

打印 ?

1 - (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
2  
3   return [NSDate dateWithTimeIntervalSince1970:timestamp];
4  
5 }

这甚至比C函数更快

注意,很多WEB APIs返回时间戳是毫秒,因为这对于javascript*终来使用和处理数据是非常常见的。只要记住将这个时间戳除以1000再传递给dateFromUnixTimestamp方法即可。

iOS定位操作,获取当前位置,计算两点之间距离

摘要:  通过CoreLocation定位,获取到用户当前位置,跟地图中的定位不同,并计算两点之间的距离

 

一、导入CoreLocation.framework

二、#import <CoreLocation/CoreLocation.h>

三、声明代理 <CLLocationManagerDelegate>

四、代码实现

1、声明

01 CLLocationManager *locationManager;//定义Manager
02 // 判断定位操作是否被允许
03 if([CLLocationManager locationServicesEnabled]) {
04     CLLocationManager *locationManager = [[[CLLocationManager alloc] init] autorelease];
05
06      self.locationManager.delegate = self;
07 }else {
08      //提示用户无法进行定位操作
09 }
10
11 // 开始定位
12 [locationManager startUpdatingLocation];

2、更新位置后代理方法,iOS6.0一下的方法

01 - (void)locationManager:(CLLocationManager *)manager
02     didUpdateToLocation:(CLLocation *)newLocation
03            fromLocation:(CLLocation *)oldLocation {
04
05     //latitude和lontitude均为NSString型变量
06         //纬度
07     self.latitude = [NSString  stringWithFormat:@"%.4f", newLocation.coordinate.latitude];
08
09         //经度
10     self.longitude = [NSString stringWithFormat:@"%.4f",                           newLocation.coordinate.longitude];
11     
12 }

3、iOS6.0以上苹果的推荐方法

01 -(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
02 {
03     //此处locations存储了持续更新的位置坐标值,取*后一个值为*新位置,如果不想让其持续更新位置,则在此方法中获取到一个值之后让locationManager stopUpdatingLocation
04     CLLocation *currentLocation = [locations lastObject];
05     
06     CLLocationCoordinate2D coor = currentLocation.coordinate;
07     self.latitude =  coor.latitude;
08     self.longitude = coor.longitude;
09    
10     //[self.locationManager stopUpdatingLocation];
11     
12 }

4、更新失败的方法

1 - (void)locationManager:(CLLocationManager *)manager
2        didFailWithError:(NSError *)error {
3     
4   if (error.code == kCLErrorDenied) {
5       // 提示用户出错原因,可按住Option键点击 KCLErrorDenied的查看更多出错信息,可打印error.code值查找原因所在
6   }
7 }

五、根据两点坐标计算两点之间的距离,此方法为苹果自带方法,亲测速度比高德API速度快很多,但是数据与高德API得到的不一样,准确度本人未能证实

1 //*个坐标
2 CLLocation *current=[[CLLocation alloc] initWithLatitude:32.178722 longitude:119.508619];
3 //第二个坐标
4 CLLocation *before=[[CLLocation alloc] initWithLatitude:32.206340 longitude:119.425600];
5 // 计算距离
6 CLLocationDistance meters=[current distanceFromLocation:before];

iOS图片拉伸的3种常见技巧

纵观移动市场,一款移动app,要想长期在移动市场立足,*起码要包含以下几个要素:实用的功能、*强的用户体验、华丽简洁的外观。华丽外观的背后,少不了美工的辛苦设计,但如果开发人员不懂得怎么合理展示这些设计好的图片,将会糟蹋了这些设计,功亏一篑。

比如下面张图片,本来是设计来做按钮背景的:

%title插图%num尺寸为:24×60

现在我们把它用作为按钮背景,按钮尺寸是150×50:

// 得到view的尺寸
CGSize viewSize = self.view.bounds.size;

// 初始化按钮
UIButton *button = [[UIButton alloc] init];
// 设置尺寸
button.bounds = CGRectMake(0, 0, 150, 50);
// 设置位置
button.center = CGPointMake(viewSize.width * 0.5f, viewSize.height * 0.5f);

// 加载图片
UIImage *image = [UIImage imageNamed:@”button”];
// 设置背景图片
[button setBackgroundImage:image forState:UIControlStateNormal];

// 添加按钮
[self.view addSubview:button];
运行效果图:

%title插图%num

可以看到,效果非常地差。原因很简单,因为原图大小为24×60,现在整张图片被全方位拉伸为150×50,比较严重的是图片的4个角。

有些人可能马上想到一个解决方案,你叫美工把图片做大一点不就好了么,怎么拉伸都没事。没错,这是一种解决方案,不过不建议采取。原因很简单:1.图片大,导致安装包也大,加载到内存中也大;2.有更好的解决方案。

细看一下图片,其实图片会变得难看,完全是因为4个角被拉伸了,中间的拉伸并没有明显地丑化外观。因此要想小图片被拉伸后不会变得难看,在图片拉伸的时候,我们只需拉伸图片的中间一块矩形区域即可,不要拉伸边缘部分。

比如只拉伸下图的矩形区域,上下左右的边缘都不拉伸:

%title插图%num

iOS中提供很好用的API帮我们实现上述功能。到iOS 6.0为止,iOS提供了3种图片拉伸的解决方案,接下来分别详细介绍这些方案。

一、iOS 5.0之前

iOS中有个叫端盖(end cap)的概念,用来指定图片中的哪一部分不用拉伸。比如下图中,黑色代表需要被拉伸的矩形区域,上下左右不需要被拉伸的边缘就称为端盖。

%title插图%num

使用UIImage的这个方法,可以通过设置端盖宽度返回一个经过拉伸处理的UIImage对象

– (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight;
这个方法只有2个参数,leftCapWidth代表左端盖宽度,topCapHeight代表顶端盖高度。系统会自动计算出右端盖宽度(rightCapWidth)和底端盖高度(bottomCapHeight),算法如下:

// width为图片宽度
rightCapWidth = width – leftCapWidth – 1;

// height为图片高度
bottomCapHeight = height – topCapHeight – 1
经过计算,你会发现中间的可拉伸区域只有1×1

// stretchWidth为中间可拉伸区域的宽度
stretchWidth = width – leftCapWidth – rightCapWidth = 1;

// stretchHeight为中间可拉伸区域的高度
stretchHeight = height – topCapHeight – bottomCapHeight = 1;
因此,使用这个方法只会拉伸图片中间1×1的区域,并不会影响到边缘和角落。

下面演示下方法的使用:

// 左端盖宽度
NSInteger leftCapWidth = image.size.width * 0.5f;
// 顶端盖高度
NSInteger topCapHeight = image.size.height * 0.5f;
// 重新赋值
image = [image stretchableImageWithLeftCapWidth:leftCapWidth topCapHeight:topCapHeight];
调用这个方法后,原来的image并不会发生改变,会产生一个新的经过拉伸的UIImage,所以第6行中需要将返回值赋值回给image变量

运行效果:

%title插图%num

可以发现,图片非常美观地显示出来了

注意:

1.这个方法在iOS 5.0出来后就过期了

2.这个方法只能拉伸1×1的区域

 

二、iOS 5.0

在iOS 5.0中,UIImage又有一个新方法可以处理图片的拉伸问题

– (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets
这个方法只接收一个UIEdgeInsets类型的参数,可以通过设置UIEdgeInsets的left、right、top、bottom来分别指定左端盖宽度、右端盖宽度、顶端盖高度、底端盖高度

CGFloat top = 25; // 顶端盖高度
CGFloat bottom = 25 ; // 底端盖高度
CGFloat left = 10; // 左端盖宽度
CGFloat right = 10; // 右端盖宽度
UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
// 伸缩后重新赋值
image = [image resizableImageWithCapInsets:insets];
运行效果:

%title插图%num

三、iOS 6.0

在iOS6.0中,UIImage又提供了一个方法处理图片拉伸

– (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode
对比iOS5.0中的方法,只多了一个UIImageResizingMode参数,用来指定拉伸的模式:

UIImageResizingModeStretch:拉伸模式,通过拉伸UIEdgeInsets指定的矩形区域来填充图片
UIImageResizingModeTile:平铺模式,通过重复显示UIEdgeInsets指定的矩形区域来填充图片
CGFloat top = 25; // 顶端盖高度
CGFloat bottom = 25 ; // 底端盖高度
CGFloat left = 10; // 左端盖宽度
CGFloat right = 10; // 右端盖宽度
UIEdgeInsets insets = UIEdgeInsetsMake(top, left, bottom, right);
// 指定为拉伸模式,伸缩后重新赋值
image = [image resizableImageWithCapInsets:insets resizingMode:UIImageResizingModeStretch];
运行效果:

%title插图%num