Objective C static class member having bad access - ios

I am trying to use the static variable here tagsToCheck, accessible from all static methods in this class.
What is the best way?
A retain here solve the problem, but is it the right way?
tagsToCheck = [#[#"<html>", #"<br>", #"<br />", #"<br/>", #"<p>", #"<div>", #"<b>",
#"<i>", #"<font>", #"<ul>", #"<li>"] retain];
The original code is,
static NSArray * tagsToCheck = nil;
#implementation DictionaryUtil
+ (void) initialize {
tagsToCheck = #[#"<html>", #"<br>", #"<br />", #"<br/>", #"<p>", #"<div>", #"<b>",
#"<i>", #"<font>", #"<ul>", #"<li>"];
}
+ (BOOL) isHtml:(NSString *)string
{
if (!string) return NO;
for (NSString *tag in tagsToCheck) { // bad access here for tagsToCheck
if ([string rangeOfString:tag options:NSCaseInsensitiveSearch].location != NSNotFound) {
return YES;
}
}
return NO;
}

Yes the Objective-C array literal is an autoreleased object, so using retain is correct.

Using retain is not the best way. The best way is to enable Automatic Reference Counting.

Related

NSString value nil inside onEnterTransitionDidFinish method

I guess its a very basic memory concept. But couldn't figure it out what happens with below case. Any insight would be helpful.
This could be similar to Problems with NSString inside viewWillDisappear
But I wanted to know why there requires a #property. How can we do it without taking #property. Please provide some inside view.
in .h I have NSString *someString
in .mm (this is my non-ARC cocos2d+box2d game scene)
-(id)initWithString:(NSString *)tempString
{
if(self = [super init])
{
someString = [[NSString allo]init];
someString = tempString;
}
return self;
}
-(void)onEnterTransitionDidfinish
{
[super onEnterTransitionDidfinish];
NSLog("The String is %#",someString);//Becomes nil here
}
-(void)printString
{
NSLog(#"The String is %#",someString);//This works fine
}
If you are not using ARC then you need to learn a lot more about memory management.
The following two lines:
someString = [[NSString allo]init];
someString = tempString;
should be:
someString = [tempString copy]; // or [tempString retain];
And be sure you call [someString release] in your dealloc method.
BTW - you are not using a property. someString is declared as an instance variable, not a property.

Is it possible to just get the domain and TLD from an NSURL? No www or anything else?

Say I have http://www.youtube.com, http://youtube.com, and https://www1.youtube.com/moretext. How would I write a check to see if all of those URLs are from youtube.com?
I tried url.host and it seems to keep the www. and whatnot, which is not what I want.
I basically just want to be able to say:
if ([url isFromWebsite:#"youtube.com"]) {
// Do things.
}
Is this possible?
Here’s an untested category on NSURL that will provide the method you want.
#implementation NSURL (IsFromWebsite)
- (BOOL) isFromWebsite:(NSString *)domain
{
NSArray *selfHostComponents = [[self host] componentsSeparatedByString:#"."];
NSArray *targetHostComponents = [domain componentsSeparatedByString:#"."];
NSInteger selfComponentsCount = [selfHostComponents count];
NSInteger targetComponentsCount = [targetHostComponents count];
NSInteger offset = selfComponentsCount - targetComponentsCount;
if (offset < 0)
return NO;
for (NSUInteger i = offset; i < selfComponentsCount; i++) {
if (![selfHostComponents[i] isEqualToString:targetHostComponents[i - offset]])
return NO;
}
return YES;
}
#end
Edit: Another (also untested) way to do the same thing, as suggested by Jesse Rusak:
#implementation NSURL (IsFromWebsite)
- (BOOL) isFromWebsite:(NSString *)domain
{
NSArray *selfComponents = [[self host] componentsSeparatedByString:#"."];
NSArray *targetComponents = [domain componentsSeparatedByString:#"."];
NSInteger sizeDifference = [selfComponents count] - [targetComponents count];
if (sizeDifference < 0)
return NO;
return [[selfComponents subarrayWithRange:NSMakeRange(sizeDifference, [targetComponents count])]
isEqualToArray:targetComponents];
}
#end
NSURL — and indeed URLs in general — doesn't care too much about the actual content of the host portion of the URL; that's up to clients to interpret and handle. So it's up to you to perform your own handling of this info.
Here's a nice simple way:
- (BOOL)isYouTubeURL:(NSURL *)url;
{
NSString *host = url.host;
// Test the simple case of the domain being a direct match
// Case-insensitive, as domain names are defined to be
if ([host caseInsensitiveCompare:#"youtube.com"] == NSOrderedSame) {
return YES;
}
// And now the more complex case of a subdomain
// Look back from just the end of the string
NSStringCompareOptions options = NSBackwardsSearch|NSAnchoredSearch|NSCaseInsensitiveSearch;
NSRange range = [host rangeOfString:#".youtube.com" options:options];
return (range.location != NSNotFound);
}
You could optimise this if needed by searching only for the domain, and then seeing if the previous character doesn't exist (case #1 above), or has a . before it (case #2 above). Going even further, CFURL has API to tell you where in the raw string the host lies, and could search directly through that.
In case anyone needs bdesham's answer in Swift 3.
extension URL {
func isFromWebsite(domain: String) -> Bool {
var selfHostComponents = self.host!.components(separatedBy: ".")
var targetHostComponents = domain.components(separatedBy: ".")
let selfComponentsCount = selfHostComponents.count
let targetComponentsCount = targetHostComponents.count
let offset = selfComponentsCount - targetComponentsCount
if offset < 0 {
return false
}
for i in offset..<selfComponentsCount {
if !(selfHostComponents[i] == targetHostComponents[i - offset]) {
return false
}
}
return true
}
}
You can use the following code as NSString category
- (BOOL)isFromWebsite: (NSString *)website {
NSRange searchResult = [self rangeOfString:website];
if (searchResult.location != NSNotFound) {
return YES;
}
return NO;
}
You can use case insensitive compare also if that is a use case.
Hope this helps.

Core Data: Automatically Trim String Properties

For my Core Data NSManagedObject, I would like to ensure any NSString properties only contain strings that have been trimmed of whitespace.
I'm aware that I could achieve this by overriding each setter method, like so:
- (void)setSomeProperty:(NSString *)someProperty
{
someProperty = [someProperty stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
if ((!someProperty && !self.someProperty) || [someProperty isEqualToString:self.someProperty]) return;
[self willChangeValueForKey:#"someProperty"];
[self setPrimitiveValue:someProperty forKey:#"someProperty"];
[self didChangeValueForKey:#"someProperty"];
}
However, this seems like a lot of code to have to write, especially since my managed object is likely to have quite a few NSString properties.
Is there an easier way?
You could create a custom NSValueTransformer for NSString and assign all of your NSString properties to the new transformer in the model editor:
#interface StringTransformer: NSValueTransformer {}
#end
#implementation StringTransformer
+ (Class)transformedValueClass {
return [NSString class];
}
+ (BOOL)allowsReverseTransformation {
return YES;
}
- (id)transformedValue:(id)value {
return value;
}
- (id)reverseTransformedValue:(id)value {
return [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
#end
If you only need to ensure that the saved data is trimmed then you can implement willSave and use changedValues to check only the changed values. This will also make it easy to do in a loop to minimise code duplication.
You could do it during property validation:
- (BOOL)validateSomeProperty:(id *)inOutValue error:(NSError **)error
{
if (inOutValue)
{
NSString *value = *inOutValue;
*inOutValue = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
return YES;
}
Core data will automatically call validateSomeProperty:error: before saving your record, so this will make sure that any data that gets saved is trimmed. It won't stop the on-change events firing if someone changes it from, say, foo to \n\nfoo\n\n, but it does mean that you don't have to fire them by hand.

OCMock passing any CGSize

I'm using OCMock and I'm trying to do to something like this in one of my tests:
[[mockScrollView expect] setContentSize:[OCMArg any]];
The problem is that [OCMArg any] returns an id type, and I want to use any CGSize, because I don't know it's exact value. How can I pass this argument?
With version 2.2 of OCMock you can use ignoringNonObjectArgs
[[mockScrollView expect] ignoringNonObjectArgs] setContentSize:dummySize];
AFAIK there is no way to accomplish that with OCMock.
An alternative is to create a hand made mock. This is a subclass of UIScrollView where you override setContentSize: assigning the given size to a ivar that later on you can inspect.
Other easier option is to use a real UIScrollView and check directly is the contentSize is the one you expect. I would go for this solution.
Sadly it looks like you'd have to extend OCMock in order to accomplish this. You could follow this pattern...
OCMArg.h
// Add this:
+ (CGSize)anyCGSize;
OCMArg.c
// Add this:
+ (CGSize)anyCGSize
{
return CGSizeMake(0.1245, 5.6789);
}
// Edit this method:
+ (id)resolveSpecialValues:(NSValue *)value
{
const char *type = [value objCType];
// Add this:
if(type[0] == '{')
{
NSString *typeString = [[[NSString alloc] initWithCString:type encoding:NSUTF8StringEncoding] autorelease];
if ([typeString rangeOfString:#"CGSize"].location != NSNotFound)
{
CGSize size = [value CGSizeValue];
if (CGSizeEqualToSize(size, CGSizeMake(0.1245, 5.6789)))
{
return [OCMArg any];
}
}
}
// Existing code stays the same...
if(type[0] == '^')
{
void *pointer = [value pointerValue];
if(pointer == (void *)0x01234567)
return [OCMArg any];
if((pointer != NULL) && (object_getClass((id)pointer) == [OCMPassByRefSetter class]))
return (id)pointer;
}
return value;
}

NSString Category Method Not Getting Called

The problem I am trying to solve is to convert nil strings into empty strings so I can put that as a value in NSDictionary.
I have the following code:
//Accessed in class
prospect.title = [self.titleLabel.text emptyWhenNil];
// NSString+Additions Category
-(NSString *) emptyWhenNil
{
if(self == nil)
return #"";
return self;
}
But for some reason the emptyWhenNil method is never even invoked!! I am lost..
The problem is, when your string pointer is nil, you have no object to send that category message to. Messages to nil are not executed (and return nil) in Objective-C.
If you want to put "null" objects into a collection, such as an NSDictionary, or NSArray, you can use the NSNull class instead.
Alternatively you could implement this method as a class method:
+ (NSString *)emptyStringIfNil:(NSString *)str {
if (!str) return #"";
return str;
}
You would then call this as
prospect.title = [NSString emptyStringIfNil:self.titleLabel.text];
If the string is nil, there is no self to execute the method on. The default behaviour of the runtime in this situation is to return a default nil value.
You should implement this as a static method instead. Something like this:
+ (NSString *)emptyStringIfNil:(NSString *)input
{
return input ? input : #"";
}
If the NSString pointer is nil, the method will not be called on it, what you can do is create a static method:
+ (NSString *) emptyIfNil:(NSString *) str
{
return str == nil ? #"" : str;
}

Resources