iOS |知识点整理(4)

378 阅读22分钟

延续上一篇iOS |知识点整理(3)

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op = [[NSBlockOperation alloc] init];
[op addExecutionBlock:^{
 for (int i = 0; i < 10000; i++) {
 if ([op isCancelled]) break;
 processData(data[i]);
 }
}];
[queue addOperation:op];
Clear the queue with -[NSOperationQueue cancelAllOperations]
• Cancel a single operation with -[NSOperationQueue cancel]
• Those will not cancel running operations
• Check -[NSOperation isCancelled] in long-running operations
• Cancel table cell-related work in tableView:didEndDisplayingCell:

So you've got a few questions here:

  1. To do SSL with ASIHTTPRequest, you just need to pass a https url instead of a http one.
  2. you don't need a real SSL certificate, you can disable validation using: [request setValidatesSecureCertificate:NO];. Disabling certificate validation does mean you lose some of the protection that SSL provides, making you vulnerable to man-in-the-middle attacks etc.
  3. Yes, you're limited by what certificate signing authorities are supported by the iPhone. So long as you stick to the big names it shouldn't really be an issue. (And as per 2. you can use a self signed certificate anyway.)

It seems CCATS not necessary anymore, you need an ERN instead - the process has changed, as of Summer 2010! See tigelane.blogspot.com/2011/01/app…. There are also extra restrictions if you want to distribute on the French app store, see Using SSL in an iPhone App - Export Compliance.

哪些情况下,不再自动合成property

当你自己实现了只读getter或setter方法,或者说,一个readonly的property. 编译器都不会自动合成instance variable

Note: The compiler will automatically synthesize an instance variable in all situations where it’s also synthesizing at least one accessor method. If you implement both a getter and a setter for a readwrite property, or a getter for a readonly property, the compiler will assume that you are taking control over the property implementation and won’t synthesize an instance variable automatically. If you still need an instance variable, you’ll need to request that one be synthesized

Programming with Objective-c - Encapsulating data - under You Can Implement Custom Accessor Methods ref:stackoverflow.com/questions/2…

When should I use @synthesize explicitly?

There's a lot of answers, but also a big confusion. I'll try to put some order (or increase the mess, we'll see...) Let's stop talking about Xcode. Xcode is an IDE. clang is a compiler. This feature we are discussing is called autosynthesis of properties and it's an Objective-C language extension supported by clang, which is the default compiler used by Xcode. Just to make it clear, if you switch to gcc in Xcode, you won't benefit from this feature (regardless from the Xcode version.) In the same way if you use a text editor and compile using clang from the command line, you will.

Thank to autosynthesis you don't need to explicitly synthesize the property as it will be automatically synthesized by the compiler as

@synthesize propertyName = _propertyName

However, a few exceptions exist:

readwrite property with custom getter and setter

when providing both a getter and setter custom implementation, the property won't be automatically synthesized

readonly property with custom getter

when providing a custom getter implementation for a readonly property, this won't be automatically synthesized

@dynamic

when using @dynamic propertyName, the property won't be automatically synthesized (pretty obvious, since @dynamic and @synthesize are mutually exclusive)

properties declared in a @protocol

when conforming to a protocol, any property the protocol defines won't be automatically synthesized

properties declared in a category

this is a case in which the @synthesize directive is not automatically inserted by the compiler, but this properties cannot be manually synthesized either. While categories can declare properties, they cannot be synthesized at all, since categories cannot create ivars. For the sake of completeness, I'll add that's it's still possible to fake the property synthesis using the Objective-C runtime.

overridden properties (new since clang-600.0.51, shipping with Xcode 6, thanks Marc Schlüpmann)

when you override a property of a superclass, you must explicitly synthesize it

It's worth noting that synthesizing a property automatically synthesize the backing ivar, so if the property synthesis is missing, the ivar will be missing too, unless explicitly declared.

Except for the last three cases, the general philosophy is that whenever you manually specify all the information about a property (by implementing all the accessor methods or using @dynamic) the compiler will assume you want full control over the property and it will disable the autosynthesis on it.

A part from the cases I listed above, the only other use of an explicit @synthesize would be to specify a different ivar name. However conventions are important, so my advice is to always use the default naming.

@interface TObject : NSObject
@property(nonatomic,assign,readonly)    NSInteger p1;
@end

@implementation TObject
@synthesize p1=_p12;
-(id)init{
    _p12=3;
    return [super init];
}

-(NSInteger)p1{
    return _p12;
}
@end

@synthesize p1 相当于@synthesize p1=p1 ref:stackoverflow.com/questions/1…

Objective C completely tames the beast, it not only not crashes, but the programmer could be almost assured that the return value might be valid. This seems a bit vague but there are rules which govern the value returned by messages sent to nil objects.

  • If the method returns an object, any pointer type, any integer scalar of size less than or equal to sizeof(void*), a float, a double, a long double, or a long long, then a message sent to nil returns 0.
  • If the method returns a struct, as defined by the Mac OS X ABI Function Call Guide to be returned in registers, then a message sent to nil returns 0.0 for every field in the data structure. Other struct data types will not be filled with zeros.
  • If the method returns anything other than the aforementioned value types the return value of a message sent to nil is undefined. Sending messages to nil references in objective C

为什么苹果不提倡在category中添加property

在category中,我们可以添加我们需要的类方法和实例方法,并且可以在其中使用需要扩展的类中的实例变量,但是我们在category中添加property是不提倡,这似乎已经成为iOS开发中的常识,但是今天我想带着这个问题来进行一下研究,为什么苹果不提倡在category中添加property?

首先我们可以在iOS的文档中,找到一句这样的话来说明原因:

Categories can be used to declare either instance methods or class methods but are not usually suitable for declaring additional properties. It’s valid syntax to include a property declaration in a category interface, but it’s not possible to declare an additional instance variable in a category. This means the compiler won’t synthesize any instance variable, nor will it synthesize any property accessor methods. You can write your own accessor methods in the category implementation, but you won’t be able to keep track of a value for that property unless it’s already stored by the original class. ``` 翻译过来大概的意思是:

category可以被用来申明实例方法或者类方法,但是我们在其中添加额外的property一般是不合适的,虽然你我们在接口中申明一个property是符合语法的,但是这里不可能声明一个额外的实例变量。这就意味着编译器不会合成任何实例变量,也不会合成任何property的访问方法,你可以在代码中实现自己的访问方法,但是你还是不能跟踪到这个property的值除非在原始的类中就有已经保存了。

为什么不能在category中添加变量呢?

参考这个答案中的一个评论,我认为的原因是这样的

category是在runtime时被添加到class中的,这里可以阅读sunnyxx的(描述方法顺序有误)objc category的秘密,也就是说这个时候class已经被注册成功,storage layout也已经确定,这时候category中再添加实例变量,对原来的storage layout并没有用。就像是在class_addIvar只能添加在objc_allocateClassPair之后,和objc_registerClassPair之前,而在objc_registerClassPair添加的变量,是不能保留的。

ref:Category中property的命运

retain cycle的比较

#import "ViewController1.h"
@interface ViewController1 ()
@property(nonatomic,strong) NSString* tt;
@property(nonatomic,strong) void(^blk)();
@property(nonatomic,strong) void(^blk1)();
@end
@implementation ViewController1
- (void)viewDidLoad {
    [super viewDidLoad];
    self.tt=@"";
    NSString* s=self.tt;
    self.blk=^{
        NSLog(@"%@",self.tt);  //会造成retain cycle
    };
    self.blk1=^{
        NSLog(@"%@“,s);  //不会造成retain cycle
    };
}
@end
    self.navigationController.navigationBar.translucent=YES;
    //设置透明的背景图,便于识别底部线条有没有被隐藏
    UIColor* c=[[UIColor lightGrayColor] colorWithAlphaComponent:0.1];
    [navBar setBackgroundImage:[UIImage imageFromContextWithColor:c]
                       forBarPosition:UIBarPositionAny
                           barMetrics:UIBarMetricsDefault];
    //此处使底部线条失效
    [navBar setShadowImage:[UIImage new]];
    self.edgesForExtendedLayout = UIRectEdgeAll;
    //设置导航栏左右按钮的字体及颜色
    self.navigationItem.leftBarButtonItem.tintColor=[UIColor redColor];
    //设置导航栏title的颜色及字体大小
    [self.navigationController.navigationBar setTitleTextAttributes:@{ NSFontAttributeName: [UIFont boldSystemFontOfSize:17], NSForegroundColorAttributeName: [UIColor whiteColor]}];
    
dequeueReusableCellWithIdentifier:返回的cell可能为nil.
dequeueReusableCellWithIdentifier:forIndexPath: 一定不为nil

下面三种情况可以调用dequeueReusableCellWithIdentifier:forIndexPath:

1、如果是用NIB自定义了一个Cell,那么就调用registerNib:forCellReuseIdentifier:
2、如果是用代码自定义了一个Cell,那么就调用registerClass:forCellReuseIdentifier:
3. If you create your table view and your cell prototypes in a storyboard, the storyboard loader takes care of registering the cell prototypes that you defined in the storyboard.

以上这三个方法可以在创建UITableView的时候进行调用。 这样在tableView:cellForRowAtIndexPath:方法中就可以省掉下面这些代码:

static NSString *CellIdentifier = @"Cell";  
if (cell == nil)   {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];  
}

取而代之的是下面这句代码:

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; 

You are creating an opaque image context. Replace this line:

UIGraphicsBeginImageContext(rect.size); 
with:
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);

iOS UIButton取消tintColor效果【原创】

在StoryBoard和xib搭界面的时候,UIButton的图片总是会受到tintColor的影响,自动改变图片的颜色。多方搜索,终于找到原因了,选中button,在button的type里把System改为Custom就可以了。

单独设置tintColor会被全局设置所覆盖

 //设置导航栏左右按钮的字体及颜色
    [[UIBarButtonItem appearance] setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor whiteColor],NSFontAttributeName:[UIFont boldSystemFontOfSize:16]} forState:UIControlStateNormal];

 UIBarButtonItem *doneButton=[[UIBarButtonItem alloc]initWithTitle:@"确定" style:UIBarButtonItemStyleBordered target:self action:@selector(keyboardAccDoneAction)];
    doneButton.imageInsets = UIEdgeInsetsMake(200, 6, 50, 30);
    doneButton.tintColor = [UIColor blackColor];//上面的全局设置会想覆盖这个设置
//用下面的方法设置titleTextAttributes才行.
    [doneButton setTitleTextAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor],NSFontAttributeName:[UIFont boldSystemFontOfSize:16]}  forState:UIControlStateNormal];

Nib中textview有默认文字造成的卡顿

080F1A31-2042-431E-9653-B182A64EE274.png

程序运行,第一次push 上面的vc时,很卡 uitextview在程序运行后,赋值,大小变化之后,只在原来的没变时的区域内响应键盘事件. 解决方法是重新alloc 新的textview

Push VC时感觉卡顿的原因

导致卡顿的的罪魁祸首就是UIViewController的View的默认color为空,背景色是透明的。这其实不是卡顿,而是由于透明颜色重叠后视觉上的问题,设置一个背景色就可以了

字符串的排序

NSString *str = @"stack";
NSMutableArray *charArray = [NSMutableArray arrayWithCapacity:str.length];
for (int i=0; i<str.length; ++i) {
   NSString *charStr = [str substringWithRange:NSMakeRange(i, 1)];
   [charArray addObject:charStr];
}
[charArray sortUsingComparator:^(NSString *a, NSString *b){
   return [a compare:b];
}];

让某个字符上移

I was able to achieve your desired result using a single label. Using a little math you can offset the baseline of the smaller text to achieve your desired result.

- (NSMutableAttributedString *)styleSalePriceLabel:(NSString *)salePrice withFont:(UIFont *)font{
    if ([salePrice rangeOfString:@"."].location == NSNotFound) {
        return [[NSMutableAttributedString alloc] initWithString:salePrice];
    } else {
        NSRange range = [salePrice rangeOfString:@"."];
        range.length = (salePrice.length - range.location);
        NSMutableAttributedString *stylizedPriceLabel = [[NSMutableAttributedString alloc] initWithString:salePrice];
        UIFont *smallFont = [UIFont fontWithName:font.fontName size:(font.pointSize / 2)];
        NSNumber *offsetAmount = @(font.capHeight - smallFont.capHeight);
        [stylizedPriceLabel addAttribute:NSFontAttributeName value:smallFont range:range];
        [stylizedPriceLabel addAttribute:NSBaselineOffsetAttributeName value:offsetAmount range:range];
        return stylizedPriceLabel;
    }
}

This yields the following:

BBB970D4-0691-46DB-9D89-3BB2EA054FFD.png

字符中插入图片

You'll need to use an attributed string and add the image as instance of

NSTextAttachment:

NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:@"like after"];
NSTextAttachment *textAttachment = [[NSTextAttachment alloc] init];
textAttachment.image = [UIImage imageNamed:@"whatever.png"];
NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment];
[attributedString replaceCharactersInRange:NSMakeRange(4, 1) withAttributedString:attrStringWithImage];

让NSUserDefaults存储non-property-list对象

问题: Attempt to set a non-property-list object{...}as an NSUserDefaults value for key shop.

解决办法:

if you don't want to bother about ensuring all your dictionary values are the property list types, then you can simply convert the dictionary into NSData,

NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
[def setObject:[NSKeyedArchiver archivedDataWithRootObject:self.myDictionary] forKey:@"MyData"];
[def synchronize];

And to get back into a dictionary from sandbox:

NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
NSData *data = [def objectForKey:@"MyData"];
NSDictionary *retrievedDictionary = [NSKeyedUnarchiver unarchiveObjectWithData:data];
self.myDictionary = [[NSDictionary alloc] initWithDictionary:retrievedDictionary];

关于brew cask

简介:
brew cask 是一个用命令行管理 Mac 下应用的工具,它是基于 homebrew 的一个增强工具。
homebrew可以管理 Mac 下的命令行工具,例如imagemagick, nodejs,如下所示:
brew install imagemagick
brew install node
而使用上brew cask之后,你还可以用它来管理 Mac 下的 Gui 程序,例如qq, chrome, evernote等,如下所示:
brew cask install qq
brew cask install google-chrome
brew cask install evernote

关于NSDataDetector

1.链接的匹配

NSError *error = NULL;
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypePhoneNumber error:&error];

NSRange inputRange = NSMakeRange(0, [phone length]);
NSArray *matches = [detector matchesInString:phone options:0 range:inputRange];

// no match at all
if ([matches count] == 0) {   return NO;  }

// found match but we need to check if it matched the whole string
NSTextCheckingResult *result = (NSTextCheckingResult *)[matches objectAtIndex:0];

if ([result resultType] == NSTextCheckingTypePhoneNumber && result.range.location == inputRange.location && result.range.length == inputRange.length) {
   // it matched the whole string
   return YES;
}
else {
   // it only matched partial string
   return NO;
}

关于NSTextCheckingResult

1.获取所有匹配

```
NSString *searchedString = @"domain-name.tld.tld2";
NSRange   searchedRange = NSMakeRange(0, [searchedString length]);
NSString *pattern = @"(?:www\\.)?((?!-)[a-zA-Z0-9-]{2,63}(?<!-))\\.?((?:[a-zA-Z0-9]{2,})?(?:\\.[a-zA-Z0-9]{2,})?)";
NSError  *error = nil;

NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern: pattern options:0 error:&error];
NSArray* matches = [regex matchesInString:searchedString options:0 range: searchedRange];
for (NSTextCheckingResult* match in matches) {
    NSString* matchText = [searchedString substringWithRange:[match range]];
    NSLog(@"Regx:%@",pattern);
    NSLog(@"match: %@", matchText);
    //得到上面正则表达式中的分组
    for(NSInteger i=0;i<match.numberOfRanges;i++){
        NSRange group=[match rangeAtIndex:i];
        NSLog(@"group%ld:%@",(long)i, [searchedString substringWithRange:group]);
    }
}

2.获取第一次匹配

NSString *searchedString = @"domain-name.tld.tld2";
NSRange   searchedRange = NSMakeRange(0, [searchedString length]);
NSString *pattern = @"(?:www\\.)?((?!-)[a-zA-Z0-9-]{2,63}(?<!-))\\.?((?:[a-zA-Z0-9]{2,})?(?:\\.[a-zA-Z0-9]{2,})?)";
NSError  *error = nil;
NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:&error];
NSTextCheckingResult *match = [regex firstMatchInString:searchedString options:0 range: searchedRange];
NSLog(@"group1: %@", [searchedString substringWithRange:[match rangeAtIndex:1]]);
NSLog(@"group2: %@", [searchedString substringWithRange:[match rangeAtIndex:2]]);

关于IOS7中的transform异常的问题

由于使用约束的缘故,IOS7中进行如下变换时:

self.view.transform=CGAffineTransformMakeTranslation(0, dY);

实际view的移动距离要比dY小,可以使用下面的办法修复,但是对于某些特殊的约束,下面的方法无效,唯一可靠 的办法,就是对view中的layer进行变换.

if you build your project with iOS 8 SDK, then on iOS 7 you'll have to manually convert points to pixels on retina devices;

Consider writing something like this:

CGFloat dY = MIN(scrollView.contentOffset.y, 0);
if ([UIDevice currentDevice].systemVersion.floatValue < 8) {
   dY *= [UIScreen mainScreen].scale;
}
headerFixed.transform = CGAffineTransformMakeTranslation(0, dY); 

There's a chance though that turning auto layout off will be enough to fix your problem.

关于tintColor被覆盖的问题

[[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor colorWithRed:0.71 green:0.71 blue:0.71 alpha:1], NSForegroundColorAttributeName, [UIFont boldSystemFontOfSize:12.0f],NSFontAttributeName,nil] forState:UIControlStateNormal];
[[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor colorWithRGBHex:0x2661a5], NSForegroundColorAttributeName, [UIFont boldSystemFontOfSize:12.0f],NSFontAttributeName,nil] forState:UIControlStateSelected];
//tabbar上文字的颜色由上面的代码决定,而下面的代码只会决定tabbar上图标的颜色.
[UITabBar appearance].tintColor=[UIColor colorWithRGBHex:0x2661a5];

关于键盘消失时相关view的动画

在键盘消失时,相关view的动画,要使用下面的代码,否则看不到动画效果

-(void)keyboardWillHide:(NSNotification*)note{
    [_kbAdjustTargetView.layer removeAllAnimations];
        NSTimeInterval duration = [[note.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
    CABasicAnimation* s=[CABasicAnimation animationWithKeyPath:@"transform"];
    s.toValue=[NSValue valueWithCATransform3D:CATransform3DIdentity];
    s.duration=duration;
    s.removedOnCompletion=NO;
    s.fillMode=kCAFillModeForwards;
    s.delegate=self;
    s.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
    //_kbAdjustTargetView.layer.transform=CATransform3DIdentity;   //设置这里,会看不到动画效果,必须在
动画完成的时候,设置才行.
[_kbAdjustTargetView.layer addAnimation:s forKey:nil];

}

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    _kbAdjustTargetView.layer.transform=CATransform3DIdentity;
}

键盘的除非是第三方IM,否则使用IQKeyboardManager第三方库管理就行。

关于RunLoop

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。

上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为"Common"属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。 有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 "commonModeItems" 中。"commonModeItems" 被 RunLoop 自动更新到所有具有"Common"属性的 Mode 里去。

在 Mach 中,所有的东西都是通过自己的对象实现的,进程、线程和虚拟内存都被称为"对象"。和其他架构不同, Mach 的对象间不能直接调用,只能通过消息传递的方式实现对象间的通信。"消息"是 Mach 中最基础的概念,消息在两个端口 (port) 之间传递,这就是 Mach 的 IPC (进程间通信) 的核心。

关于正则表达式中组匹配

1.注意颜色中标记的部分,内部的括号捕获虽然被忽略了,但是不影响其外面括号的正常捕获

 NSString *searchedString = @"domain-name.tld.tld2";
 NSRange   searchedRange = NSMakeRange(0, [searchedString length]);
 NSString *pattern = @"(?:www\\.)?((?!-)[a-zA-Z0-9-]{2,63}(?<!-))\\.?((?:[a-zA-Z0-9]{2,})?(?:\\.[a-zA-Z0-9]{2,})?)";
 NSError  *error = nil;

 NSRegularExpression* regex = [NSRegularExpression regularExpressionWithPattern: pattern options:0 error:&error];
 NSArray* matches = [regex matchesInString:searchedString options:0 range: searchedRange];
 for (NSTextCheckingResult* match in matches) {
     NSString* matchText = [searchedString substringWithRange:[match range]];
     NSLog(@"Regx:%@",pattern);
     NSLog(@"match: %@", matchText);
     for(NSInteger i=0;i<match.numberOfRanges;i++){
         NSRange group=[match rangeAtIndex:i];
         NSLog(@"group%ld:%@",(long)i, [searchedString substringWithRange:group]);
     }
 }

2016-01-26 13:12:21.737 StorageAndLayout_i7_Demo[53865:4673564] Regx:(?:www\.)?((?!-)[a-zA-Z0-9-]{2,63}(?<!-))\.?((?:[a-zA-Z0-9]{2,})?(?:\.[a-zA-Z0-9]{2,})?)
2016-01-26 13:12:21.737 StorageAndLayout_i7_Demo[53865:4673564] match: domain-name.tld.tld2
2016-01-26 13:12:21.737 StorageAndLayout_i7_Demo[53865:4673564] group0:domain-name.tld.tld2
2016-01-26 13:12:21.738 StorageAndLayout_i7_Demo[53865:4673564] group1:domain-name
2016-01-26 13:12:21.738 StorageAndLayout_i7_Demo[53865:4673564] group2:tld.tld2

**将AFNetworking请求异步变同步 **

    NSDictionary* param=nil;
    AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc]initWithBaseURL:[NSURL URLWithString:kdomainURL]];
    @weakify(self);
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json",@"text/plain",  @"text/json", @"text/html",nil];
    __block BOOL done=NO;
    [manager GET:@"url" parameters:param success:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSLog(@"aaa");
        done=YES;
    }failure:^(AFHTTPRequestOperation *operation, NSError *error){
        done=YES;
    }];
       do {
        [NSRunLoop.mainRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
    } while (!done);

当然也可以使用GCD来完成:

__block id result = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSURLRequest *req = [self requestWithMethod:@"GET"
                                       path:@"someURL"
                                 parameters:nil];
AFHTTPRequestOperation *reqOp = [self HTTPRequestOperationWithRequest:req
                                                              success:^(AFHTTPRequestOperation *operation, id responseObject) {
                                                                  result = responseObject;                                                                         
                                                                  dispatch_semaphore_signal(semaphore);
                                                              }
                                                              failure:^(AFHTTPRequestOperation *operation, NSError *error) {
                                                                  dispatch_semaphore_signal(semaphore);
                                                              }];
reqOp.failureCallbackQueue = queue;
reqOp.successCallbackQueue = queue;
[self enqueueHTTPRequestOperation:reqOp];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(semaphore);
return result;

ref:github.com/AFNetworkin…

**boundingRectWithSize 参数详解 **

NSStringDrawingTruncatesLastVisibleLine如果文本内容超出指定的矩形限制,文本将被截去并在最后一个字符后加上省略号。 如果指定了NSStringDrawingUsesLineFragmentOrigin选项,则该选项被忽略

NSStringDrawingUsesFontLeading 计算行高时使用行间距。(译者注:字体大小+行间距=行高)

NSStringDrawingUsesDeviceMetrics 计算布局时使用图元字形(而不是印刷字体)。

注意下面的代码,由于没有指定NSStringDrawingUsesLineFragmentOrigin只输出了单行的rect


  CGRect r4=[str boundingRectWithSize:CGSizeMake(100, FLT_MAX) options:NSStringDrawingUsesFontLeading attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:15.0f]} context:nil];
  NSLog(@"%@",NSStringFromCGRect(r4));

If NSStringDrawingUsesLineFragmentOrigin is specified in options, it wraps the string text as needed to make it fit. If the string is too big to fit completely inside the rectangle, the method scales the font or adjusts the letter spacing to make the string fit within the given bounds.

If NSStringDrawingUsesLineFragmentOrigin is not specified in options, the origin of the rectangle is the baseline of the only line. The text will be displayed above the rectangle and not inside of it. For example, if you specify a rectangle starting at 0,0 and draw the string ‘juxtaposed’, only the descenders of the ‘j’ and ‘p’ will be seen. The rest of the text will be on the top edge of the rectangle. This method draws the line using the attributes specified in the attributed string itself. If newline characters are present in the string, those characters are honored and cause subsequent text to be placed on the next line underneath the starting point.

Special Considerations This method uses the baseline origin by default, so it renders the string as a single line. To render the string in multiple lines, specify NSStringDrawingUsesLineFragmentOrigin in options.

Interface Builder中的强引用和弱引用

  1. Interface Builder的控件在Controller中是被弱引用的,但是由于它所在的顶级view是被Controller引用的. 但是Controller中IBOutletsCollection是被强引用的.
  2. Interface Builder中添加的对象,在运行时,是被强引用的.

使用NSPointerArray来直接存放指针

NSPointerArray适合需要直接来存放弱引用的场合

  • (NSPointerArray *)strongObjectsPointerArray NS_AVAILABLE(10_8, 6_0);
  • (NSPointerArray *)weakObjectsPointerArray NS_AVAILABLE(10_8, 6_0);
@interface KIZMultipleProxyBehavior ()
@property (nonatomic, strong) NSPointerArray *weakRefTargets;
@end

@implementation KIZMultipleProxyBehavior
- (void)setDelegateTargets:(NSArray *)delegateTargets{
    self.weakRefTargets = [NSPointerArray weakObjectsPointerArray];
    for (id delegate in delegateTargets) {
        [self.weakRefTargets addPointer:(__bridge void *)delegate];
    }
}
}
@end

outlets 和 referencing outlets的区别

84CE2A13-7B3C-4AB1-82F5-863DAE29E631.png

关于UINavigationBar

1.设置背景图片

UINavigationBar *navigationBar = self.navigationController.navigationBar;
    //设置透明的背景图,便于识别底部线条有没有被隐藏
    [navigationBar setBackgroundImage:[[UIImage alloc] init]
                       forBarPosition:UIBarPositionAny
                           barMetrics:UIBarMetricsDefault];
    //此处使底部线条失效
    [navigationBar setShadowImage:[UIImage new]];

2.获取背景图片

[navigationBar backgroundImageForBarMetrics:UIBarMetricsDefault];
  1. //此处使底部线条颜色为红色
    [navigationBar setShadowImage:[UIImage imageWithColor:[UIColor redColor]]];

4.在导航栏上添加分段控件

UISegmentedControl *segControl = [[UISegmentedControl alloc] initWithItems:@[@"消息",@"电话"]];
    segControl.tintColor = [UIColor colorWithRed:0.07 green:0.72 blue:0.96 alpha:1];
    [segControl setSelectedSegmentIndex:0];
    self.navigationItem.titleView = segControl;

5.//全局设置导航栏主题

- (void)setNavigationControllerAppearance {
    [UINavigationBar appearance].barStyle  = UIBarStyleBlack;
    [[UINavigationBar appearance] setBarTintColor:[UIColor colorWithWhite:0.1 alpha:0.5]];
    [[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
}

6.//设置NavigationBar title color

navBar.titleTextAttributes=[NSForegroundColorAttributeName:[UIColor whiteColor]]

关使用扩展而不使用继承

这个点赞的按钮功能上有几个职责:

  1. 显示已有的点赞数
  2. 点击按钮后执行一个小动画,点赞数 +1,同时发送网络请求。
  3. 若已经点赞,点击执行反向操作
  4. 若网络请求发送失败,则回退成点击前的状态

AEE834D4-B96B-47B7-80FB-54706FEB14E1.png 对于上的需要,继承不是一个好的办法,使用扩展:

@implementation FDLikeButton (FDLikeButtonSelfManager)
- (void)selfManagedConfigureWithLikeStatus:(BOOL)likeOrNot count:(NSInteger)count {
    [self configureLikeStatus:likeOrNot count:count animated:NO];
    [self addTarget:self action:@selector(likeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)likeButtonTapped:(id)sender {
    // +1 or -1 with animation
    // Network request ^(NSError *error) {
    //     if (error) {
    //         rollback
    //     }
    // }
}
@end

ref:blog.sunnyxx.com/2015/12/19/…

关于Nib中的fileOwner

  1. Nib的fileOwner可以为任何对象.
  2. Nib中的控件除了可以和fileOwner进行连线之外,也可以和Nib Top View自身的类进行连线.
  3. FileOwner一个作用就是当NIB对其进行连线后,可以通过它来引用其中的view
  4. [[NSBundle mainBundle] loadNibNamed: NSStringFromClass([self class]) owner: nil options: nil]; 返回是一个数组,即NIB中会有多个对等的view.

屏蔽init及new的调用

- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;

[NSException raise:NSInternalInconsistencyException 
format:@"You must override %@ in a subclass",NSStringFromSelector(_cmd)];

What is the difference between a Placeholder constraint and an Uninstalled constraint

1.对于placeholder用法场景,有时我们需要相应的约束在运行时用代码添加,而为了让相应的View在 interfaceBuilder中不出现错误,可以将其设置为placeHolder. 注意placeHolder约束在工程build的时候,就被 remove掉了.

2.[view removeConstraints:view.constraints] 只会删除先前添加到该view上的约束. 而对于view来说,它的 有些约束,是添加其它view上的,如view的父视图

A few things to cover here:

  1. The NSIBPrototypingLayoutConstraint constraints that you're running into (and that are causing exceptions) are auto-generated by Interface Builder in order to make your Storyboard or XIB view layout non-ambiguous. It's pretty sneaky about doing this, but it's automatically adding the minimum constraints required so that the position and size of each ambiguous view becomes fully specified. This is a change from Xcode 4, because in Xcode 4 you could not have ambiguous layouts in Interface Builder. With Xcode 5 and later you can, however IB will auto-generate these constraints for you if your layout is ambiguous at compile time.

The way to fix this issue is to add the minimum required constraints in Interface Builder so that each view's position & size is fully specified, then select each of these unwanted constraints, go to the right sidebar Attributes inspector, and check the box next to Placeholder - Remove at build time.

0CB32D93-831E-4BB9-8FA2-EF3FABCC52CB.png

这个选项,可以阻止IB为你自动添加约束,那么可以用它,在全代码管理约束的场合,防止IB为你自动生成约束.

Not only does this checkbox remove the constraint you added, but most importantly it will prevent the auto-generated IB constraint from taking its place! (As you can imagine, this is quite tedious when you have a number of views in IB and want to manage all your constraints in code. For this reason you may want to avoid using IB entirely for view hierarchies in which you intend to implement Auto Layout programmatically.)

What is the difference between a Placeholder constraint and an Uninstalled constraint? Here's a slide from my Adaptive Auto Layout talk (video) (PDF slides) comparing the two:

  1. In updateConstraints, you don't want to remove constraints and re-add them like you have there. Why not? Essentially, it's terrible for performance, and I have confirmed with Apple engineers that this is not a good idea. See the question/answer I have posted here for some more details, as well as this answer. In order to prevent constraints being added more than once, use a boolean flag (e.g. hasSetupConstraints) that you set to YES once you have set up your constraints the first time, and if updateConstraints is called again you can just return immediately if you have no new constraints to add. See this question for further discussion.

The code you're using to remove constraints may not work completely. This is because [view removeConstraints:view.constraints] will only remove constraints that have been added to view -- remember that constraints can be added to any common superview of the views they constrain -- and the constraints added to view may not be the only ones affecting the layout ofview! If you need to remove a number of constraints, you should store a reference to each of those constraints in a property (e.g. an NSArray property containing NSLayoutConstraint instances), and then deactivate/remove those constraints using the API on NSLayoutConstraint or the PureLayout open-source library. You should only deactivate/remove as few constraints as possible because it is computationally expensive to do so. On the other hand, changing the constant of any constraint is very efficient and encouraged, and you don't need to remove or re-add the constraint to do that.

关于约束的一个问题

下图中button的宽度约束是距左右两边各137, 那么如果下面的这个view,作为另外一个视图的子视图,当它的 当添加约束,使得这个view小于137*2的时候,会发生错误.

1E33FCFA-C9ED-4DD9-8F34-51FF9D9D6AC7.png

约束的类型

对于Autolayout而言,最为核心的基础就是约束。那么在iOS中约束到底有几种?它们有那些作用呢?下面就谈谈。

  • NSLayoutConstraint,开放类,几乎是程序员最常用的约束。它用于设置view在view tree之间的关系,自身大小等。
  • NSContentSizeLayoutConstraint,私有类。用于衡量view内容和大小相关的约束。比如Hugging和Compression,控制view的内容显示。
  • NSAutoresizingMaskLayoutConstraint,私有 类。由Autosizing mask转换到Autolayout系统中的约束表达。
  • _UILayoutSupportConstraint,私有类。布局支撑约束,它包括Top和Bottom的约束,用于控制view的显示边界,例如,它限制view的顶端显示不会和状态栏重合。
  • NSIBPrototypingLayoutConstraint,私有类。如果你丫在Storyboard中添加了一个UI控件,且没有在Storyboard中添加任何约束,但是标注了你要使用Autolayout,那么在实际的运行期,系统会默认为它添加NSIBPrototypingLayoutConstraint约束。

值得一提的是:NSContentSizeLayoutConstraint最主要的作用是和intrinsic size一起工作,通常这个约束和Layout约束共同决定view的显示方式。


使用GCD中的semaphore来代替OSSpinLock

2015-12-14 那天,swift-dev 邮件列表里有人在讨论 weak 属性的线程安全问题,其中有几位苹果工程师透露了自旋锁的 bug,对话内容大致如下:

新版 iOS 中,系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比 它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。

具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。这并不只是理论上的问题,libobjc 已经遇到了很多次这个问题了,于是苹果的工程师停用了 OSSpinLock。

苹果工程师 Greg Parker 提到,对于这个问题,一种解决方案是用 truly unbounded backoff 算法,这能避免 livelock 问题,但如果系统负载高时,它仍有可能将高优先级的线程阻塞数十秒之久;另一种方案是使用 handoff lock 算法,这也是 libobjc 目前正在使用的。锁的持有者会把线程 ID 保存到锁内部,锁的等待者会临时贡献出它的优先级来避免优先级反转的问题。理论上这种模式会在比较复杂的多锁条件下产生问题,但实践上目前还一切都好。 libobjc 里用的是 Mach 内核的 thread_switch() 然后传递了一个 mach thread port 来避免优先级反转,另外它还用了一个私有的参数选项,所以开发者无法自己实现这个锁。另一方面,由于二进制兼容问题,OSSpinLock 也不能有改动。

最终的结论就是,除非开发者能保证访问锁的线程全部都处于同一优先级,否则 iOS 系统中所有类型的自旋锁都不能再使用了.

使用dispatch_semaphore来代替OSSpinLock.

ref:blog.ibireme.com/2016/01/16/…


GCD做的事件通知

    dispatch_queue_t queue=dispatch_queue_create("apdfsfdlication11", NULL);
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, queue);
    dispatch_source_set_event_handler(source, ^{
        //这里是必须的,要不然相应的block,不会执行
       //同时还要注意,只有当source所在队列有机会执行的时候,这个block才执行.  参考一下它在主队列中的运用:Updating the UI Using Dispatch_Async in Swift
        long value = dispatch_source_get_data(source);
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"run in main");
        });
    });

    dispatch_resume(source);

    dispatch_async(queue, ^{
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
        dispatch_after(time, queue, ^{
            dispatch_source_merge_data(source, 1); //通知队列
        });
    });

参考:GCD-Part III: Dispatch Sources


GCD中的semaphore

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 创建 Dispatch Semaphore,计数初始值为 1,保证可访问 NSMutableArray 对象的线程同时只能有 1 个。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 100000; ++i) { 
    dispatch_async(queue, ^{
        // 一直等待,直到 Dispatch Semaphore 的计数值达到大于等于 1。
        // 当 Dispatch Semaphore 的值达到大于等于 1 时dispatch_semaphore_wait 函数将 dispatch_semaphore_wait 的计数值减去 1,函数返回。
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        // 这里 Dispatch Semaphore 的计数值恒为 0。
        // 此时访问 NSMutableArray 的线程只有 1 个,可以安全操作。
        [array addObject:[NSNumber numberWithInt:i]];
        // 排他控制结束,调用 dispatch_semaphore_signal 将 Dispatch Semaphore 计数值加 1。
        // 这时如果有在 dispatch_semaphore_wait 上等待的线程,就能让最先等待的线程执行。
        dispatch_semaphore_signal(semaphore);
    }); 
}

用GCD来实现Timer

dispatch_source_t addDispatchTimer(CGFloat start,CGFloat interval, dispatch_queue_t queue, dispatch_block_t block)
{
    dispatch_source_t newTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue);
    if ( newTimer ) {
        uint64_t nsec = (uint64_t)(interval * NSEC_PER_SEC);
       //如果start为零的话,第一次的block会立马执行.  否则block的第一次执行会向后推.
        dispatch_source_set_timer(newTimer,dispatch_time(DISPATCH_TIME_NOW, start*NSEC_PER_SEC),nsec, 0);
        dispatch_source_set_event_handler(newTimer, block);
        dispatch_resume(newTimer);
    }
    return newTimer;
}

-(void)invalidate
{
    dispatch_source_t theTimer = _timer;
    if ( theTimer ) {
        dispatch_source_cancel(theTimer);
        self.timer = ((void*)0);
    }
}

求两个数组交集

NSMutableSet *set1 = [NSMutableSet setWithArray: array1];
NSSet *set2 = [NSSet setWithArray: array2];
[set1 intersectSet: set2];
NSArray *resultArray = [set1 allObjects];

在IOS8中直接跳转到用户设置

-(void)openSetting{
    NSURL* url=[NSURL URLWithString:UIApplicationOpenSettingsURLString];
    [[UIApplication sharedApplication] openURL:url];
}

关于Toll-Free Bridging

Toll-free bridging,简称为TFB,是一种允许某些ObjC类与其对应的CoreFoundation类(严格来说应该是结构体)之间可以互换使用的机制。比如 NSString与CFString是桥接(bridged)的, 这意味着可以将任意NSString当做CFString使用,也可以将任意的CFString当做NSString使用。

简单来说,就是Foundation中的类对象和CoreFoundation中相应结构体中的相互转换

UITextView中光标位置的计算

- (void)textViewDidBeginEditing:(UITextView *)textView {
    if ([textView.text isEqualToString:@"点击这里输入文字..."]) {
        textView.text = @"";
        textView.textColor = [UIColor blackColor];
    }
    [self performSelector:@selector(textViewDidChange1:) withObject:textView afterDelay:0.1f];
}


- (void)textViewDidChange1:(UITextView *)textView {
    CGFloat cursorPosition;
    if (textView.selectedTextRange) {
        cursorPosition = [textView caretRectForPosition:textView.selectedTextRange.start].origin.y;
    } else {
        cursorPosition = 0;
    }
    NSLog(@"cursor position:%lf",cursorPosition);
}

另外的一个例子:

UITextView *textStuff = [[UITextView alloc] init];
textStuff.frame = CGRectMake(2.0, 200.0, 200.0, 40.0);
textStuff.text = @"how are you today?";
textStuff.textColor = [UIColor blackColor];
UITextPosition *Pos2 = [textStuff positionFromPosition: textStuff.endOfDocument offset: nil];
UITextPosition *Pos1 = [textStuff positionFromPosition: textStuff.endOfDocument offset: -3];
UITextRange *range = [textStuff textRangeFromPosition:Pos1 toPosition:Pos2];
CGRect result1 = [textStuff firstRectForRange:(UITextRange *)range ];
NSLog(@"%f, %f", result1.origin.x, result1.origin.y);
UIView *view1 = [[UIView alloc] initWithFrame:result1];
view1.backgroundColor = [UIColor colorWithRed:0.2f green:0.5f blue:0.2f alpha:0.4f];
[textStuff addSubview:view1];

5537C07D-9045-4FA8-ABCA-B670D8EF19AA.png

ref:stackoverflow.com/questions/1…

UITextField的rightView的用法

@interface ViewController ()
{
    UIButton* overlayButton;
}
@property (weak, nonatomic) IBOutlet UITextField *txtField;
@end
@implementation ViewController
- (void)textFieldDidBeginEditing:(UITextField *)textField {
        textField.rightView = self.overlayButton;
        textField.rightViewMode = UITextFieldViewModeWhileEditing;
}
- (UIButton *)overlayButton {
    if (!overlayButton) {
        overlayButton = [UIButton buttonWithType:UIButtonTypeCustom] ;
        overlayButton.frame=CGRectMake(0, 0, 30, 30);
        [overlayButton setBackgroundImage:[UIImage imageNamed:@"bookmark"] forState:UIControlStateNormal];
        [overlayButton addTarget:self action:@selector(bookmarkTapped:)
                    forControlEvents:UIControlEventTouchUpInside];
    }
    return overlayButton;
}
}

screenshot.png

import区别

import指令能保证相同的头文件只包含一次. 而include得使用guardmarco

The #import directive was added to Objective-C as an improved version of #include. Whether or not it's improved, however, is still a matter of debate. #import ensures that a file is only ever included once so that you never have a problem with recursive includes. However, most decent header files protect themselves against this anyway, so it's not really that much of a benefit. Basically, it's up to you to decide which you want to use. I tend to #import headers for Objective-C things (like class definitions and such) and #include standard C stuff that I need. For example, one of my source files might look like this:

#import <Foundation/Foundation.h>
#include <asl.h>
#include <mach/mach.h>

There seems to be a lot of confusion regarding the preprocessor. What the compiler does when it sees a #include that it replaces that line with the contents of the included files, no questions asked. So if you have a file a.h with this contents:

typedef int my_number;

and a file b.c with this content:

#include "a.h"
#include "a.h"

the file b.c will be translated by the preprocessor before compilation to

typedef int my_number;
typedef int my_number;

which will result in a compiler error, since the type my_number is defined twice. Even though the definition is the same this is not allowed by the C language. Since a header often is used in more than one place include guards usually are used in C. This looks like this:

 #ifndef _a_h_included_
 #define _a_h_included_

 typedef int my_number;

 #endif

The file b.c still would have the whole contents of the header in it twice after being preprocessed. But the second instance would be ignored since the macro a_h_included would already have been defined. This works really well, but has two drawbacks. First of all the include guards have to be written, and the macro name has to be different in every header. And secondly the compiler has still to look for the header file and read it as often as it is included.

Objective-C has the #import preprocessor instruction (it also can be used for C and C++ code with some compilers and options). This does almost the same as #include, but it also notes internally which file has already been included. The #import line is only replaced by the contents of the named file for the first time it is encountered. Every time after that it is just ignored.


添加多个barButtonItem

- (void)viewDidLoad {
   
    [super viewDidLoad];
    [self configureView];
   
    // create three funky nav bar buttons
    UIBarButtonItem *one = [[UIBarButtonItem alloc]initWithTitle:@"One" style:UIBarButtonItemStylePlain target:self action:@selector(testMethod)];
    UIBarButtonItem *two = [[UIBarButtonItem alloc]initWithTitle:@"Two" style:UIBarButtonItemStylePlain target:self action:@selector(testMethod)];
    UIBarButtonItem *three = [[UIBarButtonItem alloc]initWithTitle:@"Three" style:UIBarButtonItemStylePlain target:self action:@selector(testMethod)];
   
    // create a spacer
    UIBarButtonItem *space = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:self action:nil];
    space.width = 30;
   
    NSArray *buttons = @[one, space, two, space, three];
   
    self.navigationItem.rightBarButtonItems = buttons;
}

78732B58-3BF3-402E-8DF4-108427E1B286.png

Bounds和frame的关系

Here are the key equations that tie these three pieces together:

frame.origin = center - bounds.size / 2
(which is the same as)
center = frame.origin + bounds.size / 2

(and there’s also)

frame.size = bounds.size

ref:bynomial.com/blog/?p=24


上传到App Store出现下面错误的解决方法

3B78C528-97E6-4E6A-8D69-2FFC9E6BDC4A.png 找到相应的bundle,把其中 executable file键给删除即可


关于canOpenURL的用法

E50BED71-879E-412B-A5B3-1F5D51880C7A.png


bash中$0的作用

These are positional arguments of the script. Executing

./script.sh Hello World

Will make

$0 = ./script.sh
$1 = Hello
$2 = World

在Xcode的run script中使用:

echo "warning: ohoh"
echo "error: meh"

60F1F439-81EA-41F6-A99C-DC7FC5690CBA.png


cancelsTouchesInView对手势的影响

screenshot.png

场景:对UIButton添加UITapGestureRecognizer

        UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc]
                                                 initWithTarget:self
                                                 action:@selector(hideAlert)];
        UIButton* btn=[[UIButton alloc] init];
        [btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];

条件1: tapGesture.cancelsTouchesInView = YES; 那么只有UIButton的点击事件会响应.

条件2: tapGesture.cancelsTouchesInView = NO; 那么tapGesture和UIButton的点击事件都会同时响应.


手势与响应链

gestureRecognizer不依赖responderChain。从[hitView -> window]链上的所有gestureRecognizers都可以处理touch。

touchesBegan:和touchesMoved:会同时发给hitView和[hitView -> window]链上的所有gestureRecognizers。

在touchesEnded阶段,如果沿链有gestureRecognizer识别成功就给hitView发touchesCancelled:(默认gestureRecognizer.cancelsTouchesInView == YES),如果识别失败就给hitView发touchesEnded:withEvent:。

Touch Handling 笔记

Gesture Recognizers与触摸事件分发


拦截UIScrollView的手势

UIScrollView 用得相当普遍,衍生出来的 UITableView 也用得不少。最近有人问我,当给 UIScrollView 加上左右滑动手势 UISwipeGesture 时,感觉好难滑动,必须要很平的左右划才会触发 Swipe,否则都是触发 UIScrollView 的滚动事件。

这时候,我们会想到,不需要那么水平的滑动就好了,例如以滑动45度为分割线,滑动轨迹与水平线夹角在正负45度,都认为是水平滑动,超过45度,就认为是垂直滚动。

看上去好像可以做。那么我们就要拦截发送给 UIScrollView 的手势——重载 UIScrollView 的手势响应方法:

- (void)viewDidLoad {
    [super viewDidLoad];
    UISwipeGestureRecognizer* swap=[[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swapAction:)];
    swap.direction=UISwipeGestureRecognizerDirectionLeft;
    [_scrollview addGestureRecognizer:swap];
}

-(void)swapAction:(UISwipeGestureRecognizer*)swipe{
    NSLog(@"swapAction");
}

//_scrollview
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        CGPoint point = [(UIPanGestureRecognizer *)gestureRecognizer translationInView:self];
        if ((fabs(point.y) / fabs(point.x)) < 1) { // 判断角度 tan(45),这里需要通过正负来判断手势方向
            NSLog(@"横向手势");
            return NO;
        }
    }
    return [super gestureRecognizerShouldBegin:gestureRecognizer];
}
重载 UIScrollView,用这个新的对象,并且适当的调整其中的角度,来优化 APP 中的手势灵敏度。

NSMutableArray的KVO

@interface MyArray : NSObject
@property(nonatomic,strong) NSMutableArray* innerArray;
@end

#import "MyArray.h"
@interface MyArray()
@end
@implementation MyArray
-(id)init{
    self=[super init];
    if(self){
        _innerArray=[[NSMutableArray alloc] init];
    }
    return self;
}
//手动KVO,当然也可以不实现下面的方法,那么会进行自动KVO
- (void)insertObject:(NSObject *)object inInnerArrayAtIndex:(NSUInteger)index {
    [self.innerArray insertObject:object atIndex:index];
}
- (void)insertInnerArray:(NSArray *)array atIndexes:(NSIndexSet *)indexes {
    [self.innerArray insertObjects:array atIndexes:indexes];
}
- (void)removeInnerArrayAtIndexes:(NSIndexSet *)indexes {
    [self.innerArray removeObjectsAtIndexes:indexes];
}
@end

#import "ViewController.h"
#import "MyArray.h"
@interface ViewController ()
@property(nonatomic,strong) MyArray* arrObj;
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.arrObj=[[MyArray alloc] init];
    [self.arrObj addObserver:self forKeyPath:@"innerArray" options:NSKeyValueObservingOptionNew context:nil];

}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSLog(@"%ld",(long)self.arrObj.innerArray.count);
    NSLog(@"object: %@ keyPath: %@ property: %p  change:%@", object, keyPath, self.arrObj.innerArray,change);
}

- (IBAction)clickAction:(id)sender {
    static int val=0;
   //如果要想KVO被触发,需要使用如下的mutalbeArrayValueForKey来获取一个新的数组,对这个数组进行操作,那么会改变innerArray的内容,同时,也会让KVO触发.
    [[self.arrObj mutableArrayValueForKey:@"innerArray" ] addObject:[NSString stringWithFormat:@"%d",val++]];
}

@end

I would not use willChangeValueForKey and didChangeValueForKey in this situation. For one, they're meant to indicate the value at that path has changed, not that values in a to-many relationship are changing. You would want to use willChange:valuesAtIndexes:forKey: instead, if you did it this way. Even so, using manual KVO notifications like this is bad encapsulation. A better way of doing it is defining a method addSomeObject: in the class that actually owns the array, which would include the manual KVO notifications. This way, outside methods that are adding objects to the array don't need to worry about handling the array owner's KVO as well, which wouldn't be very intuitive and could lead to unneeded code and possibly bugs if you start adding objects to the array from several places.

In this example I would actually continue to use mutableArrayValueForKey:. I'm not positive with mutable arrays, but I believe from reading the documentation that this method actually replaces the entire array with a new object, so if performance is a concern you'll also want to implement insertObject:in<Key>AtIndex: and removeObjectFrom<Key>AtIndex: in the class that owns the array.

ref:stackoverflow.com/questions/3…

atomic一定是线程安全的嘛?

atomic只保证写操作的原子性,并不能保证线程安全,线程安全和原子性不是同一个逻辑层面的问题,原子性可以说,只是保证线程安全一个手段而已. 比如对于两个属性的组合,对于firstname和lastname的访问是原子性的,但是对于firstname+lastname的原子性就不能保证了.那么线程安全也就不能保证了.

An atomic property in Objective C guarantees that you will never see partial writes. When a @property has the attribute atomic it is impossible to only partially write the value. The setter is like that:

- (void)setProp:(NSString *)newValue {
    [_prop lock];
    _prop = newValue;
    [_prop unlock];
}

So if two thread want to write the value @"test" and @"otherTest" at the same time, then at any given time the property can only be the initial value of the property or @"test" or @"otherTest". nonatomic is faster but the value is a garbage value and no partial String of @"test"/@"otherTest" (thx @Gavin) or any other garbage value.

But atomic is only thread-safe with simple use. It is not garantueed. Appledoc says the following:

Consider an XYZPerson object in which both a person’s first and last names are changed using atomic accessors from one thread. If another thread accesses both names at the same time, the atomic getter methods will return complete strings (without crashing), but there’s no guarantee that those values will be the right names relative to each other. If the first name is accessed before the change, but the last name is accessed after the change, you’ll end up with an inconsistent, mismatched pair of names.

I never had a problem using atomic at all. I designed the code that way, that there is not problem with atomic properties.

UITabBarViewController的横线

BB34A1EF-540A-4605-87E1-F929BF087F53.png

[[UITabBar appearance] setShadowImage:[[UIImage alloc] init]];

获取UITableView当前在最上面显示的section

  [[tableView indexPathForCell:[[menu visibleCells] objectAtIndex:0]] section]; 

私有init方法

- (instancetype)init {
    [self doesNotRecognizeSelector:_cmd];
    return nil;
}

- (instancetype)initPrivate {
    self = [super init];
    if (self) {
    }
    return self;
}

+ (instancetype)sharedInstance {
    static MySingleton *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] initPrivate];
    });
    return sharedInstance;
}

Have init return your singleton

- (instancetype)init {
    return [[self class] sharedInstance];
}
- (instancetype)initPrivate {
    self = [super init];
    if (self) {
    }
    return self;
}
+ (instancetype)sharedInstance {
    static MySingleton2 *sharedInstance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedInstance = [[self alloc] initPrivate];
    });
    return sharedInstance;
}