Basic data validation: from Objective-C to Swift

I am creating a dummy iOS project to understand how to implement validation in Core Data using Swift. The project's Core Data model has one object named Person

, which contains two attributes: firstName

and lastName

. The project is based on Swift, but to run it I use Objective-C to define the subclass NSManagedObject

:

Person.h

@interface Person : NSManagedObject

@property (nonatomic, retain) NSString *firstName;
@property (nonatomic, retain) NSString *lastName;

@end

      

Person.m

@implementation Person

@dynamic firstName;
@dynamic lastName;    

-(BOOL)validateFirstName:(id *)ioValue error:(NSError **)outError {
    if (*ioValue == nil || [*ioValue isEqualToString: @""]) {
        if (outError != NULL) {
            NSString *errorStr = NSLocalizedStringFromTable(@"First name can't be empty", @"Person", @"validation: first name error");
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorStr };
            NSError *error = [[NSError alloc] initWithDomain:@"Domain" code: 101 userInfo: userInfoDict];
            *outError = error;
        }
        return NO;
    }
    return YES;
}

@end

      

Human-Bridging-header.h

#import "Person.h"

      

In the master data model editor, I have set the entity class inside the data model inspector as stated:

class: Person

      

When I first run the project, I create an instance Person

in a method AppDelegate

application:didFinishLaunchingWithOptions:

with the following code:

if !NSUserDefaults.standardUserDefaults().boolForKey("isNotInitialLoad") {
    let person = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: managedObjectContext!) as Person
    person.firstName = "John"
    person.lastName = "Doe"

    var error: NSError?
    if !managedObjectContext!.save(&error) {
        println("Unresolved error \(error), \(error!.userInfo)")
        abort()
    }

    NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isNotInitialLoad")
    NSUserDefaults.standardUserDefaults().synchronize()
}

      

The project has one UIViewController

with the following code:

class ViewController: UIViewController {

    var managedObjectContext: NSManagedObjectContext!
    var person: Person!

    override func viewDidLoad() {
        super.viewDidLoad()

        //Fetch the Person object
        var error: NSError?
        let fetchRequest = NSFetchRequest(entityName: "Person")
        let array = managedObjectContext.executeFetchRequest(fetchRequest, error:&error)
        if array == nil {
            println("Unresolved error \(error), \(error!.userInfo)")
            abort()
        }
        person = array![0] as Person
    }

    @IBAction func changeFirstName(sender: AnyObject) {
        //Generate a random firstName
        let array = ["John", "Jimmy", "James", "Johnny", ""]
        person.firstName = array[Int(arc4random_uniform(UInt32(5)))]

        var error: NSError?
        if !managedObjectContext.save(&error) {
            println("Unresolved error \(error), \(error!.userInfo)")
            return
        }

        //If success, display the new person name
        println("\(person.firstName)" + " " + "\(person.lastName)")
    }

}

      

changeFirstName:

linked to a UIButton

. Therefore, whenever I click on this button, a new one is String

generated randomly and assigned person.firstName

. If this new is String

empty, validateFirstName:error:

creates NSError

, and the save operation fails.

This works fine, but in order to have a clean design Swift, I decided to remove Person.h

, Person.m

and Person-Bridging-Header.h

and replace them with a single file Swift:

class Person: NSManagedObject {

    @NSManaged var firstName: String
    @NSManaged var lastName: String

    func validateFirstName(ioValue: AnyObject, error: NSErrorPointer) -> Bool {
        if ioValue as? String == "" {
            if error != nil {
                let myBundle = NSBundle(forClass: self.dynamicType)
                let errorString = myBundle.localizedStringForKey("First name can't be empty", value: "validation: first name error", table: "Person")
                let userInfo = NSMutableDictionary()
                userInfo[NSLocalizedFailureReasonErrorKey] = errorString
                userInfo[NSValidationObjectErrorKey] = self
                var validationError = NSError(domain: "Domain", code: NSManagedObjectValidationError, userInfo: userInfo)
                error.memory = validationError
            }
            return false
        }

        return true
    }

}

      

In the master data model editor, I also changed the entity class inside the data model inspector as stated:

class: Person.Person //<Project name>.Person

      

Now the problem is that the project crashes when I call changeFirstName:

. The strangest thing is that if I put a breakpoint inside validateFirstName:

, I see that this method is never called.

What am I doing wrong?

+3


source to share


2 answers


I'm guessing a bit here, but the parameter (id *)ioValue

shows up in Swift as

ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>

      

so the Swift variant should probably look like

func validateFirstName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>, error: NSErrorPointer) -> Bool {
    if let firstName = ioValue.memory as? String {
        if firstName == "" {
            // firstName is empty string
            // ...
        }
    } else {
        // firstName is nil (or not a String)
        // ...
    }
    return true
}

      



Update for Swift 2:

func validateFirstName(ioValue: AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
    guard let firstName = ioValue.memory as? String where firstName != ""  else {
        // firstName is nil, empty, or not a String
        let errorString = "First name can't be empty"
        let userDict = [ NSLocalizedDescriptionKey: errorString ]
        throw NSError(domain: "domain", code: NSManagedObjectValidationError, userInfo: userDict)
    }
    // firstName is a non-empty string
}

      

As @SantaClaus correctly pointed out, now the validation function will throw an error if validation fails.

+5


source


Apple 's Master Data Programming Guide has now been updated for Swift 3. Here's a sample code on the Object Lifecycle> Object Validation page (Object Validation page) has memory

been renamed to pointee

):

func validateAge(value: AutoreleasingUnsafeMutablePointer<AnyObject?>!) throws {
    if value == nil {
        return
    }

    let valueNumber = value!.pointee as! NSNumber
    if valueNumber.floatValue > 0.0 {
        return
    }
    let errorStr = NSLocalizedString("Age must be greater than zero", tableName: "Employee", comment: "validation: zero age error")
    let userInfoDict = [NSLocalizedDescriptionKey: errorStr]
    let error = NSError(domain: "EMPLOYEE_ERROR_DOMAIN", code: 1123, userInfo: userInfoDict)
    throw error
}

      




The EDIT: . The example is not entirely correct. To make it work, I changed AutoreleasingUnsafeMutablePointer<AnyObject?>

to expanded optional and value?.pointee

to value.pointee

.

0


source







All Articles